@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.

Make inline content "state-oriented", not "string-oriented"

Summary:
Ref T13513. Currently, all the inline code passes around strings to describe content. I plan to add background music, animation effects, etc., soon. To prepare for this change, make content a state object.

This does not change any user-visible behavior, it just prepares for content to become more complicated than a single string.

Test Plan: Created, edited, submitted, cancelled, etc., comments.

Maniphest Tasks: T13513

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

+176 -149
+28 -28
resources/celerity/map.php
··· 13 13 'core.pkg.js' => '845355f4', 14 14 'dark-console.pkg.js' => '187792c2', 15 15 'differential.pkg.css' => '42a2334f', 16 - 'differential.pkg.js' => '8f59bce2', 16 + 'differential.pkg.js' => 'ff8ca085', 17 17 'diffusion.pkg.css' => '42c75c37', 18 18 'diffusion.pkg.js' => 'a98c0bf7', 19 19 'maniphest.pkg.css' => '35995d6d', ··· 379 379 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'a2ab19be', 380 380 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '1e413dc9', 381 381 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '0116d3e8', 382 - 'rsrc/js/application/diff/DiffChangeset.js' => '0c083409', 383 - 'rsrc/js/application/diff/DiffChangesetList.js' => 'db615898', 384 - 'rsrc/js/application/diff/DiffInline.js' => 'b00168c1', 382 + 'rsrc/js/application/diff/DiffChangeset.js' => '6e5e03d2', 383 + 'rsrc/js/application/diff/DiffChangesetList.js' => 'b51ba93a', 384 + 'rsrc/js/application/diff/DiffInline.js' => '28e53a2c', 385 385 'rsrc/js/application/diff/DiffPathView.js' => '8207abf9', 386 386 'rsrc/js/application/diff/DiffTreeView.js' => '5d83623b', 387 387 'rsrc/js/application/differential/behavior-diff-radios.js' => '925fe8cd', ··· 774 774 'phabricator-darklog' => '3b869402', 775 775 'phabricator-darkmessage' => '26cd4b73', 776 776 'phabricator-dashboard-css' => '5a205b9d', 777 - 'phabricator-diff-changeset' => '0c083409', 778 - 'phabricator-diff-changeset-list' => 'db615898', 779 - 'phabricator-diff-inline' => 'b00168c1', 777 + 'phabricator-diff-changeset' => '6e5e03d2', 778 + 'phabricator-diff-changeset-list' => 'b51ba93a', 779 + 'phabricator-diff-inline' => '28e53a2c', 780 780 'phabricator-diff-path-view' => '8207abf9', 781 781 'phabricator-diff-tree-view' => '5d83623b', 782 782 'phabricator-drag-and-drop-file-upload' => '4370900d', ··· 1000 1000 'javelin-dom', 1001 1001 'phabricator-draggable-list', 1002 1002 ), 1003 - '0c083409' => array( 1004 - 'javelin-dom', 1005 - 'javelin-util', 1006 - 'javelin-stratcom', 1007 - 'javelin-install', 1008 - 'javelin-workflow', 1009 - 'javelin-router', 1010 - 'javelin-behavior-device', 1011 - 'javelin-vector', 1012 - 'phabricator-diff-inline', 1013 - 'phabricator-diff-path-view', 1014 - 'phuix-button-view', 1015 - ), 1016 1003 '0cf79f45' => array( 1017 1004 'javelin-behavior', 1018 1005 'javelin-stratcom', ··· 1150 1137 'javelin-install', 1151 1138 'javelin-util', 1152 1139 ), 1140 + '28e53a2c' => array( 1141 + 'javelin-dom', 1142 + ), 1153 1143 '29819b75' => array( 1154 1144 'phabricator-notification', 1155 1145 'javelin-stratcom', ··· 1561 1551 'javelin-install', 1562 1552 'javelin-util', 1563 1553 ), 1554 + '6e5e03d2' => array( 1555 + 'javelin-dom', 1556 + 'javelin-util', 1557 + 'javelin-stratcom', 1558 + 'javelin-install', 1559 + 'javelin-workflow', 1560 + 'javelin-router', 1561 + 'javelin-behavior-device', 1562 + 'javelin-vector', 1563 + 'phabricator-diff-inline', 1564 + 'phabricator-diff-path-view', 1565 + 'phuix-button-view', 1566 + ), 1564 1567 70245195 => array( 1565 1568 'javelin-behavior', 1566 1569 'javelin-stratcom', ··· 1935 1938 'javelin-behavior-device', 1936 1939 'javelin-vector', 1937 1940 ), 1938 - 'b00168c1' => array( 1939 - 'javelin-dom', 1940 - ), 1941 1941 'b105a3a6' => array( 1942 1942 'javelin-behavior', 1943 1943 'javelin-stratcom', ··· 1969 1969 ), 1970 1970 'b517bfa0' => array( 1971 1971 'phui-oi-list-view-css', 1972 + ), 1973 + 'b51ba93a' => array( 1974 + 'javelin-install', 1975 + 'phuix-button-view', 1976 + 'phabricator-diff-tree-view', 1972 1977 ), 1973 1978 'b557770a' => array( 1974 1979 'javelin-install', ··· 2118 2123 'javelin-behavior', 2119 2124 'javelin-uri', 2120 2125 'phabricator-notification', 2121 - ), 2122 - 'db615898' => array( 2123 - 'javelin-install', 2124 - 'phuix-button-view', 2125 - 'phabricator-diff-tree-view', 2126 2126 ), 2127 2127 'e150bd50' => array( 2128 2128 'javelin-behavior',
+55 -35
src/infrastructure/diff/PhabricatorInlineCommentController.php
··· 39 39 private $isOnRight; 40 40 private $lineNumber; 41 41 private $lineLength; 42 - private $commentText; 43 42 private $operation; 44 43 private $commentID; 45 44 private $renderer; ··· 99 98 $request = $this->getRequest(); 100 99 $viewer = $this->getViewer(); 101 100 101 + if (!$request->validateCSRF()) { 102 + return new Aphront404Response(); 103 + } 104 + 102 105 $this->readRequestParameters(); 103 106 104 107 $op = $this->getOperation(); 105 108 switch ($op) { 106 109 case 'hide': 107 110 case 'show': 108 - if (!$request->validateCSRF()) { 109 - return new Aphront404Response(); 110 - } 111 - 112 111 $ids = $request->getStrList('ids'); 113 112 if ($ids) { 114 113 if ($op == 'hide') { ··· 120 119 121 120 return id(new AphrontAjaxResponse())->setContent(array()); 122 121 case 'done': 123 - if (!$request->validateCSRF()) { 124 - return new Aphront404Response(); 125 - } 126 122 $inline = $this->loadCommentForDone($this->getCommentID()); 127 123 128 124 $is_draft_state = false; ··· 158 154 case 'delete': 159 155 case 'undelete': 160 156 case 'refdelete': 161 - if (!$request->validateCSRF()) { 162 - return new Aphront404Response(); 163 - } 164 - 165 157 // NOTE: For normal deletes, we just process the delete immediately 166 158 // and show an "Undo" action. For deletes by reference from the 167 159 // preview ("refdelete"), we prompt first (because the "Undo" may ··· 193 185 194 186 return $this->buildEmptyResponse(); 195 187 case 'edit': 188 + case 'save': 196 189 $inline = $this->loadCommentByIDForEdit($this->getCommentID()); 197 - $text = $this->getCommentText(); 190 + if ($op === 'save') { 191 + $this->updateCommentContentState($inline); 198 192 199 - if ($request->isFormPost()) { 200 - if (strlen($text)) { 201 - $inline 202 - ->setContent($text) 203 - ->setIsEditing(false); 193 + $inline->setIsEditing(false); 204 194 195 + if (!$inline->isVoidComment($viewer)) { 205 196 $this->saveComment($inline); 206 197 207 198 return $this->buildRenderedCommentResponse( ··· 216 207 } 217 208 } else { 218 209 // NOTE: At time of writing, the "editing" state of inlines is 219 - // preserved by simluating a click on "Edit" when the inline loads. 210 + // preserved by simulating a click on "Edit" when the inline loads. 220 211 221 212 // In this case, we don't want to "saveComment()", because it 222 213 // recalculates object drafts and purges versioned drafts. ··· 234 225 $is_dirty = true; 235 226 } 236 227 237 - if (strlen($text)) { 238 - $inline->setContent($text); 228 + if ($this->hasContentState()) { 229 + $this->updateCommentContentState($inline); 239 230 $is_dirty = true; 240 231 } else { 241 232 PhabricatorInlineComment::loadAndAttachVersionedDrafts( ··· 262 253 // If the user uses "Undo" to get into an edited state ("AB"), then 263 254 // clicks cancel to return to the previous state ("A"), we also want 264 255 // to set the stored state back to "A". 265 - $text = $this->getCommentText(); 266 - if (strlen($text)) { 267 - $inline->setContent($text); 268 - } 256 + $this->updateCommentContentState($inline); 269 257 270 - $content = $inline->getContent(); 271 - if (!strlen($content)) { 258 + if ($inline->isVoidComment($viewer)) { 272 259 $inline->setIsDeleted(1); 273 260 } 274 261 ··· 283 270 $viewer->getPHID(), 284 271 $inline->getID()); 285 272 286 - $text = $this->getCommentText(); 287 - 288 - $versioned_draft 289 - ->setProperty('inline.text', $text) 290 - ->save(); 273 + $map = $this->getContentState(); 274 + foreach ($map as $key => $value) { 275 + $versioned_draft->setProperty($key, $value); 276 + } 277 + $versioned_draft->save(); 291 278 292 279 // We have to synchronize the draft engine after saving a versioned 293 280 // draft, because taking an inline comment from "no text, no draft" ··· 318 305 ->setIsNewFile($is_new) 319 306 ->setLineNumber($number) 320 307 ->setLineLength($length) 321 - ->setContent((string)$this->getCommentText()) 322 308 ->setReplyToCommentPHID($this->getReplyToCommentPHID()) 323 309 ->setIsEditing(true) 324 310 ->setStartOffset($request->getInt('startOffset')) 325 - ->setEndOffset($request->getInt('endOffset')); 311 + ->setEndOffset($request->getInt('endOffset')) 312 + ->setContent(''); 326 313 327 314 $document_engine_key = $request->getStr('documentEngineKey'); 328 315 if ($document_engine_key !== null) { ··· 336 323 $fixed_state = PhabricatorInlineComment::STATE_DRAFT; 337 324 $inline->setFixedState($fixed_state); 338 325 } 326 + } 327 + 328 + if ($this->hasContentState()) { 329 + $this->updateCommentContentState($inline); 339 330 } 340 331 341 332 $this->saveComment($inline); ··· 365 356 $this->isOnRight = $request->getBool('on_right'); 366 357 $this->lineNumber = $request->getInt('number'); 367 358 $this->lineLength = $request->getInt('length'); 368 - $this->commentText = $request->getStr('text'); 369 359 $this->commentID = $request->getInt('id'); 370 360 $this->operation = $request->getStr('op'); 371 361 $this->renderer = $request->getStr('renderer'); ··· 527 517 } 528 518 529 519 return $inline; 520 + } 521 + 522 + private function hasContentState() { 523 + $request = $this->getRequest(); 524 + return (bool)$request->getBool('hasContentState'); 525 + } 526 + 527 + private function getContentState() { 528 + $request = $this->getRequest(); 529 + 530 + $comment_text = $request->getStr('text'); 531 + 532 + return array( 533 + 'inline.text' => (string)$comment_text, 534 + ); 535 + } 536 + 537 + private function updateCommentContentState(PhabricatorInlineComment $inline) { 538 + if (!$this->hasContentState()) { 539 + throw new Exception( 540 + pht( 541 + 'Attempting to update comment content state, but request has no '. 542 + 'content state.')); 543 + } 544 + 545 + $state = $this->getContentState(); 546 + 547 + $text = $state['inline.text']; 548 + 549 + $inline->setContent($text); 530 550 } 531 551 532 552 private function saveComment(PhabricatorInlineComment $inline) {
+5
src/infrastructure/diff/interface/PhabricatorInlineComment.php
··· 322 322 return $draft_text; 323 323 } 324 324 325 + public function getContentState() { 326 + return array( 327 + 'text' => $this->getContent(), 328 + ); 329 + } 325 330 326 331 /* -( PhabricatorMarkupInterface Implementation )-------------------------- */ 327 332
-27
src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php
··· 22 22 'sigil' => 'inline-edit-form', 23 23 ), 24 24 array( 25 - $this->renderInputs(), 26 25 $this->renderBody(), 27 26 )); 28 27 29 28 return $content; 30 - } 31 - 32 - private function renderInputs() { 33 - $inputs = array(); 34 - $inline = $this->getInlineComment(); 35 - 36 - $inputs[] = array('op', 'edit'); 37 - $inputs[] = array('id', $inline->getID()); 38 - 39 - $inputs[] = array('on_right', $this->getIsOnRight()); 40 - $inputs[] = array('renderer', $this->getRenderer()); 41 - 42 - $out = array(); 43 - 44 - foreach ($inputs as $input) { 45 - list($name, $value) = $input; 46 - $out[] = phutil_tag( 47 - 'input', 48 - array( 49 - 'type' => 'hidden', 50 - 'name' => $name, 51 - 'value' => $value, 52 - )); 53 - } 54 - 55 - return $out; 56 29 } 57 30 58 31 private function renderBody() {
+1 -2
src/infrastructure/diff/view/PHUIDiffInlineCommentView.php
··· 82 82 'number' => $inline->getLineNumber(), 83 83 'length' => $inline->getLineLength(), 84 84 'isNewFile' => (bool)$inline->getIsNewFile(), 85 - 'original' => $inline->getContent(), 86 85 'replyToCommentPHID' => $inline->getReplyToCommentPHID(), 87 86 'isDraft' => $inline->isDraft(), 88 87 'isFixed' => $is_fixed, ··· 93 92 'documentEngineKey' => $inline->getDocumentEngineKey(), 94 93 'startOffset' => $inline->getStartOffset(), 95 94 'endOffset' => $inline->getEndOffset(), 96 - 97 95 'on_right' => $this->getIsOnRight(), 96 + 'contentState' => $inline->getContentState(), 98 97 ); 99 98 } 100 99
+2 -2
webroot/rsrc/js/application/diff/DiffChangeset.js
··· 761 761 return inline; 762 762 }, 763 763 764 - newInlineReply: function(original, text) { 764 + newInlineReply: function(original, state) { 765 765 var inline = new JX.DiffInline() 766 766 .setChangeset(this) 767 767 .bindToReply(original); 768 768 769 769 this._inlines.push(inline); 770 770 771 - inline.create(text); 771 + inline.create(state); 772 772 773 773 return inline; 774 774 },
+1 -1
webroot/rsrc/js/application/diff/DiffChangesetList.js
··· 2410 2410 2411 2411 switch (action) { 2412 2412 case 'save': 2413 - inline.save(e.getTarget()); 2413 + inline.save(); 2414 2414 break; 2415 2415 case 'cancel': 2416 2416 inline.cancel();
+84 -54
webroot/rsrc/js/application/diff/DiffInline.js
··· 19 19 _displaySide: null, 20 20 _isNewFile: null, 21 21 _replyToCommentPHID: null, 22 - _originalText: null, 22 + _originalState: null, 23 23 _snippet: null, 24 24 _menuItems: null, 25 25 _documentEngineKey: null, ··· 42 42 _editRow: null, 43 43 _undoRow: null, 44 44 _undoType: null, 45 - _undoText: null, 45 + _undoState: null, 46 46 47 47 _draftRequest: null, 48 48 _skipFocus: false, ··· 76 76 this._number = parseInt(data.number, 10); 77 77 this._length = parseInt(data.length, 10); 78 78 79 - var original = '' + data.original; 80 - if (original.length) { 81 - this._originalText = original; 82 - } else { 83 - this._originalText = null; 84 - } 79 + this._originalState = data.contentState; 85 80 this._isNewFile = data.isNewFile; 86 81 87 82 this._replyToCommentPHID = data.replyToCommentPHID; ··· 314 309 return this._hasMenuAction('collapse'); 315 310 }, 316 311 317 - getRawText: function() { 318 - return this._originalText; 319 - }, 320 - 321 312 _newRow: function() { 322 313 var attributes = { 323 314 sigil: 'inline-row' ··· 332 323 this._phid = null; 333 324 this._isCollapsed = false; 334 325 335 - this._originalText = null; 326 + this._originalState = null; 336 327 337 328 return row; 338 329 }, ··· 404 395 this._didUpdate(); 405 396 }, 406 397 407 - create: function(text) { 398 + create: function(content_state) { 408 399 var changeset = this.getChangeset(); 409 400 if (!this._documentEngineKey) { 410 401 this._documentEngineKey = changeset.getResponseDocumentEngineKey(); ··· 412 403 413 404 var uri = this._getInlineURI(); 414 405 var handler = JX.bind(this, this._oncreateresponse); 415 - var data = this._newRequestData('new', text); 406 + var data = this._newRequestData('new', content_state); 416 407 417 408 this.setLoading(true); 418 409 ··· 421 412 .send(); 422 413 }, 423 414 415 + _getContentState: function() { 416 + var state; 417 + 418 + if (this._editRow) { 419 + state = this._readFormState(this._editRow); 420 + } else { 421 + state = this._originalState; 422 + } 423 + 424 + return state; 425 + }, 426 + 424 427 reply: function(with_quote) { 425 428 this._closeMenu(); 426 429 427 - var text; 430 + var content_state = this._newContentState(); 428 431 if (with_quote) { 429 - text = this.getRawText(); 432 + var text = this._getContentState().text; 430 433 text = '> ' + text.replace(/\n/g, '\n> ') + '\n\n'; 431 - } else { 432 - text = ''; 434 + content_state.text = text; 433 435 } 434 436 435 437 var changeset = this.getChangeset(); 436 - return changeset.newInlineReply(this, text); 438 + return changeset.newInlineReply(this, content_state); 437 439 }, 438 440 439 - edit: function(text, skip_focus) { 441 + edit: function(content_state, skip_focus) { 440 442 this._closeMenu(); 441 443 442 444 this._skipFocus = !!skip_focus; ··· 456 458 var uri = this._getInlineURI(); 457 459 var handler = JX.bind(this, this._oneditresponse); 458 460 459 - var data = this._newRequestData('edit', text || null); 461 + var data = this._newRequestData('edit', content_state); 460 462 461 463 this.setLoading(true); 462 464 ··· 545 547 return this; 546 548 }, 547 549 548 - _newRequestData: function(operation, text) { 550 + _newRequestData: function(operation, content_state) { 549 551 var data = { 550 552 op: operation, 551 553 is_new: this.isNewFile(), 552 554 on_right: ((this.getDisplaySide() == 'right') ? 1 : 0), 553 - renderer: this.getChangeset().getRendererKey(), 554 - text: text || null 555 + renderer: this.getChangeset().getRendererKey() 555 556 }; 556 557 557 558 if (operation === 'new') { ··· 574 575 JX.copy(data, edit_data); 575 576 } 576 577 578 + if (content_state) { 579 + data.hasContentState = 1; 580 + JX.copy(data, content_state); 581 + } 582 + 577 583 return data; 578 584 }, 579 585 ··· 608 614 // If there's an existing editor, remove it. This happens when you 609 615 // delete a comment from the comment preview area. In this case, we 610 616 // read and preserve the text so "Undo" restores it. 611 - var text; 617 + var state = null; 612 618 if (this._editRow) { 613 - text = this._readText(this._editRow); 619 + state = this._readFormState(this._editRow); 614 620 JX.DOM.remove(this._editRow); 615 621 this._editRow = null; 616 622 } 617 623 618 - this._drawUndeleteRows(text); 624 + this._drawUndeleteRows(state); 619 625 620 626 this.setLoading(false); 621 627 this.setDeleted(true); ··· 623 629 this._didUpdate(); 624 630 }, 625 631 626 - _drawUndeleteRows: function(text) { 632 + _drawUndeleteRows: function(content_state) { 627 633 this._undoType = 'undelete'; 628 - this._undoText = text || null; 634 + this._undoState = content_state || null; 629 635 630 636 return this._drawUndoRows('undelete', this._row); 631 637 }, 632 638 633 - _drawUneditRows: function(text) { 639 + _drawUneditRows: function(content_state) { 634 640 this._undoType = 'unedit'; 635 - this._undoText = text; 641 + this._undoState = content_state; 636 642 637 - return this._drawUndoRows('unedit', null, text); 643 + return this._drawUndoRows('unedit', null); 638 644 }, 639 645 640 - _drawUndoRows: function(mode, cursor, text) { 646 + _drawUndoRows: function(mode, cursor) { 641 647 var templates = this.getChangeset().getUndoTemplates(); 642 648 643 649 var template; ··· 648 654 } 649 655 template = JX.$H(template).getNode(); 650 656 651 - this._undoRow = this._drawRows(template, cursor, mode, text); 657 + this._undoRow = this._drawRows(template, cursor, mode); 652 658 }, 653 659 654 660 _drawContentRows: function(rows) { ··· 660 666 this._editRow = this._drawRows(rows, null, 'edit'); 661 667 }, 662 668 663 - _drawRows: function(rows, cursor, type, text) { 669 + _drawRows: function(rows, cursor, type) { 664 670 var first_row = JX.DOM.scry(rows, 'tr')[0]; 665 671 var row = first_row; 666 672 var anchor = cursor || this._row; ··· 713 719 return result_row; 714 720 }, 715 721 716 - save: function(form) { 722 + save: function() { 717 723 var handler = JX.bind(this, this._onsubmitresponse); 718 724 719 725 this.setLoading(true); 720 726 721 - JX.Workflow.newFromForm(form) 722 - .setHandler(handler) 723 - .start(); 727 + var uri = this._getInlineURI(); 728 + var data = this._newRequestData('save', this._getContentState()); 729 + 730 + new JX.Request(uri, handler) 731 + .setData(data) 732 + .send(); 724 733 }, 725 734 726 735 undo: function() { ··· 740 749 .send(); 741 750 } 742 751 743 - if (this._undoText !== null) { 744 - this.edit(this._undoText); 752 + if (this._undoState !== null) { 753 + this.edit(this._undoState); 745 754 } 746 755 }, 747 756 ··· 751 760 }, 752 761 753 762 cancel: function() { 754 - var text = this._readText(this._editRow); 763 + var state = this._readFormState(this._editRow); 755 764 756 765 JX.DOM.remove(this._editRow); 757 766 this._editRow = null; 758 767 759 - if (text && text.length && (text != this._originalText)) { 760 - this._drawUneditRows(text); 768 + var is_empty = this._isVoidContentState(state); 769 + var is_same = this._isSameContentState(state, this._originalState); 770 + if (!is_empty && !is_same) { 771 + this._drawUneditRows(state); 761 772 } 762 773 763 774 // If this was an empty box and we typed some text and then hit cancel, 764 775 // don't show the empty concrete inline. 765 - if (!this._originalText) { 776 + if (!this._isVoidContentState(this._originalState)) { 766 777 this.setInvisible(true); 767 778 } else { 768 779 this.setInvisible(false); ··· 773 784 // text ("A") to the server as the current persistent state. 774 785 775 786 var uri = this._getInlineURI(); 776 - var data = this._newRequestData('cancel', this._originalText); 787 + var data = this._newRequestData('cancel', this._originalState); 777 788 var handler = JX.bind(this, this._onCancelResponse); 778 789 779 790 this.setLoading(true); ··· 792 803 // If the comment was empty when we started editing it (there's no 793 804 // original text) and empty when we finished editing it (there's no 794 805 // undo row), just delete the comment. 795 - if (!this._originalText && !this.isUndo()) { 806 + if (this._isVoidContentState(this._originalState) && !this.isUndo()) { 796 807 this.setDeleted(true); 797 808 798 809 JX.DOM.remove(this._row); ··· 802 813 } 803 814 }, 804 815 805 - _readText: function(row) { 816 + _readFormState: function(row) { 806 817 var textarea; 807 818 try { 808 819 textarea = JX.DOM.find( ··· 813 824 return null; 814 825 } 815 826 816 - return textarea.value; 827 + return { 828 + text: textarea.value 829 + }; 817 830 }, 818 831 819 832 _onsubmitresponse: function(response) { ··· 920 933 return null; 921 934 } 922 935 923 - var text = this._readText(this._editRow); 924 - if (text === null) { 936 + var state = this._readFormState(this._editRow); 937 + if (this._isVoidContentState(state)) { 925 938 return null; 926 939 } 927 940 928 - return { 941 + var draft_data = { 929 942 op: 'draft', 930 943 id: this.getID(), 931 - text: text 932 944 }; 945 + 946 + JX.copy(draft_data, state); 947 + 948 + return draft_data; 933 949 }, 934 950 935 951 triggerDraft: function() { ··· 1035 1051 if (this._menu) { 1036 1052 this._menu.close(); 1037 1053 } 1054 + }, 1055 + 1056 + _isVoidContentState: function(state) { 1057 + return !state.text.length; 1058 + }, 1059 + 1060 + _isSameContentState: function(u, v) { 1061 + return (u.text === v.text); 1062 + }, 1063 + 1064 + _newContentState: function() { 1065 + return { 1066 + text: '' 1067 + }; 1038 1068 } 1039 1069 1040 1070 }