diff --git a/src/applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php b/src/applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php index 9d87d616c0..3e2f1343f7 100644 --- a/src/applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php +++ b/src/applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php @@ -1,37 +1,43 @@ getReleephRequest() - ->loadPhabricatorRepositoryCommitData(); - if (!$commit_data) { - return ''; - } - - $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); - $engine->setConfig('viewer', $this->getUser()); $markup = phutil_tag( 'div', array( 'class' => 'phabricator-remarkup', ), - $engine->markupText($commit_data->getCommitMessage())); + $this->getMarkupEngineOutput()); return id(new AphrontNoteView()) ->setTitle('Commit Message') ->appendChild($markup) ->render(); } + public function shouldMarkup() { + return true; + } + + public function getMarkupText($field) { + $commit_data = $this + ->getReleephRequest() + ->loadPhabricatorRepositoryCommitData(); + if ($commit_data) { + return $commit_data->getCommitMessage(); + } else { + return ''; + } + } + } diff --git a/src/applications/releeph/field/specification/ReleephFieldSpecification.php b/src/applications/releeph/field/specification/ReleephFieldSpecification.php index 992808f12f..a93d258210 100644 --- a/src/applications/releeph/field/specification/ReleephFieldSpecification.php +++ b/src/applications/releeph/field/specification/ReleephFieldSpecification.php @@ -1,359 +1,418 @@ getStorageKey() !== null; } /** * This will be called many times if you are using **Selecting**. In * particular, for N selecting fields, selectReleephRequests() is called * N-squared times, each time for R ReleephRequests. */ final public function getValue() { $key = $this->getRequiredStorageKey(); return $this->getReleephRequest()->getDetail($key); } final public function setValue($value) { $key = $this->getRequiredStorageKey(); return $this->getReleephRequest()->setDetail($key, $value); } /** * @throws ReleephFieldParseException, to show an error. */ public function validate($value) { return; } /* -( Header View )-------------------------------------------------------- */ /** * Return a label for use in rendering the fields table. If you return null, * the renderLabelForHeaderView data will span both columns. */ public function renderLabelForHeaderView() { return $this->getName(); } public function renderValueForHeaderView() { $key = $this->getRequiredStorageKey(); return $this->getReleephRequest()->getDetail($key); } /* -( Edit View )---------------------------------------------------------- */ public function renderEditControl(AphrontRequest $request) { throw new ReleephFieldSpecificationIncompleteException($this); } public function setValueFromAphrontRequest(AphrontRequest $request) { $data = $request->getRequestData(); $value = idx($data, $this->getRequiredStorageKey()); $this->validate($value); $this->setValue($value); } /* -( Conduit )------------------------------------------------------------ */ public function getKeyForConduit() { return $this->getRequiredStorageKey(); } public function getValueForConduit() { return $this->getValue(); } public function setValueFromConduitAPIRequest(ConduitAPIRequest $request) { $value = idx( $request->getValue('fields', array()), $this->getRequiredStorageKey()); $this->validate($value); $this->setValue($value); } /* -( Arcanist )----------------------------------------------------------- */ public function renderHelpForArcanist() { return ''; } /* -( Context )------------------------------------------------------------ */ private $releephProject; private $releephBranch; private $releephRequest; private $user; final public function setReleephProject(ReleephProject $rp) { $this->releephProject = $rp; return $this; } final public function setReleephBranch(ReleephBranch $rb) { $this->releephRequest = $rb; return $this; } final public function setReleephRequest(ReleephRequest $rr) { $this->releephRequest = $rr; return $this; } final public function setUser(PhabricatorUser $user) { $this->user = $user; return $this; } final public function getReleephProject() { return $this->releephProject; } final public function getReleephBranch() { return $this->releephBranch; } final public function getReleephRequest() { return $this->releephRequest; } final public function getUser() { return $this->user; } /* -( Bulk loading )------------------------------------------------------- */ public function bulkLoad(array $releeph_requests) { } /* -( Selecting )---------------------------------------------------------- */ /** * Append select controls to the given form. * * You are given: * * - the AphrontFormView to append to; * * - the AphrontRequest, so you can make use of the value currently selected * in the form; * * - $all_releeph_requests: an array of all the ReleephRequests without any * selection based filtering; and * * - $all_releeph_requests_without_this_field: an array of ReleephRequests * that have been selected by all the other select controls on this page. * * The example in ReleephLevelFieldSpecification shows how to use these. * $all_releeph_requests lets you find out all the values of a field in all * ReleephRequests, so you can render controls for every known value. * * $all_releeph_requests_without_this_field lets you count how many * ReleephRequests could be affected by this field's select control, after * all the other fields have made their selections. * ReleephLevelFieldSpecification uses this to render a preview count for * each select button, and disables the button completely (but still renders * it) if it couldn't possibly select anything. */ protected function appendSelectControls( AphrontFormView $form, AphrontRequest $request, array $all_releeph_requests, array $all_releeph_requests_without_this_field) { return null; } /** * Filter the $releeph_requests using the data you set with your form * controls, and which is now available in the provided AphrontRequest. */ protected function selectReleephRequests(AphrontRequest $request, array &$releeph_requests) { return null; } /** * If you have PHIDs that can be used in an AphrontFormTokenizerControl, * return true here, return the PHIDs in getSelectablePHIDs(), and return the * URL the Tokenizer should use for the form control in * getSelectTokenizerDatasource(). * * This is a cheap alternative to implementing appendSelectControls() and * selectReleephRequests() in full. */ protected function hasSelectablePHIDs() { return false; } protected function getSelectablePHIDs() { throw new ReleephFieldSpecificationIncompleteException($this); } protected function getSelectTokenizerDatasource() { throw new ReleephFieldSpecificationIncompleteException($this); } /* -( Commit Messages )---------------------------------------------------- */ public function shouldAppearOnCommitMessage() { return false; } public function renderLabelForCommitMessage() { throw new ReleephFieldSpecificationIncompleteException($this); } public function renderValueForCommitMessage() { throw new ReleephFieldSpecificationIncompleteException($this); } public function shouldAppearOnRevertMessage() { return false; } public function renderLabelForRevertMessage() { return $this->renderLabelForCommitMessage(); } public function renderValueForRevertMessage() { return $this->renderValueForCommitMessage(); } +/* -( Markup Interface )--------------------------------------------------- */ + + const MARKUP_FIELD_GENERIC = 'releeph:generic-markup-field'; + + private $engine; + + /** + * ReleephFieldSpecification implements much of PhabricatorMarkupInterface + * for you. If you return true from `shouldMarkup()`, and implement + * `getMarkupText()` then your text will be rendered through the Phabricator + * markup pipeline. + * + * Output is retrievable with `getMarkupEngineOutput()`. + */ + public function shouldMarkup() { + return false; + } + + public function getMarkupText($field) { + throw new ReleephFieldSpecificationIncompleteException($this); + } + + final public function getMarkupEngineOutput() { + return $this->engine->getOutput($this, self::MARKUP_FIELD_GENERIC); + } + + final public function setMarkupEngine(PhabricatorMarkupEngine $engine) { + $this->engine = $engine; + $engine->addObject($this, self::MARKUP_FIELD_GENERIC); + return $this; + } + + final public function getMarkupFieldKey($field) { + return sprintf( + '%s:%s:%s:%s', + $this->getReleephRequest()->getPHID(), + $this->getStorageKey(), + $field, + PhabricatorHash::digest($this->getMarkupText($field))); + } + + final public function newMarkupEngine($field) { + return PhabricatorMarkupEngine::newDifferentialMarkupEngine(); + } + + final public function didMarkupText( + $field, + $output, + PhutilMarkupEngine $engine) { + + return $output; + } + + final public function shouldUseMarkupCache($field) { + return true; + } + + /* -( Implementation )----------------------------------------------------- */ protected function getRequiredStorageKey() { $key = $this->getStorageKey(); if ($key === null) { throw new ReleephFieldSpecificationIncompleteException($this); } if (strpos($key, '.') !== false) { /** * Storage keys are reused for form controls, and periods in form control * names break HTML forms. */ throw new Exception( "You can't use '.' in storage keys!"); } return $key; } /** * The "hook" functions ##appendSelectControlsHook()## and * ##selectReleephRequestsHook()## are used with ##hasSelectablePHIDs()##, to * use the tokenizing helpers if ##hasSelectablePHIDs()## returns true. */ public function appendSelectControlsHook( AphrontFormView $form, AphrontRequest $request, array $all_releeph_requests, array $all_releeph_requests_without_this_field) { if ($this->hasSelectablePHIDs()) { $this->appendTokenizingSelectControl( $form, $request, $all_releeph_requests, $all_releeph_requests_without_this_field); } else { $this->appendSelectControls( $form, $request, $all_releeph_requests, $all_releeph_requests_without_this_field); } } // See above public function selectReleephRequestsHook(AphrontRequest $request, array &$releeph_requests) { if ($this->hasSelectablePHIDs()) { $this->selectReleephRequestsFromTokens( $request, $releeph_requests); } else { $this->selectReleephRequests( $request, $releeph_requests); } } private function appendTokenizingSelectControl( AphrontFormView $form, AphrontRequest $request, array $all_releeph_requests, array $all_releeph_requests_without_this_field) { $key = urlencode(strtolower($this->getName())); $selected_phids = $request->getArr($key); $handles = id(new PhabricatorObjectHandleData($selected_phids)) ->setViewer($request->getUser()) ->loadHandles(); $tokens = array(); foreach ($selected_phids as $phid) { $tokens[$phid] = $handles[$phid]->getFullName(); } $datasource = $this->getSelectTokenizerDatasource(); $control = id(new AphrontFormTokenizerControl()) ->setDatasource($datasource) ->setName($key) ->setLabel($this->getName()) ->setValue($tokens); $form->appendChild($control); } private function selectReleephRequestsFromTokens(AphrontRequest $request, array &$releeph_requests) { $key = urlencode(strtolower($this->getName())); $selected_phids = $request->getArr($key); if (!$selected_phids) { return; } $selected_phid_lookup = array(); foreach ($selected_phids as $phid) { $selected_phid_lookup[$phid] = $phid; } $filtered = array(); foreach ($releeph_requests as $releeph_request) { $rq_phids = $this ->setReleephRequest($releeph_request) ->getSelectablePHIDs(); foreach ($rq_phids as $rq_phid) { if (idx($selected_phid_lookup, $rq_phid)) { $filtered[] = $releeph_request; break; } } } $releeph_requests = $filtered; } } diff --git a/src/applications/releeph/field/specification/ReleephReasonFieldSpecification.php b/src/applications/releeph/field/specification/ReleephReasonFieldSpecification.php index 7b805cd490..c243e3090d 100644 --- a/src/applications/releeph/field/specification/ReleephReasonFieldSpecification.php +++ b/src/applications/releeph/field/specification/ReleephReasonFieldSpecification.php @@ -1,78 +1,84 @@ getValue(); - if (!$reason) { - return ''; - } - - $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); - $engine->setConfig('viewer', $this->getUser()); $markup = phutil_tag( 'div', array( 'class' => 'phabricator-remarkup', ), - $engine->markupText($reason)); + $this->getMarkupEngineOutput()); return id(new AphrontNoteView()) ->setTitle('Reason') ->appendChild($markup) ->render(); } private $error = true; public function renderEditControl(AphrontRequest $request) { $reason = $request->getStr('reason', $this->getValue()); return id(new AphrontFormTextAreaControl()) ->setLabel('Reason') ->setName('reason') ->setError($this->error) ->setValue($reason); } public function validate($reason) { if (!$reason) { $this->error = 'Required'; throw new ReleephFieldParseException( $this, "You must give a reason for your request."); } } public function renderHelpForArcanist() { $text = "Fully explain why you are requesting this code be included ". "in the next release.\n"; return phutil_console_wrap($text, 8); } public function shouldAppearOnCommitMessage() { return true; } public function renderLabelForCommitMessage() { return 'Request Reason'; } public function renderValueForCommitMessage() { return $this->getValue(); } + public function shouldMarkup() { + return true; + } + + public function getMarkupText($field) { + $reason = $this->getValue(); + if ($reason) { + return $reason; + } else { + return ''; + } + } + } diff --git a/src/applications/releeph/view/request/header/ReleephRequestHeaderListView.php b/src/applications/releeph/view/request/header/ReleephRequestHeaderListView.php index 26631172ad..82a9da27e0 100644 --- a/src/applications/releeph/view/request/header/ReleephRequestHeaderListView.php +++ b/src/applications/releeph/view/request/header/ReleephRequestHeaderListView.php @@ -1,111 +1,128 @@ originType = $origin; return $this; } public function setReleephProject(ReleephProject $rp) { $this->releephProject = $rp; return $this; } public function setReleephBranch(ReleephBranch $rb) { $this->releephBranch = $rb; return $this; } public function setReleephRequests(array $requests) { assert_instances_of($requests, 'ReleephRequest'); $this->releephRequests = $requests; return $this; } public function setAphrontRequest(AphrontRequest $request) { $this->aphrontRequest = $request; return $this; } public function setReloadOnStateChange($bool) { $this->reload = $bool; return $this; } public function render() { $views = $this->renderInner(); require_celerity_resource('phabricator-notification-css'); Javelin::initBehavior('releeph-request-state-change', array( 'reload' => $this->reload, )); $error_view = null; if ($this->errors) { $error_view = id(new AphrontErrorView()) ->setTitle('Bulk load errors') ->setSeverity(AphrontErrorView::SEVERITY_WARNING) ->setErrors($this->errors) ->render(); } $list = phutil_tag( 'div', array( 'data-sigil' => 'releeph-request-header-list', ), $views); return array($error_view, $list); } /** * Required for generating markup for ReleephRequestActionController. * * That controller just needs the markup, and doesn't need to start the * javelin behavior. */ public function renderInner() { $selector = $this->releephProject->getReleephFieldSelector(); $fields = $selector->getFieldSpecifications(); foreach ($fields as $field) { $field ->setReleephProject($this->releephProject) ->setReleephBranch($this->releephBranch) ->setUser($this->user); try { $field->bulkLoad($this->releephRequests); } catch (Exception $ex) { $this->errors[] = $ex; } } - $field_groups = $selector->arrangeFieldsForHeaderView($fields); + $engine = id(new PhabricatorMarkupEngine()) + ->setViewer($this->getUser()); $views = array(); foreach ($this->releephRequests as $releeph_request) { + $our_fields = array(); + foreach ($fields as $key => $field) { + $our_fields[$key] = clone $field; + } + + foreach ($our_fields as $field) { + if ($field->shouldMarkup()) { + $field + ->setReleephRequest($releeph_request) + ->setMarkupEngine($engine); + } + } + + $our_field_groups = $selector->arrangeFieldsForHeaderView($our_fields); + $views[] = id(new ReleephRequestHeaderView()) ->setUser($this->user) ->setAphrontRequest($this->aphrontRequest) ->setOriginType($this->originType) ->setReleephProject($this->releephProject) ->setReleephBranch($this->releephBranch) ->setReleephRequest($releeph_request) - ->setReleephFieldGroups($field_groups) - ->render(); + ->setReleephFieldGroups($our_field_groups); } + $engine->process(); + return $views; } }