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

Add an "Unsaved" button to the Differential persistent header

Summary: Ref T12733.

Test Plan: {F4987956}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T12733

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

+181 -45
+32 -31
resources/celerity/map.php
··· 12 12 'core.pkg.css' => '5284a0e0', 13 13 'core.pkg.js' => '1475bd91', 14 14 'darkconsole.pkg.js' => '1f9a31bc', 15 - 'differential.pkg.css' => 'a2755617', 16 - 'differential.pkg.js' => '9cab3335', 15 + 'differential.pkg.css' => '1ccbf3a9', 16 + 'differential.pkg.js' => 'b453b745', 17 17 'diffusion.pkg.css' => 'b93d9b8c', 18 18 'diffusion.pkg.js' => '84c8f8fd', 19 19 'favicon.ico' => '30672e08', ··· 64 64 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 65 65 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 66 66 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', 67 - 'rsrc/css/application/differential/changeset-view.css' => '983751ee', 67 + 'rsrc/css/application/differential/changeset-view.css' => 'c3f44655', 68 68 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 69 69 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 70 70 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', ··· 393 393 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 394 394 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 395 395 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 396 - 'rsrc/js/application/diff/DiffChangeset.js' => 'aaaf4cb5', 397 - 'rsrc/js/application/diff/DiffChangesetList.js' => '85abc805', 396 + 'rsrc/js/application/diff/DiffChangeset.js' => 'fc3919c8', 397 + 'rsrc/js/application/diff/DiffChangesetList.js' => 'ffa063d8', 398 398 'rsrc/js/application/diff/DiffInline.js' => '1d17130f', 399 399 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 400 400 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', ··· 526 526 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 527 527 'rsrc/js/phuix/PHUIXActionView.js' => 'b3465b9b', 528 528 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'f6699267', 529 - 'rsrc/js/phuix/PHUIXButtonView.js' => '0f13520b', 529 + 'rsrc/js/phuix/PHUIXButtonView.js' => 'b3c515be', 530 530 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50', 531 531 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', 532 532 'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671', ··· 560 560 'conpherence-thread-manager' => '4d863052', 561 561 'conpherence-transaction-css' => '85129c68', 562 562 'd3' => 'a11a5ff2', 563 - 'differential-changeset-view-css' => '983751ee', 563 + 'differential-changeset-view-css' => 'c3f44655', 564 564 'differential-core-view-css' => '5b7b8ff4', 565 565 'differential-revision-add-comment-css' => 'c47f8c40', 566 566 'differential-revision-comment-css' => '14b8565a', ··· 772 772 'phabricator-darklog' => 'c8e1ffe3', 773 773 'phabricator-darkmessage' => 'c48cccdd', 774 774 'phabricator-dashboard-css' => 'fe5b1869', 775 - 'phabricator-diff-changeset' => 'aaaf4cb5', 776 - 'phabricator-diff-changeset-list' => '85abc805', 775 + 'phabricator-diff-changeset' => 'fc3919c8', 776 + 'phabricator-diff-changeset-list' => 'ffa063d8', 777 777 'phabricator-diff-inline' => '1d17130f', 778 778 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 779 779 'phabricator-draggable-list' => 'bea6e7f4', ··· 878 878 'phuix-action-list-view' => 'b5c256b8', 879 879 'phuix-action-view' => 'b3465b9b', 880 880 'phuix-autocomplete' => 'f6699267', 881 - 'phuix-button-view' => '0f13520b', 881 + 'phuix-button-view' => 'b3c515be', 882 882 'phuix-dropdown-menu' => '8018ee50', 883 883 'phuix-form-control-view' => '83e03671', 884 884 'phuix-icon-view' => 'bff6884b', ··· 959 959 'javelin-workflow', 960 960 'javelin-dom', 961 961 'javelin-router', 962 - ), 963 - '0f13520b' => array( 964 - 'javelin-install', 965 - 'javelin-dom', 966 962 ), 967 963 '0f764c35' => array( 968 964 'javelin-install', ··· 1514 1510 'javelin-dom', 1515 1511 'javelin-stratcom', 1516 1512 ), 1517 - '85abc805' => array( 1518 - 'javelin-install', 1519 - ), 1520 1513 '85ee8ce6' => array( 1521 1514 'aphront-dialog-view-css', 1522 1515 ), ··· 1621 1614 'javelin-mask', 1622 1615 'phabricator-drag-and-drop-file-upload', 1623 1616 ), 1624 - '983751ee' => array( 1625 - 'phui-inline-comment-view-css', 1626 - ), 1627 1617 '9a6dd75c' => array( 1628 1618 'javelin-behavior', 1629 1619 'javelin-stratcom', ··· 1716 1706 'javelin-util', 1717 1707 'phabricator-prefab', 1718 1708 ), 1719 - 'aaaf4cb5' => array( 1720 - 'javelin-dom', 1721 - 'javelin-util', 1722 - 'javelin-stratcom', 1723 - 'javelin-install', 1724 - 'javelin-workflow', 1725 - 'javelin-router', 1726 - 'javelin-behavior-device', 1727 - 'javelin-vector', 1728 - 'phabricator-diff-inline', 1729 - ), 1730 1709 'ab2f381b' => array( 1731 1710 'javelin-request', 1732 1711 'javelin-behavior', ··· 1784 1763 'b3a4b884' => array( 1785 1764 'javelin-behavior', 1786 1765 'phabricator-prefab', 1766 + ), 1767 + 'b3c515be' => array( 1768 + 'javelin-install', 1769 + 'javelin-dom', 1787 1770 ), 1788 1771 'b3e7d692' => array( 1789 1772 'javelin-install', ··· 1876 1859 'javelin-stratcom', 1877 1860 'javelin-dom', 1878 1861 'javelin-vector', 1862 + ), 1863 + 'c3f44655' => array( 1864 + 'phui-inline-comment-view-css', 1879 1865 ), 1880 1866 'c420b0b9' => array( 1881 1867 'javelin-behavior', ··· 2155 2141 'javelin-behavior-device', 2156 2142 'phabricator-keyboard-shortcut', 2157 2143 ), 2144 + 'fc3919c8' => array( 2145 + 'javelin-dom', 2146 + 'javelin-util', 2147 + 'javelin-stratcom', 2148 + 'javelin-install', 2149 + 'javelin-workflow', 2150 + 'javelin-router', 2151 + 'javelin-behavior-device', 2152 + 'javelin-vector', 2153 + 'phabricator-diff-inline', 2154 + ), 2158 2155 'fc91ab6c' => array( 2159 2156 'javelin-behavior', 2160 2157 'javelin-dom', ··· 2165 2162 'javelin-dom', 2166 2163 'javelin-view-visitor', 2167 2164 'javelin-util', 2165 + ), 2166 + 'ffa063d8' => array( 2167 + 'javelin-install', 2168 + 'phuix-button-view', 2168 2169 ), 2169 2170 ), 2170 2171 'packages' => array(
+4
src/applications/differential/view/DifferentialChangesetListView.php
··· 275 275 pht('Hide or show the current file.'), 276 276 'You must select a file to hide or show.' => 277 277 pht('You must select a file to hide or show.'), 278 + 279 + 'Unsaved' => pht('Unsaved'), 280 + 'Unsubmitted' => pht('Unsubmitted'), 281 + 'Comments' => pht('Comments'), 278 282 ), 279 283 )); 280 284
+4
webroot/rsrc/css/application/differential/changeset-view.css
··· 413 413 .diff-banner-has-unsubmitted { 414 414 background: {$sh-yellowbackground}; 415 415 } 416 + 417 + .diff-banner-buttons { 418 + float: right; 419 + }
+26 -2
webroot/rsrc/js/application/diff/DiffChangeset.js
··· 403 403 block.items.push(rows[ii]); 404 404 } 405 405 406 + var last_inline = null; 407 + var last_inline_item = null; 406 408 for (ii = 0; ii < blocks.length; ii++) { 407 409 block = blocks[ii]; 408 410 ··· 422 424 for (var jj = 0; jj < block.items.length; jj++) { 423 425 var inline = this.getInlineForRow(block.items[jj]); 424 426 425 - items.push({ 427 + // When comments are being edited, they have a hidden row with 428 + // the actual comment and then a visible row with the editor. 429 + 430 + // In this case, we only want to generate one item, but it should 431 + // use the editor as a scroll target. To accomplish this, check if 432 + // this row has the same inline as the previous row. If so, update 433 + // the last item to use this row's nodes. 434 + 435 + if (inline === last_inline) { 436 + last_inline_item.nodes.begin = block.items[jj]; 437 + last_inline_item.nodes.end = block.items[jj]; 438 + continue; 439 + } else { 440 + last_inline = inline; 441 + } 442 + 443 + last_inline_item = { 426 444 type: block.type, 427 445 changeset: this, 428 446 target: inline, 429 447 hidden: inline.isHidden(), 448 + deleted: !inline.getID() && !inline.isEditing(), 430 449 nodes: { 431 450 begin: block.items[jj], 432 451 end: block.items[jj] 452 + }, 453 + attributes: { 454 + unsaved: inline.isEditing() 433 455 } 434 - }); 456 + }; 457 + 458 + items.push(last_inline_item); 435 459 } 436 460 } 437 461 }
+114 -12
webroot/rsrc/js/application/diff/DiffChangesetList.js
··· 1 1 /** 2 2 * @provides phabricator-diff-changeset-list 3 3 * @requires javelin-install 4 + * phuix-button-view 4 5 * @javelin 5 6 */ 6 7 ··· 111 112 _rangeTarget: null, 112 113 113 114 _bannerNode: null, 115 + _unsavedButton: null, 114 116 115 117 sleep: function() { 116 118 this._asleep = true; ··· 258 260 259 261 _installJumpKey: function(key, label, delta, filter, show_hidden) { 260 262 filter = filter || null; 261 - var handler = JX.bind(this, this._onjumpkey, delta, filter, show_hidden); 263 + 264 + var options = { 265 + filter: filter, 266 + hidden: show_hidden 267 + }; 268 + 269 + var handler = JX.bind(this, this._onjumpkey, delta, options); 262 270 return this._installKey(key, label, handler); 263 271 }, 264 272 ··· 440 448 .show(); 441 449 }, 442 450 443 - _onjumpkey: function(delta, filter, show_hidden, manager) { 451 + _onjumpkey: function(delta, options) { 444 452 var state = this._getSelectionState(); 445 453 454 + var filter = options.filter || null; 455 + var hidden = options.hidden || false; 456 + var wrap = options.wrap || false; 457 + var attribute = options.attribute || null; 458 + 446 459 var cursor = state.cursor; 447 460 var items = state.items; 448 461 ··· 452 465 return; 453 466 } 454 467 468 + var did_wrap = false; 455 469 while (true) { 456 470 if (cursor === null) { 457 471 cursor = 0; ··· 464 478 return; 465 479 } 466 480 467 - // If we've gone forward off the end of the list, bail out. 481 + // If we've gone forward off the end of the list, figure out where we 482 + // should end up. 468 483 if (cursor >= items.length) { 469 - return; 484 + if (!wrap) { 485 + // If we aren't wrapping around, we're done. 486 + return; 487 + } 488 + 489 + if (did_wrap) { 490 + // If we're already wrapped around, we're done. 491 + return; 492 + } 493 + 494 + // Otherwise, wrap the cursor back to the top. 495 + cursor = 0; 496 + did_wrap = true; 470 497 } 471 498 472 499 // If we're selecting things of a particular type (like only files) ··· 479 506 480 507 // If the item is hidden, don't select it when iterating with jump 481 508 // keys. It can still potentially be selected in other ways. 482 - if (!show_hidden) { 509 + if (!hidden) { 483 510 if (items[cursor].hidden) { 484 511 continue; 485 512 } 486 513 } 487 514 515 + // If the item has been deleted, don't select it when iterating. The 516 + // cursor may remain on it until it is removed. 517 + if (items[cursor].deleted) { 518 + continue; 519 + } 520 + 521 + // If we're selecting things with a particular attribute, like 522 + // "unsaved", skip items without the attribute. 523 + if (attribute !== null) { 524 + if (!(items[cursor].attributes || {})[attribute]) { 525 + continue; 526 + } 527 + } 528 + 488 529 // Otherwise, we've found a valid item to select. 489 530 break; 490 531 } 491 532 492 - this._setSelectionState(items[cursor], manager); 533 + this._setSelectionState(items[cursor]); 493 534 }, 494 535 495 536 _getSelectionState: function() { ··· 512 553 }; 513 554 }, 514 555 515 - _setSelectionState: function(item, manager) { 556 + _setSelectionState: function(item) { 516 557 this._cursorItem = item; 517 - this._redrawSelection(manager, true); 558 + this._redrawSelection(true); 518 559 519 560 return this; 520 561 }, 521 562 522 - _redrawSelection: function(manager, scroll) { 563 + _redrawSelection: function(scroll) { 523 564 var cursor = this._cursorItem; 524 565 if (!cursor) { 525 566 this.setFocus(null); 526 567 return; 527 568 } 528 569 570 + // If this item has been removed from the document (for example: create 571 + // a new empty comment, then use the "Unsaved" button to select it, then 572 + // cancel it), we can still keep the cursor here but do not want to show 573 + // a selection reticle over an invisible node. 574 + if (cursor.deleted) { 575 + this.setFocus(null); 576 + return; 577 + } 578 + 529 579 this.setFocus(cursor.nodes.begin, cursor.nodes.end); 530 580 531 - if (manager && scroll) { 532 - manager.scrollTo(cursor.nodes.begin); 581 + if (scroll) { 582 + var pos = JX.$V(cursor.nodes.begin); 583 + JX.DOM.scrollToPosition(0, pos.y - 60); 533 584 } 534 585 535 586 return this; ··· 1320 1371 'diff-banner-has-unsubmitted', 1321 1372 !!unsubmitted.length); 1322 1373 1374 + var unsaved_button = this._getUnsavedButton(); 1375 + var pht = this.getTranslations(); 1376 + 1377 + if (unsaved.length) { 1378 + unsaved_button.setText(unsaved.length + ' ' + pht('Unsaved')); 1379 + JX.DOM.show(unsaved_button.getNode()); 1380 + } else { 1381 + JX.DOM.hide(unsaved_button.getNode()); 1382 + } 1383 + 1384 + var path_view = [icon, ' ', changeset.getDisplayPath()]; 1385 + 1386 + var buttons_attrs = { 1387 + className: 'diff-banner-buttons' 1388 + }; 1389 + 1390 + var buttons_list = [ 1391 + unsaved_button.getNode() 1392 + ]; 1393 + 1394 + var buttons_view = JX.$N('div', buttons_attrs, buttons_list); 1395 + 1323 1396 var icon = new JX.PHUIXIconView() 1324 1397 .setIcon(changeset.getIcon()) 1325 1398 .getNode(); 1326 - JX.DOM.setContent(node, [icon, ' ', changeset.getDisplayPath()]); 1399 + JX.DOM.setContent(node, [buttons_view, path_view]); 1327 1400 1328 1401 document.body.appendChild(node); 1402 + }, 1403 + 1404 + _getUnsavedButton: function() { 1405 + if (!this._unsavedButton) { 1406 + var button = new JX.PHUIXButtonView() 1407 + .setIcon('fa-commenting-o') 1408 + .setButtonType(JX.PHUIXButtonView.BUTTONTYPE_SIMPLE); 1409 + 1410 + var node = button.getNode(); 1411 + 1412 + var onunsaved = JX.bind(this, this._onunsavedclick); 1413 + JX.DOM.listen(node, 'click', null, onunsaved); 1414 + 1415 + this._unsavedButton = button; 1416 + } 1417 + 1418 + return this._unsavedButton; 1419 + }, 1420 + 1421 + _onunsavedclick: function(e) { 1422 + e.kill(); 1423 + 1424 + var options = { 1425 + filter: 'comment', 1426 + wrap: true, 1427 + attribute: 'unsaved' 1428 + }; 1429 + 1430 + this._onjumpkey(1, options); 1329 1431 }, 1330 1432 1331 1433 _getBannerNode: function() {
+1
webroot/rsrc/js/phuix/PHUIXButtonView.js
··· 57 57 58 58 setText: function(text) { 59 59 JX.DOM.setContent(this._getTextNode(), text); 60 + this._redraw(); 60 61 return this; 61 62 }, 62 63