@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Allow Phriction document edits to be saved as drafts

Summary:
Depends on D19661. Ref T13077. See PHI840.

When a user edits a page normally, add a "Save as Draft" button. Much of this change is around making that button render and behave properly: it needs to be an `<input type="submit" ...>` so browsers submit it and we can figure out which button the user clicked.

Then there are a few minor rules:

- If you're editing a page which is already a draft, we only give you "Save as Draft". This makes edits to update/revise a draft more natural.
- Highlight "Publish" if it's a likely action that you might want to take.

Internally, there are two types of edits. Both types create a new version with the new content. However:

- A "content" edit sets the version shown on the live page to the newly-created version.
- A "draft" edit does not update the version shown on the live page.

Test Plan: Edited a published document, edited the draft. Published documents. Reverted documents.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13077

Differential Revision: https://secure.phabricator.com/D19662

+218 -123
+5 -5
resources/celerity/map.php
··· 9 9 'names' => array( 10 10 'conpherence.pkg.css' => 'e68cf1fa', 11 11 'conpherence.pkg.js' => '15191c65', 12 - 'core.pkg.css' => 'badf3f16', 12 + 'core.pkg.css' => '2574c199', 13 13 'core.pkg.js' => 'b5a949ca', 14 14 'differential.pkg.css' => '06dc617c', 15 15 'differential.pkg.js' => 'c1cfa143', ··· 122 122 'rsrc/css/layout/phabricator-source-code-view.css' => '2ab25dfa', 123 123 'rsrc/css/phui/button/phui-button-bar.css' => 'f1ff5494', 124 124 'rsrc/css/phui/button/phui-button-simple.css' => '8e1baf68', 125 - 'rsrc/css/phui/button/phui-button.css' => '1863cc6e', 125 + 'rsrc/css/phui/button/phui-button.css' => '6ccb303c', 126 126 'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893', 127 127 'rsrc/css/phui/calendar/phui-calendar-list.css' => '576be600', 128 128 'rsrc/css/phui/calendar/phui-calendar-month.css' => '21154caf', ··· 151 151 'rsrc/css/phui/phui-document.css' => 'c4ac41f9', 152 152 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', 153 153 'rsrc/css/phui/phui-fontkit.css' => '1320ed01', 154 - 'rsrc/css/phui/phui-form-view.css' => 'f808e5be', 154 + 'rsrc/css/phui/phui-form-view.css' => '2f43fae7', 155 155 'rsrc/css/phui/phui-form.css' => '7aaa04e3', 156 156 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', 157 157 'rsrc/css/phui/phui-header-view.css' => '1ba8b707', ··· 800 800 'phui-box-css' => '4bd6cdb9', 801 801 'phui-bulk-editor-css' => '9a81e5d5', 802 802 'phui-button-bar-css' => 'f1ff5494', 803 - 'phui-button-css' => '1863cc6e', 803 + 'phui-button-css' => '6ccb303c', 804 804 'phui-button-simple-css' => '8e1baf68', 805 805 'phui-calendar-css' => 'f1ddf11c', 806 806 'phui-calendar-day-css' => '572b1893', ··· 819 819 'phui-font-icon-base-css' => '870a7360', 820 820 'phui-fontkit-css' => '1320ed01', 821 821 'phui-form-css' => '7aaa04e3', 822 - 'phui-form-view-css' => 'f808e5be', 822 + 'phui-form-view-css' => '2f43fae7', 823 823 'phui-head-thing-view-css' => 'fd311e5f', 824 824 'phui-header-view-css' => '1ba8b707', 825 825 'phui-hovercard' => '1bd28176',
+5 -1
src/__phutil_library_map__.php
··· 5021 5021 'PhrictionDocumentController' => 'applications/phriction/controller/PhrictionDocumentController.php', 5022 5022 'PhrictionDocumentDatasource' => 'applications/phriction/typeahead/PhrictionDocumentDatasource.php', 5023 5023 'PhrictionDocumentDeleteTransaction' => 'applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php', 5024 + 'PhrictionDocumentDraftTransaction' => 'applications/phriction/xaction/PhrictionDocumentDraftTransaction.php', 5024 5025 'PhrictionDocumentEditEngine' => 'applications/phriction/editor/PhrictionDocumentEditEngine.php', 5026 + 'PhrictionDocumentEditTransaction' => 'applications/phriction/xaction/PhrictionDocumentEditTransaction.php', 5025 5027 'PhrictionDocumentFerretEngine' => 'applications/phriction/search/PhrictionDocumentFerretEngine.php', 5026 5028 'PhrictionDocumentFulltextEngine' => 'applications/phriction/search/PhrictionDocumentFulltextEngine.php', 5027 5029 'PhrictionDocumentHeraldAdapter' => 'applications/phriction/herald/PhrictionDocumentHeraldAdapter.php', ··· 11138 11140 ), 11139 11141 'PhrictionDocumentAuthorHeraldField' => 'PhrictionDocumentHeraldField', 11140 11142 'PhrictionDocumentContentHeraldField' => 'PhrictionDocumentHeraldField', 11141 - 'PhrictionDocumentContentTransaction' => 'PhrictionDocumentVersionTransaction', 11143 + 'PhrictionDocumentContentTransaction' => 'PhrictionDocumentEditTransaction', 11142 11144 'PhrictionDocumentController' => 'PhrictionController', 11143 11145 'PhrictionDocumentDatasource' => 'PhabricatorTypeaheadDatasource', 11144 11146 'PhrictionDocumentDeleteTransaction' => 'PhrictionDocumentVersionTransaction', 11147 + 'PhrictionDocumentDraftTransaction' => 'PhrictionDocumentEditTransaction', 11145 11148 'PhrictionDocumentEditEngine' => 'PhabricatorEditEngine', 11149 + 'PhrictionDocumentEditTransaction' => 'PhrictionDocumentVersionTransaction', 11146 11150 'PhrictionDocumentFerretEngine' => 'PhabricatorFerretEngine', 11147 11151 'PhrictionDocumentFulltextEngine' => 'PhabricatorFulltextEngine', 11148 11152 'PhrictionDocumentHeraldAdapter' => 'HeraldAdapter',
+20 -11
src/applications/phriction/controller/PhrictionDocumentController.php
··· 450 450 $publish_name = pht('Publish Revert'); 451 451 } 452 452 453 + // If you're looking at the current version; and it's an unpublished 454 + // draft; and you can publish it, add a UI hint that this might be an 455 + // interesting action to take. 456 + $hint_publish = false; 457 + if ($is_draft) { 458 + if ($can_publish) { 459 + if ($document->getMaxVersion() == $content->getVersion()) { 460 + $hint_publish = true; 461 + } 462 + } 463 + } 464 + 453 465 $publish_uri = "/phriction/publish/{$id}/{$content_id}/"; 454 466 455 - if (PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) { 456 - $publish_name = pht('Publish (Prototype!)'); 457 - 458 - $curtain->addAction( 459 - id(new PhabricatorActionView()) 460 - ->setName($publish_name) 461 - ->setIcon('fa-upload') 462 - ->setDisabled(!$can_publish) 463 - ->setWorkflow(true) 464 - ->setHref($publish_uri)); 465 - } 467 + $curtain->addAction( 468 + id(new PhabricatorActionView()) 469 + ->setName($publish_name) 470 + ->setIcon('fa-upload') 471 + ->setSelected($hint_publish) 472 + ->setDisabled(!$can_publish) 473 + ->setWorkflow(true) 474 + ->setHref($publish_uri)); 466 475 467 476 if ($document->getStatus() == PhrictionDocumentStatus::STATUS_EXISTS) { 468 477 $curtain->addAction(
+35 -6
src/applications/phriction/controller/PhrictionEditController.php
··· 95 95 $v_space = $document->getSpacePHID(); 96 96 97 97 $content_text = $content->getContent(); 98 + $is_draft_mode = ($document->getContent()->getVersion() != $max_version); 98 99 99 100 if ($request->isFormPost()) { 101 + $save_as_draft = ($is_draft_mode || $request->getExists('draft')); 100 102 101 103 $title = $request->getStr('title'); 102 104 $content_text = $request->getStr('content'); ··· 108 110 $v_projects = $request->getArr('projects'); 109 111 $v_space = $request->getStr('spacePHID'); 110 112 113 + if ($save_as_draft) { 114 + $edit_type = PhrictionDocumentDraftTransaction::TRANSACTIONTYPE; 115 + } else { 116 + $edit_type = PhrictionDocumentContentTransaction::TRANSACTIONTYPE; 117 + } 118 + 111 119 $xactions = array(); 112 120 $xactions[] = id(new PhrictionTransaction()) 113 121 ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) 114 122 ->setNewValue($title); 115 123 $xactions[] = id(new PhrictionTransaction()) 116 - ->setTransactionType( 117 - PhrictionDocumentContentTransaction::TRANSACTIONTYPE) 124 + ->setTransactionType($edit_type) 118 125 ->setNewValue($content_text); 119 126 $xactions[] = id(new PhrictionTransaction()) 120 127 ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) ··· 147 154 $editor->applyTransactions($document, $xactions); 148 155 149 156 $uri = PhrictionDocument::getSlugURI($document->getSlug()); 157 + $uri = new PhutilURI($uri); 158 + 159 + // If the user clicked "Save as Draft", take them to the draft, not 160 + // to the current published page. 161 + if ($save_as_draft) { 162 + $uri = $uri->alter('v', $document->getMaxVersion()); 163 + } 164 + 150 165 return id(new AphrontRedirectResponse())->setURI($uri); 151 166 } catch (PhabricatorApplicationTransactionValidationException $ex) { 152 167 $validation_exception = $ex; ··· 176 191 if ($overwrite) { 177 192 $submit_button = pht('Overwrite Changes'); 178 193 } else { 179 - $submit_button = pht('Save Changes'); 194 + $submit_button = pht('Save and Publish'); 180 195 } 181 196 } else { 182 197 $submit_button = pht('Create Document'); ··· 196 211 $view_capability = PhabricatorPolicyCapability::CAN_VIEW; 197 212 $edit_capability = PhabricatorPolicyCapability::CAN_EDIT; 198 213 199 - 200 214 $form = id(new AphrontFormView()) 201 215 ->setUser($viewer) 202 216 ->addHiddenInput('slug', $document->getSlug()) ··· 253 267 ->setLabel(pht('Edit Notes')) 254 268 ->setValue($notes) 255 269 ->setError(null) 256 - ->setName('description')) 257 - ->appendChild( 270 + ->setName('description')); 271 + 272 + if ($is_draft_mode) { 273 + $form->appendControl( 274 + id(new AphrontFormSubmitControl()) 275 + ->addCancelButton($cancel_uri) 276 + ->setValue(pht('Save Draft'))); 277 + } else { 278 + $draft_button = id(new PHUIButtonView()) 279 + ->setTag('input') 280 + ->setName('draft') 281 + ->setText(pht('Save as Draft')) 282 + ->setColor(PHUIButtonView::GREEN); 283 + 284 + $form->appendControl( 258 285 id(new AphrontFormSubmitControl()) 286 + ->addButton($draft_button) 259 287 ->addCancelButton($cancel_uri) 260 288 ->setValue($submit_button)); 289 + } 261 290 262 291 $form_box = id(new PHUIObjectBoxView()) 263 292 ->setHeaderText($page_title)
+15
src/applications/phriction/editor/PhrictionTransactionEditor.php
··· 74 74 return $this; 75 75 } 76 76 77 + public function setShouldPublishContent( 78 + PhrictionDocument $object, 79 + $publish) { 80 + 81 + if ($publish) { 82 + $content_phid = $this->getNewContent()->getPHID(); 83 + } else { 84 + $content_phid = $this->getOldContent()->getPHID(); 85 + } 86 + 87 + $object->setContentPHID($content_phid); 88 + 89 + return $this; 90 + } 91 + 77 92 public function getEditorApplicationClass() { 78 93 return 'PhabricatorPhrictionApplication'; 79 94 }
+4 -82
src/applications/phriction/xaction/PhrictionDocumentContentTransaction.php
··· 1 1 <?php 2 2 3 3 final class PhrictionDocumentContentTransaction 4 - extends PhrictionDocumentVersionTransaction { 4 + extends PhrictionDocumentEditTransaction { 5 5 6 6 const TRANSACTIONTYPE = 'content'; 7 7 8 - public function generateOldValue($object) { 9 - if ($this->getEditor()->getIsNewObject()) { 10 - return null; 11 - } 12 - return $object->getContent()->getContent(); 13 - } 14 - 15 - public function generateNewValue($object, $value) { 16 - return $value; 17 - } 18 - 19 8 public function applyInternalEffects($object, $value) { 20 - $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); 21 - 22 - $content = $this->getNewDocumentContent($object); 23 - $content->setContent($value); 24 - } 25 - 26 - public function shouldHide() { 27 - if ($this->getOldValue() === null) { 28 - return true; 29 - } else { 30 - return false; 31 - } 32 - } 33 - 34 - public function getActionStrength() { 35 - return 1.3; 36 - } 37 - 38 - public function getActionName() { 39 - return pht('Edited'); 40 - } 41 - 42 - public function getTitle() { 43 - return pht( 44 - '%s edited the content of this document.', 45 - $this->renderAuthor()); 46 - } 47 - 48 - public function getTitleForFeed() { 49 - return pht( 50 - '%s edited the content of %s.', 51 - $this->renderAuthor(), 52 - $this->renderObject()); 53 - } 54 - 55 - public function hasChangeDetailView() { 56 - return true; 57 - } 58 - 59 - public function getMailDiffSectionHeader() { 60 - return pht('CHANGES TO DOCUMENT CONTENT'); 61 - } 9 + parent::applyInternalEffects($object, $value); 62 10 63 - public function newChangeDetailView() { 64 - $viewer = $this->getViewer(); 11 + $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); 65 12 66 - return id(new PhabricatorApplicationTransactionTextDiffDetailView()) 67 - ->setViewer($viewer) 68 - ->setOldText($this->getOldValue()) 69 - ->setNewText($this->getNewValue()); 70 - } 71 - 72 - public function newRemarkupChanges() { 73 - $changes = array(); 74 - 75 - $changes[] = $this->newRemarkupChange() 76 - ->setOldValue($this->getOldValue()) 77 - ->setNewValue($this->getNewValue()); 78 - 79 - return $changes; 80 - } 81 - 82 - public function validateTransactions($object, array $xactions) { 83 - $errors = array(); 84 - 85 - $content = $object->getContent()->getContent(); 86 - if ($this->isEmptyTextTransaction($content, $xactions)) { 87 - $errors[] = $this->newRequiredError( 88 - pht('Documents must have content.')); 89 - } 90 - 91 - return $errors; 13 + $this->getEditor()->setShouldPublishContent($object, true); 92 14 } 93 15 94 16 }
+14
src/applications/phriction/xaction/PhrictionDocumentDraftTransaction.php
··· 1 + <?php 2 + 3 + final class PhrictionDocumentDraftTransaction 4 + extends PhrictionDocumentEditTransaction { 5 + 6 + const TRANSACTIONTYPE = 'draft'; 7 + 8 + public function applyInternalEffects($object, $value) { 9 + parent::applyInternalEffects($object, $value); 10 + 11 + $this->getEditor()->setShouldPublishContent($object, false); 12 + } 13 + 14 + }
+95
src/applications/phriction/xaction/PhrictionDocumentEditTransaction.php
··· 1 + <?php 2 + 3 + abstract class PhrictionDocumentEditTransaction 4 + extends PhrictionDocumentVersionTransaction { 5 + 6 + public function generateOldValue($object) { 7 + if ($this->getEditor()->getIsNewObject()) { 8 + return null; 9 + } 10 + 11 + // NOTE: We want to get the newest version of the content here, regardless 12 + // of whether it's published or not. 13 + 14 + $actor = $this->getActor(); 15 + 16 + return id(new PhrictionContentQuery()) 17 + ->setViewer($actor) 18 + ->withDocumentPHIDs(array($object->getPHID())) 19 + ->setOrder('newest') 20 + ->setLimit(1) 21 + ->executeOne() 22 + ->getContent(); 23 + } 24 + 25 + public function generateNewValue($object, $value) { 26 + return $value; 27 + } 28 + 29 + public function applyInternalEffects($object, $value) { 30 + $content = $this->getNewDocumentContent($object); 31 + $content->setContent($value); 32 + } 33 + 34 + public function getActionStrength() { 35 + return 1.3; 36 + } 37 + 38 + public function getActionName() { 39 + return pht('Edited'); 40 + } 41 + 42 + public function getTitle() { 43 + return pht( 44 + '%s edited the content of this document.', 45 + $this->renderAuthor()); 46 + } 47 + 48 + public function getTitleForFeed() { 49 + return pht( 50 + '%s edited the content of %s.', 51 + $this->renderAuthor(), 52 + $this->renderObject()); 53 + } 54 + 55 + public function getMailDiffSectionHeader() { 56 + return pht('CHANGES TO DOCUMENT CONTENT'); 57 + } 58 + 59 + public function hasChangeDetailView() { 60 + return true; 61 + } 62 + 63 + public function newChangeDetailView() { 64 + $viewer = $this->getViewer(); 65 + 66 + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) 67 + ->setViewer($viewer) 68 + ->setOldText($this->getOldValue()) 69 + ->setNewText($this->getNewValue()); 70 + } 71 + 72 + public function newRemarkupChanges() { 73 + $changes = array(); 74 + 75 + $changes[] = $this->newRemarkupChange() 76 + ->setOldValue($this->getOldValue()) 77 + ->setNewValue($this->getNewValue()); 78 + 79 + return $changes; 80 + } 81 + 82 + public function validateTransactions($object, array $xactions) { 83 + $errors = array(); 84 + 85 + $content = $object->getContent()->getContent(); 86 + if ($this->isEmptyTextTransaction($content, $xactions)) { 87 + $errors[] = $this->newRequiredError( 88 + pht('Documents must have content.')); 89 + } 90 + 91 + return $errors; 92 + } 93 + 94 + 95 + }
+15 -4
src/view/phui/PHUIButtonView.php
··· 233 233 $classes = array(); 234 234 } 235 235 236 - // See PHI823. If we aren't rendering a "<button>" tag, give the tag we 237 - // are rendering a "button" role as a hint to screen readers. 236 + // See PHI823. If we aren't rendering a "<button>" or "<input>" tag, 237 + // give the tag we are rendering a "button" role as a hint to screen 238 + // readers. 238 239 $role = null; 239 - if ($this->tag !== 'button') { 240 + if ($this->tag !== 'button' && $this->tag !== 'input') { 240 241 $role = 'button'; 241 242 } 242 243 243 - return array( 244 + $attrs = array( 244 245 'class' => $classes, 245 246 'href' => $this->href, 246 247 'name' => $this->name, ··· 249 250 'meta' => $meta, 250 251 'role' => $role, 251 252 ); 253 + 254 + if ($this->tag == 'input') { 255 + $attrs['type'] = 'submit'; 256 + $attrs['value'] = $this->text; 257 + } 258 + 259 + return $attrs; 252 260 } 253 261 254 262 protected function getTagContent() { 263 + if ($this->tag === 'input') { 264 + return null; 265 + } 255 266 256 267 $icon = $this->icon; 257 268 $text = null;
+8 -5
webroot/rsrc/css/phui/button/phui-button.css
··· 4 4 5 5 6 6 button, 7 - a.button { 7 + a.button, 8 + input[type="submit"] { 8 9 font: {$basefont}; 9 10 -webkit-font-smoothing: antialiased; 10 11 -webkit-user-select: none; ··· 72 73 73 74 button.button-green, 74 75 a.button-green.button, 75 - a.button-green.button:visited { 76 + a.button-green.button:visited, 77 + input[type="submit"].button-green { 76 78 background-color: {$green.button.color}; 77 79 border-color: {$green.button.color}; 78 80 background-image: {$green.button.gradient}; ··· 80 82 81 83 button.button-red, 82 84 a.button-red.button, 83 - a.button-red.button:visited { 85 + a.button-red.button:visited, 86 + input[type="submit"].button-red { 84 87 background-color: {$red.button.color}; 85 88 border-color: {$red.button.color}; 86 89 background-image: {$red.button.gradient}; 87 90 } 88 91 89 92 button.button-grey, 90 - input[type="submit"].button-grey, 91 93 a.button-grey, 92 - a.button-grey:visited { 94 + a.button-grey:visited, 95 + input[type="submit"].button-grey { 93 96 background-color: {$grey.button.color}; 94 97 background-image: {$grey.button.gradient}; 95 98 border: 1px solid rgba({$alphablue}, 0.3);
+2 -9
webroot/rsrc/css/phui/phui-form-view.css
··· 133 133 } 134 134 135 135 .aphront-form-control-submit button, 136 - .aphront-form-control-submit a.button { 136 + .aphront-form-control-submit a.button, 137 + .aphront-form-control-submit input[type="submit"] { 137 138 float: right; 138 139 margin: 4px 0 0 8px; 139 - } 140 - 141 - .phui-form-control-multi-submit input, 142 - .phui-form-control-multi-submit button, 143 - .phui-form-control-multi-submit a { 144 - float: right; 145 - margin: 4px 0 0 8px; 146 - width: auto; 147 140 } 148 141 149 142 .aphront-form-control-textarea textarea.aphront-textarea-very-short {