diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1570,6 +1570,7 @@ 'PhabricatorApplicationDatasource' => 'applications/meta/typeahead/PhabricatorApplicationDatasource.php', 'PhabricatorApplicationDetailViewController' => 'applications/meta/controller/PhabricatorApplicationDetailViewController.php', 'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php', + 'PhabricatorApplicationEditEngine' => 'applications/transactions/editengine/PhabricatorApplicationEditEngine.php', 'PhabricatorApplicationEmailCommandsController' => 'applications/meta/controller/PhabricatorApplicationEmailCommandsController.php', 'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php', 'PhabricatorApplicationPanelController' => 'applications/meta/controller/PhabricatorApplicationPanelController.php', @@ -2063,6 +2064,7 @@ 'PhabricatorDataCacheSpec' => 'applications/cache/spec/PhabricatorDataCacheSpec.php', 'PhabricatorDataNotAttachedException' => 'infrastructure/storage/lisk/PhabricatorDataNotAttachedException.php', 'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php', + 'PhabricatorDatasourceEditField' => 'applications/transactions/editfield/PhabricatorDatasourceEditField.php', 'PhabricatorDateTimeSettingsPanel' => 'applications/settings/panel/PhabricatorDateTimeSettingsPanel.php', 'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php', 'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php', @@ -2097,6 +2099,7 @@ 'PhabricatorEdgeTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeTestCase.php', 'PhabricatorEdgeType' => 'infrastructure/edges/type/PhabricatorEdgeType.php', 'PhabricatorEdgeTypeTestCase' => 'infrastructure/edges/type/__tests__/PhabricatorEdgeTypeTestCase.php', + 'PhabricatorEditField' => 'applications/transactions/editfield/PhabricatorEditField.php', 'PhabricatorEditor' => 'infrastructure/PhabricatorEditor.php', 'PhabricatorElasticSearchEngine' => 'applications/search/engine/PhabricatorElasticSearchEngine.php', 'PhabricatorElasticSearchSetupCheck' => 'applications/config/check/PhabricatorElasticSearchSetupCheck.php', @@ -2554,6 +2557,7 @@ 'PhabricatorPasteController' => 'applications/paste/controller/PhabricatorPasteController.php', 'PhabricatorPasteDAO' => 'applications/paste/storage/PhabricatorPasteDAO.php', 'PhabricatorPasteEditController' => 'applications/paste/controller/PhabricatorPasteEditController.php', + 'PhabricatorPasteEditEngine' => 'applications/paste/editor/PhabricatorPasteEditEngine.php', 'PhabricatorPasteEditor' => 'applications/paste/editor/PhabricatorPasteEditor.php', 'PhabricatorPasteListController' => 'applications/paste/controller/PhabricatorPasteListController.php', 'PhabricatorPastePastePHIDType' => 'applications/paste/phid/PhabricatorPastePastePHIDType.php', @@ -2654,6 +2658,7 @@ 'PhabricatorPolicyDAO' => 'applications/policy/storage/PhabricatorPolicyDAO.php', 'PhabricatorPolicyDataTestCase' => 'applications/policy/__tests__/PhabricatorPolicyDataTestCase.php', 'PhabricatorPolicyEditController' => 'applications/policy/controller/PhabricatorPolicyEditController.php', + 'PhabricatorPolicyEditField' => 'applications/transactions/editfield/PhabricatorPolicyEditField.php', 'PhabricatorPolicyException' => 'applications/policy/exception/PhabricatorPolicyException.php', 'PhabricatorPolicyExplainController' => 'applications/policy/controller/PhabricatorPolicyExplainController.php', 'PhabricatorPolicyFilter' => 'applications/policy/filter/PhabricatorPolicyFilter.php', @@ -2917,6 +2922,7 @@ 'PhabricatorSearchWorker' => 'applications/search/worker/PhabricatorSearchWorker.php', 'PhabricatorSecurityConfigOptions' => 'applications/config/option/PhabricatorSecurityConfigOptions.php', 'PhabricatorSecuritySetupCheck' => 'applications/config/check/PhabricatorSecuritySetupCheck.php', + 'PhabricatorSelectEditField' => 'applications/transactions/editfield/PhabricatorSelectEditField.php', 'PhabricatorSendGridConfigOptions' => 'applications/config/option/PhabricatorSendGridConfigOptions.php', 'PhabricatorSessionsSettingsPanel' => 'applications/settings/panel/PhabricatorSessionsSettingsPanel.php', 'PhabricatorSettingsAddEmailAction' => 'applications/settings/action/PhabricatorSettingsAddEmailAction.php', @@ -2957,6 +2963,7 @@ 'PhabricatorSlugTestCase' => 'infrastructure/util/__tests__/PhabricatorSlugTestCase.php', 'PhabricatorSortTableUIExample' => 'applications/uiexample/examples/PhabricatorSortTableUIExample.php', 'PhabricatorSourceCodeView' => 'view/layout/PhabricatorSourceCodeView.php', + 'PhabricatorSpaceEditField' => 'applications/transactions/editfield/PhabricatorSpaceEditField.php', 'PhabricatorSpacesApplication' => 'applications/spaces/application/PhabricatorSpacesApplication.php', 'PhabricatorSpacesArchiveController' => 'applications/spaces/controller/PhabricatorSpacesArchiveController.php', 'PhabricatorSpacesCapabilityCreateSpaces' => 'applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php', @@ -3063,6 +3070,8 @@ 'PhabricatorTestNoCycleEdgeType' => 'applications/transactions/edges/PhabricatorTestNoCycleEdgeType.php', 'PhabricatorTestStorageEngine' => 'applications/files/engine/PhabricatorTestStorageEngine.php', 'PhabricatorTestWorker' => 'infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php', + 'PhabricatorTextAreaEditField' => 'applications/transactions/editfield/PhabricatorTextAreaEditField.php', + 'PhabricatorTextEditField' => 'applications/transactions/editfield/PhabricatorTextEditField.php', 'PhabricatorTime' => 'infrastructure/time/PhabricatorTime.php', 'PhabricatorTimeGuard' => 'infrastructure/time/PhabricatorTimeGuard.php', 'PhabricatorTimeTestCase' => 'infrastructure/time/__tests__/PhabricatorTimeTestCase.php', @@ -3084,6 +3093,7 @@ 'PhabricatorTokenReceiverQuery' => 'applications/tokens/query/PhabricatorTokenReceiverQuery.php', 'PhabricatorTokenTokenPHIDType' => 'applications/tokens/phid/PhabricatorTokenTokenPHIDType.php', 'PhabricatorTokenUIEventListener' => 'applications/tokens/event/PhabricatorTokenUIEventListener.php', + 'PhabricatorTokenizerEditField' => 'applications/transactions/editfield/PhabricatorTokenizerEditField.php', 'PhabricatorTokensApplication' => 'applications/tokens/application/PhabricatorTokensApplication.php', 'PhabricatorTokensSettingsPanel' => 'applications/settings/panel/PhabricatorTokensSettingsPanel.php', 'PhabricatorTooltipUIExample' => 'applications/uiexample/examples/PhabricatorTooltipUIExample.php', @@ -5489,6 +5499,7 @@ 'PhabricatorApplicationDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorApplicationDetailViewController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController', + 'PhabricatorApplicationEditEngine' => 'Phobject', 'PhabricatorApplicationEmailCommandsController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationLaunchView' => 'AphrontTagView', 'PhabricatorApplicationPanelController' => 'PhabricatorApplicationsController', @@ -6080,6 +6091,7 @@ 'PhabricatorDataCacheSpec' => 'PhabricatorCacheSpec', 'PhabricatorDataNotAttachedException' => 'Exception', 'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck', + 'PhabricatorDatasourceEditField' => 'PhabricatorTokenizerEditField', 'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDebugController' => 'PhabricatorController', 'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', @@ -6113,6 +6125,7 @@ 'PhabricatorEdgeTestCase' => 'PhabricatorTestCase', 'PhabricatorEdgeType' => 'Phobject', 'PhabricatorEdgeTypeTestCase' => 'PhabricatorTestCase', + 'PhabricatorEditField' => 'Phobject', 'PhabricatorEditor' => 'Phobject', 'PhabricatorElasticSearchEngine' => 'PhabricatorSearchEngine', 'PhabricatorElasticSearchSetupCheck' => 'PhabricatorSetupCheck', @@ -6647,6 +6660,7 @@ 'PhabricatorPasteController' => 'PhabricatorController', 'PhabricatorPasteDAO' => 'PhabricatorLiskDAO', 'PhabricatorPasteEditController' => 'PhabricatorPasteController', + 'PhabricatorPasteEditEngine' => 'PhabricatorApplicationEditEngine', 'PhabricatorPasteEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorPasteListController' => 'PhabricatorPasteController', 'PhabricatorPastePastePHIDType' => 'PhabricatorPHIDType', @@ -6762,6 +6776,7 @@ 'PhabricatorPolicyDAO' => 'PhabricatorLiskDAO', 'PhabricatorPolicyDataTestCase' => 'PhabricatorTestCase', 'PhabricatorPolicyEditController' => 'PhabricatorPolicyController', + 'PhabricatorPolicyEditField' => 'PhabricatorEditField', 'PhabricatorPolicyException' => 'Exception', 'PhabricatorPolicyExplainController' => 'PhabricatorPolicyController', 'PhabricatorPolicyFilter' => 'Phobject', @@ -7090,6 +7105,7 @@ 'PhabricatorSearchWorker' => 'PhabricatorWorker', 'PhabricatorSecurityConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorSecuritySetupCheck' => 'PhabricatorSetupCheck', + 'PhabricatorSelectEditField' => 'PhabricatorEditField', 'PhabricatorSendGridConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorSessionsSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorSettingsAddEmailAction' => 'PhabricatorSystemAction', @@ -7140,6 +7156,7 @@ 'PhabricatorSlugTestCase' => 'PhabricatorTestCase', 'PhabricatorSortTableUIExample' => 'PhabricatorUIExample', 'PhabricatorSourceCodeView' => 'AphrontView', + 'PhabricatorSpaceEditField' => 'PhabricatorEditField', 'PhabricatorSpacesApplication' => 'PhabricatorApplication', 'PhabricatorSpacesArchiveController' => 'PhabricatorSpacesController', 'PhabricatorSpacesCapabilityCreateSpaces' => 'PhabricatorPolicyCapability', @@ -7252,6 +7269,8 @@ 'PhabricatorTestNoCycleEdgeType' => 'PhabricatorEdgeType', 'PhabricatorTestStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorTestWorker' => 'PhabricatorWorker', + 'PhabricatorTextAreaEditField' => 'PhabricatorEditField', + 'PhabricatorTextEditField' => 'PhabricatorEditField', 'PhabricatorTime' => 'Phobject', 'PhabricatorTimeGuard' => 'Phobject', 'PhabricatorTimeTestCase' => 'PhabricatorTestCase', @@ -7278,6 +7297,7 @@ 'PhabricatorTokenReceiverQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorTokenTokenPHIDType' => 'PhabricatorPHIDType', 'PhabricatorTokenUIEventListener' => 'PhabricatorEventListener', + 'PhabricatorTokenizerEditField' => 'PhabricatorEditField', 'PhabricatorTokensApplication' => 'PhabricatorApplication', 'PhabricatorTokensSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorTooltipUIExample' => 'PhabricatorUIExample', diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -521,6 +521,14 @@ } + public function buildApplicationCrumbsForEditEngine() { + // TODO: This is kind of gross, I'm bascially just making this public so + // I can use it in EditEngine. We could do this without making it public + // by using controller delegation, or make it properly public. + return $this->buildApplicationCrumbs(); + } + + /* -( Deprecated )--------------------------------------------------------- */ diff --git a/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php b/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php --- a/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php +++ b/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php @@ -44,16 +44,11 @@ $paste = PhabricatorPaste::initializeNewPaste($viewer); - $file = PhabricatorPasteEditor::initializeFileForPaste( - $viewer, - $title, - $content); - $xactions = array(); $xactions[] = id(new PhabricatorPasteTransaction()) ->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT) - ->setNewValue($file->getPHID()); + ->setNewValue($content); $xactions[] = id(new PhabricatorPasteTransaction()) ->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE) diff --git a/src/applications/paste/controller/PhabricatorPasteEditController.php b/src/applications/paste/controller/PhabricatorPasteEditController.php --- a/src/applications/paste/controller/PhabricatorPasteEditController.php +++ b/src/applications/paste/controller/PhabricatorPasteEditController.php @@ -3,248 +3,9 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { public function handleRequest(AphrontRequest $request) { - $viewer = $request->getViewer(); - $id = $request->getURIData('id'); - - $parent = null; - $parent_id = null; - if (!$id) { - $is_create = true; - - $paste = PhabricatorPaste::initializeNewPaste($viewer); - - $parent_id = $request->getStr('parent'); - if ($parent_id) { - // NOTE: If the Paste is forked from a paste which the user no longer - // has permission to see, we still let them edit it. - $parent = id(new PhabricatorPasteQuery()) - ->setViewer($viewer) - ->withIDs(array($parent_id)) - ->needContent(true) - ->needRawContent(true) - ->execute(); - $parent = head($parent); - - if ($parent) { - $paste->setParentPHID($parent->getPHID()); - $paste->setViewPolicy($parent->getViewPolicy()); - } - } - - $paste->setAuthorPHID($viewer->getPHID()); - $paste->attachRawContent(''); - } else { - $is_create = false; - - $paste = id(new PhabricatorPasteQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($id)) - ->needRawContent(true) - ->executeOne(); - if (!$paste) { - return new Aphront404Response(); - } - } - - $v_space = $paste->getSpacePHID(); - if ($is_create && $parent) { - $v_title = pht('Fork of %s', $parent->getFullName()); - $v_language = $parent->getLanguage(); - $v_text = $parent->getRawContent(); - $v_space = $parent->getSpacePHID(); - } else { - $v_title = $paste->getTitle(); - $v_language = $paste->getLanguage(); - $v_text = $paste->getRawContent(); - } - $v_view_policy = $paste->getViewPolicy(); - $v_edit_policy = $paste->getEditPolicy(); - $v_status = $paste->getStatus(); - - if ($is_create) { - $v_projects = array(); - } else { - $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( - $paste->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - $v_projects = array_reverse($v_projects); - } - - $validation_exception = null; - if ($request->isFormPost()) { - $xactions = array(); - - $v_text = $request->getStr('text'); - $v_title = $request->getStr('title'); - $v_language = $request->getStr('language'); - $v_view_policy = $request->getStr('can_view'); - $v_edit_policy = $request->getStr('can_edit'); - $v_projects = $request->getArr('projects'); - $v_space = $request->getStr('spacePHID'); - $v_status = $request->getStr('status'); - - // NOTE: The author is the only editor and can always view the paste, - // so it's impossible for them to choose an invalid policy. - - if ($is_create || ($v_text !== $paste->getRawContent())) { - $file = PhabricatorPasteEditor::initializeFileForPaste( - $viewer, - $v_title, - $v_text); - - $xactions[] = id(new PhabricatorPasteTransaction()) - ->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT) - ->setNewValue($file->getPHID()); - } - - $xactions[] = id(new PhabricatorPasteTransaction()) - ->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE) - ->setNewValue($v_title); - $xactions[] = id(new PhabricatorPasteTransaction()) - ->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE) - ->setNewValue($v_language); - $xactions[] = id(new PhabricatorPasteTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($v_view_policy); - $xactions[] = id(new PhabricatorPasteTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) - ->setNewValue($v_edit_policy); - $xactions[] = id(new PhabricatorPasteTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_SPACE) - ->setNewValue($v_space); - $xactions[] = id(new PhabricatorPasteTransaction()) - ->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS) - ->setNewValue($v_status); - - $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $xactions[] = id(new PhabricatorPasteTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $proj_edge_type) - ->setNewValue(array('=' => array_fuse($v_projects))); - - $editor = id(new PhabricatorPasteEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - $xactions = $editor->applyTransactions($paste, $xactions); - return id(new AphrontRedirectResponse())->setURI($paste->getURI()); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - } - } - - $form = new AphrontFormView(); - - $langs = array( - '' => pht('(Detect From Filename in Title)'), - ) + PhabricatorEnv::getEnvConfig('pygments.dropdown-choices'); - - $form - ->setUser($viewer) - ->addHiddenInput('parent', $parent_id) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Title')) - ->setValue($v_title) - ->setName('title')) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Language')) - ->setName('language') - ->setValue($v_language) - ->setOptions($langs)); - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($paste) - ->execute(); - - $form->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicyObject($paste) - ->setPolicies($policies) - ->setValue($v_view_policy) - ->setSpacePHID($v_space) - ->setName('can_view')); - - $form->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicyObject($paste) - ->setPolicies($policies) - ->setValue($v_edit_policy) - ->setName('can_edit')); - - $form->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Status')) - ->setName('status') - ->setValue($v_status) - ->setOptions($paste->getStatusNameMap())); - - $form->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Projects')) - ->setName('projects') - ->setValue($v_projects) - ->setDatasource(new PhabricatorProjectDatasource())); - - $form - ->appendChild( - id(new AphrontFormTextAreaControl()) - ->setLabel(pht('Text')) - ->setValue($v_text) - ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) - ->setCustomClass('PhabricatorMonospaced') - ->setName('text')); - - $submit = new AphrontFormSubmitControl(); - - if (!$is_create) { - $submit->addCancelButton($paste->getURI()); - $submit->setValue(pht('Save Paste')); - $title = pht('Edit %s', $paste->getFullName()); - $short = pht('Edit'); - } else { - $submit->setValue(pht('Create Paste')); - $title = pht('Create New Paste'); - $short = pht('Create'); - } - - $form->appendChild($submit); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setForm($form); - - if ($validation_exception) { - $form_box->setValidationException($validation_exception); - } - - $crumbs = $this->buildApplicationCrumbs(); - if (!$is_create) { - $crumbs->addTextCrumb('P'.$paste->getID(), '/P'.$paste->getID()); - } - $crumbs->addTextCrumb($short); - - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $title, - )); + return id(new PhabricatorPasteEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php --- a/src/applications/paste/controller/PhabricatorPasteViewController.php +++ b/src/applications/paste/controller/PhabricatorPasteViewController.php @@ -137,9 +137,7 @@ $paste, PhabricatorPolicyCapability::CAN_EDIT); - $can_fork = $viewer->isLoggedIn(); $id = $paste->getID(); - $fork_uri = $this->getApplicationURI('/create/?parent='.$id); return id(new PhabricatorActionListView()) ->setUser($viewer) @@ -154,13 +152,6 @@ ->setHref($this->getApplicationURI("edit/{$id}/"))) ->addAction( id(new PhabricatorActionView()) - ->setName(pht('Fork This Paste')) - ->setIcon('fa-code-fork') - ->setDisabled(!$can_fork) - ->setWorkflow(!$can_fork) - ->setHref($fork_uri)) - ->addAction( - id(new PhabricatorActionView()) ->setName(pht('View Raw File')) ->setIcon('fa-file-text-o') ->setHref($this->getApplicationURI("raw/{$id}/"))); diff --git a/src/applications/paste/editor/PhabricatorPasteEditEngine.php b/src/applications/paste/editor/PhabricatorPasteEditEngine.php new file mode 100644 --- /dev/null +++ b/src/applications/paste/editor/PhabricatorPasteEditEngine.php @@ -0,0 +1,69 @@ +getViewer()); + } + + protected function newObjectQuery() { + return id(new PhabricatorPasteQuery()) + ->needRawContent(true); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Paste'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit %s %s', $object->getMonogram(), $object->getTitle()); + } + + protected function getObjectEditShortText($object) { + return $object->getMonogram(); + } + + protected function getObjectCreateShortText($object) { + return pht('Create Paste'); + } + + protected function getObjectViewURI($object) { + return '/P'.$object->getID(); + } + + protected function buildCustomEditFields($object) { + $langs = array( + '' => pht('(Detect From Filename in Title)'), + ) + PhabricatorEnv::getEnvConfig('pygments.dropdown-choices'); + + return array( + id(new PhabricatorTextEditField()) + ->setKey('title') + ->setLabel(pht('Title')) + ->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE) + ->setValue($object->getTitle()), + id(new PhabricatorSelectEditField()) + ->setKey('language') + ->setLabel(pht('Language')) + ->setAliases(array('lang')) + ->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE) + ->setValue($object->getLanguage()) + ->setOptions($langs), + id(new PhabricatorSelectEditField()) + ->setKey('status') + ->setLabel(pht('Status')) + ->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS) + ->setValue($object->getStatus()) + ->setOptions(PhabricatorPaste::getStatusNameMap()), + id(new PhabricatorTextAreaEditField()) + ->setKey('text') + ->setLabel(pht('Text')) + ->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT) + ->setMonospaced(true) + ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) + ->setValue($object->getRawContent()), + ); + } + +} diff --git a/src/applications/paste/editor/PhabricatorPasteEditor.php b/src/applications/paste/editor/PhabricatorPasteEditor.php --- a/src/applications/paste/editor/PhabricatorPasteEditor.php +++ b/src/applications/paste/editor/PhabricatorPasteEditor.php @@ -3,6 +3,8 @@ final class PhabricatorPasteEditor extends PhabricatorApplicationTransactionEditor { + private $fileName; + public function getEditorApplicationClass() { return 'PhabricatorPasteApplication'; } @@ -41,6 +43,35 @@ return $types; } + protected function shouldApplyInitialEffects( + PhabricatorLiskDAO $object, + array $xactions) { + return true; + } + + protected function applyInitialEffects( + PhabricatorLiskDAO $object, + array $xactions) { + + // Find the most user-friendly filename we can by examining the title of + // the paste and the pending transactions. We'll use this if we create a + // new file to store raw content later. + + $name = $object->getTitle(); + if (!strlen($name)) { + $name = 'paste.raw'; + } + + $type_title = PhabricatorPasteTransaction::TYPE_TITLE; + foreach ($xactions as $xaction) { + if ($xaction->getTransactionType() == $type_title) { + $name = $xaction->getNewValue(); + } + } + + $this->fileName = $name; + } + protected function getCustomTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { @@ -62,11 +93,26 @@ PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorPasteTransaction::TYPE_CONTENT: case PhabricatorPasteTransaction::TYPE_TITLE: case PhabricatorPasteTransaction::TYPE_LANGUAGE: case PhabricatorPasteTransaction::TYPE_STATUS: return $xaction->getNewValue(); + case PhabricatorPasteTransaction::TYPE_CONTENT: + // If this transaction does not really change the paste content, return + // the current file PHID so this transaction no-ops. + $new_content = $xaction->getNewValue(); + $old_content = $object->getRawContent(); + $file_phid = $object->getFilePHID(); + if (($new_content === $old_content) && $file_phid) { + return $file_phid; + } + + $file = self::initializeFileForPaste( + $this->getActor(), + $this->fileName, + $xaction->getNewValue()); + + return $file->getPHID(); } } diff --git a/src/applications/paste/mail/PasteCreateMailReceiver.php b/src/applications/paste/mail/PasteCreateMailReceiver.php --- a/src/applications/paste/mail/PasteCreateMailReceiver.php +++ b/src/applications/paste/mail/PasteCreateMailReceiver.php @@ -21,16 +21,11 @@ $title = pht('Email Paste'); } - $file = PhabricatorPasteEditor::initializeFileForPaste( - $sender, - $title, - $mail->getCleanTextBody()); - $xactions = array(); $xactions[] = id(new PhabricatorPasteTransaction()) ->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT) - ->setNewValue($file->getPHID()); + ->setNewValue($mail->getCleanTextBody()); $xactions[] = id(new PhabricatorPasteTransaction()) ->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE) diff --git a/src/applications/paste/storage/PhabricatorPaste.php b/src/applications/paste/storage/PhabricatorPaste.php --- a/src/applications/paste/storage/PhabricatorPaste.php +++ b/src/applications/paste/storage/PhabricatorPaste.php @@ -45,7 +45,8 @@ ->setAuthorPHID($actor->getPHID()) ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy) - ->setSpacePHID($actor->getDefaultSpacePHID()); + ->setSpacePHID($actor->getDefaultSpacePHID()) + ->attachRawContent(null); } public static function getStatusNameMap() { diff --git a/src/applications/transactions/editengine/PhabricatorApplicationEditEngine.php b/src/applications/transactions/editengine/PhabricatorApplicationEditEngine.php new file mode 100644 --- /dev/null +++ b/src/applications/transactions/editengine/PhabricatorApplicationEditEngine.php @@ -0,0 +1,303 @@ +viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + final public function setController(PhabricatorController $controller) { + $this->controller = $controller; + $this->setViewer($controller->getViewer()); + return $this; + } + + final public function getController() { + return $this->controller; + } + + final protected function buildEditFields($object) { + $viewer = $this->getViewer(); + $editor = $object->getApplicationTransactionEditor(); + + $types = $editor->getTransactionTypesForObject($object); + $types = array_fuse($types); + + $fields = $this->buildCustomEditFields($object); + + if ($object instanceof PhabricatorPolicyInterface) { + $policies = id(new PhabricatorPolicyQuery()) + ->setViewer($viewer) + ->setObject($object) + ->execute(); + + $map = array( + PhabricatorTransactions::TYPE_VIEW_POLICY => array( + 'key' => 'policy.view', + 'aliases' => array('view'), + 'capability' => PhabricatorPolicyCapability::CAN_VIEW, + ), + PhabricatorTransactions::TYPE_EDIT_POLICY => array( + 'key' => 'policy.edit', + 'aliases' => array('edit'), + 'capability' => PhabricatorPolicyCapability::CAN_EDIT, + ), + PhabricatorTransactions::TYPE_JOIN_POLICY => array( + 'key' => 'policy.join', + 'aliases' => array('join'), + 'capability' => PhabricatorPolicyCapability::CAN_JOIN, + ), + ); + + foreach ($map as $type => $spec) { + if (empty($types[$type])) { + continue; + } + + $capability = $spec['capability']; + $key = $spec['key']; + $aliases = $spec['aliases']; + + $policy_field = id(new PhabricatorPolicyEditField()) + ->setKey($key) + ->setAliases($aliases) + ->setCapability($capability) + ->setPolicies($policies) + ->setTransactionType($type) + ->setValue($object->getPolicy($capability)); + $fields[] = $policy_field; + + if ($object instanceof PhabricatorSpacesInterface) { + if ($capability == PhabricatorPolicyCapability::CAN_VIEW) { + $type_space = PhabricatorTransactions::TYPE_SPACE; + if (isset($types[$type_space])) { + $space_field = id(new PhabricatorSpaceEditField()) + ->setKey('spacePHID') + ->setAliases(array('space', 'policy.space')) + ->setTransactionType($type_space) + ->setValue($object->getSpacePHID()); + $fields[] = $space_field; + + $policy_field->setSpaceField($space_field); + } + } + } + } + } + + $edge_type = PhabricatorTransactions::TYPE_EDGE; + $object_phid = $object->getPHID(); + + $project_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; + + if ($object instanceof PhabricatorProjectInterface) { + if (isset($types[$edge_type])) { + if ($object_phid) { + $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $object_phid, + $project_edge_type); + $project_phids = array_reverse($project_phids); + } else { + $project_phids = array(); + } + + $edge_field = id(new PhabricatorDatasourceEditField()) + ->setKey('projectPHIDs') + ->setLabel(pht('Projects')) + ->setDatasource(new PhabricatorProjectDatasource()) + ->setAliases(array('project', 'projects')) + ->setTransactionType($edge_type) + ->setMetadataValue('edge:type', $project_edge_type) + ->setValue($project_phids); + $fields[] = $edge_field; + } + } + + $subscribers_type = PhabricatorTransactions::TYPE_SUBSCRIBERS; + + if ($object instanceof PhabricatorSubscribableInterface) { + if (isset($types[$subscribers_type])) { + if ($object_phid) { + $sub_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( + $object_phid); + } else { + // TODO: Allow applications to provide default subscribers; Maniphest + // does this at a minimum. + $sub_phids = array(); + } + + $subscribers_field = id(new PhabricatorDatasourceEditField()) + ->setKey('subscriberPHIDs') + ->setLabel(pht('Subscribers')) + ->setDatasource(new PhabricatorMetaMTAMailableDatasource()) + ->setAliases(array('subscriber', 'subscribers')) + ->setTransactionType($subscribers_type) + ->setValue($sub_phids); + $fields[] = $subscribers_field; + } + } + + return $fields; + } + + abstract protected function newEditableObject(); + abstract protected function newObjectQuery(); + abstract protected function buildCustomEditFields($object); + + abstract protected function getObjectCreateTitleText($object); + abstract protected function getObjectEditTitleText($object); + abstract protected function getObjectCreateShortText($object); + abstract protected function getObjectEditShortText($object); + abstract protected function getObjectViewURI($object); + + protected function getObjectCreateCancelURI($object) { + return $this->getController()->getApplicationURI(); + } + + protected function getObjectEditCancelURI($object) { + return $this->getObjectViewURI($object); + } + + protected function getObjectCreateButtonText($object) { + return $this->getObjectCreateTitleText($object); + } + + protected function getObjectEditButtonText($object) { + return pht('Save Changes'); + } + + final public function buildResponse() { + $controller = $this->getController(); + $viewer = $this->getViewer(); + $request = $controller->getRequest(); + + $id = $request->getURIData('id'); + if ($id) { + $object = $this->newObjectQuery() + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$object) { + return new Aphront404Response(); + } + + $is_create = false; + } else { + $object = $this->newEditableObject(); + + $is_create = true; + } + + $fields = $this->buildEditFields($object); + + foreach ($fields as $field) { + $field + ->setViewer($viewer) + ->setObject($object); + } + + $validation_exception = null; + if ($request->isFormPost()) { + foreach ($fields as $field) { + $field->readValueFromSubmit($request); + } + + $template = $object->getApplicationTransactionTemplate(); + + $xactions = array(); + foreach ($fields as $field) { + $xactions[] = $field->generateTransaction(clone $template); + } + + $editor = $object->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(false); + + try { + + $editor->applyTransactions($object, $xactions); + + return id(new AphrontRedirectResponse()) + ->setURI($this->getObjectViewURI($object)); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + } + } else { + if ($is_create) { + foreach ($fields as $field) { + $field->readValueFromRequest($request); + } + } else { + foreach ($fields as $field) { + $field->readValueFromObject($object); + } + } + } + + $box = id(new PHUIObjectBoxView()) + ->setUser($viewer); + + $crumbs = $controller->buildApplicationCrumbsForEditEngine(); + + if ($is_create) { + $header_text = $this->getObjectCreateTitleText($object); + + $crumbs->addTextCrumb( + $this->getObjectCreateShortText($object)); + + $cancel_uri = $this->getObjectCreateCancelURI($object); + $submit_button = $this->getObjectCreateButtonText($object); + } else { + $header_text = $this->getObjectEditTitleText($object); + + $crumbs->addTextCrumb( + $this->getObjectEditShortText($object), + $this->getObjectViewURI($object)); + + $cancel_uri = $this->getObjectEditCancelURI($object); + $submit_button = $this->getObjectEditButtonText($object); + } + + $box->setHeaderText($header_text); + + $form = id(new AphrontFormView()) + ->setUser($viewer); + + foreach ($fields as $field) { + $field->appendToForm($form); + } + + $form->appendControl( + id(new AphrontFormSubmitControl()) + ->addCancelButton($cancel_uri) + ->setValue($submit_button)); + + $box->appendChild($form); + + if ($validation_exception) { + $box->setValidationException($validation_exception); + } + + return $controller->newPage() + ->setTitle($header_text) + ->setCrumbs($crumbs) + ->appendChild($box); + } + + +} diff --git a/src/applications/transactions/editfield/PhabricatorDatasourceEditField.php b/src/applications/transactions/editfield/PhabricatorDatasourceEditField.php new file mode 100644 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorDatasourceEditField.php @@ -0,0 +1,21 @@ +datasource = $datasource; + return $this; + } + + public function getDatasource() { + return $this->datasource; + } + + protected function newDatasource() { + return id(clone $this->getDatasource()); + } + +} diff --git a/src/applications/transactions/editfield/PhabricatorEditField.php b/src/applications/transactions/editfield/PhabricatorEditField.php new file mode 100644 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorEditField.php @@ -0,0 +1,212 @@ +key = $key; + return $this; + } + + public function getKey() { + return $this->key; + } + + public function setLabel($label) { + $this->label = $label; + return $this; + } + + public function getLabel() { + return $this->label; + } + + public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function setAliases(array $aliases) { + $this->aliases = $aliases; + return $this; + } + + public function getAliases() { + return $this->aliases; + } + + public function setObject($object) { + $this->object = $object; + return $this; + } + + public function getObject() { + return $this->object; + } + + abstract protected function newControl(); + + protected function renderControl() { + $control = $this->newControl(); + if ($control === null) { + return null; + } + + $control + ->setValue($this->getValueForControl()) + ->setName($this->getKey()); + + if (!$control->getLabel()) { + $control->setLabel($this->getLabel()); + } + + return $control; + } + + public function appendToForm(AphrontFormView $form) { + $control = $this->renderControl(); + if ($control !== null) { + $form->appendControl($control); + } + return $this; + } + + protected function getValueForControl() { + return $this->getValue(); + } + + protected function getValue() { + return $this->value; + } + + public function setValue($value) { + $this->hasValue = true; + $this->value = $value; + return $this; + } + + public function generateTransaction( + PhabricatorApplicationTransaction $xaction) { + + $xaction + ->setTransactionType($this->getTransactionType()) + ->setNewValue($this->getValueForTransaction()); + + foreach ($this->metadata as $key => $value) { + $xaction->setMetadataValue($key, $value); + } + + return $xaction; + } + + public function setMetadataValue($key, $value) { + $this->metadata[$key] = $value; + return $this; + } + + protected function getValueForTransaction() { + return $this->getValue(); + } + + public function getTransactionType() { + if (!$this->transactionType) { + throw new PhutilInvalidStateException('setTransactionType'); + } + return $this->transactionType; + } + + public function setTransactionType($type) { + $this->transactionType = $type; + return $this; + } + + public function readValueFromRequest(AphrontRequest $request) { + $check = array_merge(array($this->getKey()), $this->getAliases()); + foreach ($check as $key) { + if (!$this->getValueExistsInRequest($request, $key)) { + continue; + } + + $this->value = $this->getValueFromRequest($request, $key); + return; + } + + $this->readValueFromObject($this->getObject()); + + return $this; + } + + public function readValueFromObject($object) { + $this->value = $this->getValueFromObject($object); + return $this; + } + + protected function getValueFromObject($object) { + if ($this->hasValue) { + return $this->value; + } else { + return $this->getDefaultValue(); + } + } + + protected function getValueExistsInRequest(AphrontRequest $request, $key) { + return $this->getValueExistsInSubmit($request, $key); + } + + protected function getValueFromRequest(AphrontRequest $request, $key) { + return $this->getValueFromSubmit($request, $key); + } + + public function readValueFromSubmit(AphrontRequest $request) { + $key = $this->getKey(); + if ($this->getValueExistsInSubmit($request, $key)) { + $value = $this->getValueFromSubmit($request, $key); + } else { + $value = $this->getDefaultValue(); + } + $this->value = $value; + return $this; + } + + protected function getValueExistsInSubmit(AphrontRequest $request, $key) { + return $request->getExists($key); + } + + protected function getValueFromSubmit(AphrontRequest $request, $key) { + return $request->getStr($key); + } + + protected function getDefaultValue() { + return null; + } + + protected function getListFromRequest( + AphrontRequest $request, + $key) { + + $list = $request->getArr($key, null); + if ($list === null) { + $list = $request->getStrList($key); + } + + if (!$list) { + return array(); + } + + return $list; + } + +} diff --git a/src/applications/transactions/editfield/PhabricatorPolicyEditField.php b/src/applications/transactions/editfield/PhabricatorPolicyEditField.php new file mode 100644 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorPolicyEditField.php @@ -0,0 +1,54 @@ +policies = $policies; + return $this; + } + + public function getPolicies() { + if ($this->policies === null) { + throw new PhutilInvalidStateException('setPolicies'); + } + return $this->policies; + } + + public function setCapability($capability) { + $this->capability = $capability; + return $this; + } + + public function getCapability() { + return $this->capability; + } + + public function setSpaceField(PhabricatorSpaceEditField $space_field) { + $this->spaceField = $space_field; + return $this; + } + + public function getSpaceField() { + return $this->spaceField; + } + + protected function newControl() { + $control = id(new AphrontFormPolicyControl()) + ->setCapability($this->getCapability()) + ->setPolicyObject($this->getObject()) + ->setPolicies($this->getPolicies()); + + $space_field = $this->getSpaceField(); + if ($space_field) { + $control->setSpacePHID($space_field->getValueForControl()); + } + + return $control; + } + +} diff --git a/src/applications/transactions/editfield/PhabricatorSelectEditField.php b/src/applications/transactions/editfield/PhabricatorSelectEditField.php new file mode 100644 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorSelectEditField.php @@ -0,0 +1,25 @@ +options = $options; + return $this; + } + + public function getOptions() { + if ($this->options === null) { + throw new PhutilInvalidStateException('setOptions'); + } + return $this->options; + } + + protected function newControl() { + return id(new AphrontFormSelectControl()) + ->setOptions($this->getOptions()); + } + +} diff --git a/src/applications/transactions/editfield/PhabricatorSpaceEditField.php b/src/applications/transactions/editfield/PhabricatorSpaceEditField.php new file mode 100644 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorSpaceEditField.php @@ -0,0 +1,12 @@ +monospaced = $monospaced; + return $this; + } + + public function getMonospaced() { + return $this->monospaced; + } + + public function setHeight($height) { + $this->height = $height; + return $this; + } + + public function getHeight() { + return $this->height; + } + + protected function newControl() { + $control = new AphrontFormTextAreaControl(); + + if ($this->getMonospaced()) { + $control->setCustomClass('PhabricatorMonospaced'); + } + + $height = $this->getHeight(); + if ($height) { + $control->setHeight($height); + } + + return $control; + } + +} diff --git a/src/applications/transactions/editfield/PhabricatorTextEditField.php b/src/applications/transactions/editfield/PhabricatorTextEditField.php new file mode 100644 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorTextEditField.php @@ -0,0 +1,10 @@ +setDatasource($this->newDatasource()); + + if ($this->originalValue !== null) { + $control->setOriginalValue($this->originalValue); + } + + return $control; + } + + public function setOriginalValue(array $value) { + $this->originalValue = $value; + return $this; + } + + public function setValue($value) { + $this->originalValue = $value; + return parent::setValue($value); + } + + protected function getValueFromSubmit(AphrontRequest $request, $key) { + // TODO: Maybe move this unusual read somewhere else so subclassing this + // correctly is easier? + $this->originalValue = $request->getArr($key.'.original'); + + return $this->getListFromRequest($request, $key); + } + + protected function getDefaultValue() { + return array(); + } + + protected function getValueForTransaction() { + $new = parent::getValueForTransaction(); + + $edge_types = array( + PhabricatorTransactions::TYPE_EDGE => true, + PhabricatorTransactions::TYPE_SUBSCRIBERS => true, + ); + + if (isset($edge_types[$this->getTransactionType()])) { + if ($this->originalValue !== null) { + // If we're building an edge transaction and the request has data + // about the original value the user saw when they loaded the form, + // interpret the edit as a mixture of "+" and "-" operations instead + // of a single "=" operation. This limits our exposure to race + // conditions by making most concurrent edits merge correctly. + + $new = parent::getValueForTransaction(); + $old = $this->originalValue; + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + $value = array(); + + if ($add) { + $value['+'] = array_fuse($add); + } + if ($rem) { + $value['-'] = array_fuse($rem); + } + + return $value; + } else { + + if (!is_array($new)) { + throw new Exception(print_r($new, true)); + } + + return array( + '=' => array_fuse($new), + ); + } + } + + return $new; + } + +} diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -242,6 +242,19 @@ return $this->applicationEmail; } + public function getTransactionTypesForObject($object) { + $old = $this->object; + try { + $this->object = $object; + $result = $this->getTransactionTypes(); + $this->object = $old; + } catch (Exception $ex) { + $this->object = $old; + throw $ex; + } + return $result; + } + public function getTransactionTypes() { $types = array(); @@ -1650,7 +1663,7 @@ throw new Exception( pht( "Invalid '%s' value for PHID transaction. Value should contain only ". - "keys '%s' (add PHIDs), '%' (remove PHIDs) and '%s' (set PHIDS).", + "keys '%s' (add PHIDs), '%s' (remove PHIDs) and '%s' (set PHIDS).", 'new', '+', '-', diff --git a/src/view/control/AphrontTokenizerTemplateView.php b/src/view/control/AphrontTokenizerTemplateView.php --- a/src/view/control/AphrontTokenizerTemplateView.php +++ b/src/view/control/AphrontTokenizerTemplateView.php @@ -6,6 +6,7 @@ private $name; private $id; private $browseURI; + private $originalValue; public function setBrowseURI($browse_uri) { $this->browseURI = $browse_uri; @@ -36,6 +37,15 @@ return $this->name; } + public function setOriginalValue(array $original_value) { + $this->originalValue = $original_value; + return $this; + } + + public function getOriginalValue() { + return $this->originalValue; + } + public function render() { require_celerity_resource('aphront-tokenizer-control-css'); @@ -85,6 +95,20 @@ $classes[] = 'has-browse'; } + $original = array(); + $original_value = $this->getOriginalValue(); + if ($original_value) { + foreach ($this->getOriginalValue() as $value) { + $original[] = phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => $name.'.original[]', + 'value' => $value, + )); + } + } + $frame = javelin_tag( 'div', array( @@ -94,6 +118,7 @@ array( $container, $browse, + $original, )); return $frame; diff --git a/src/view/form/control/AphrontFormTokenizerControl.php b/src/view/form/control/AphrontFormTokenizerControl.php --- a/src/view/form/control/AphrontFormTokenizerControl.php +++ b/src/view/form/control/AphrontFormTokenizerControl.php @@ -7,6 +7,7 @@ private $limit; private $placeholder; private $handles; + private $originalValue; public function setDatasource(PhabricatorTypeaheadDatasource $datasource) { $this->datasource = $datasource; @@ -32,6 +33,15 @@ return $this; } + public function setOriginalValue(array $original_value) { + $this->originalValue = $original_value; + return $this; + } + + public function getOriginalValue() { + return $this->originalValue; + } + public function willRender() { // Load the handles now so we'll get a bulk load later on when we actually // render them. @@ -69,10 +79,15 @@ $token->setInputName($this->getName()); } - $template = new AphrontTokenizerTemplateView(); - $template->setName($name); - $template->setID($id); - $template->setValue($tokens); + $template = id(new AphrontTokenizerTemplateView()) + ->setName($name) + ->setID($id) + ->setValue($tokens); + + $original_value = $this->getOriginalValue(); + if ($original_value !== null) { + $template->setOriginalValue($original_value); + } $username = null; if ($this->user) {