@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 "Sort by Creation Date" filter to workboards and modularize remaining order behaviors

Summary:
Depends on D20274. Ref T10578. This is en route to an ordering by points, it's just a simpler half-step on the way there.

Allow columns to be sorted by creation date, so the newest tasks rise to the top.

In this ordering you can never reposition cards, since editing a creation date by dragging makes no sense. This will be true of the "points" ordering too (although we could imagine doing something like prompting the user, some day).

Test Plan: Viewed boards by "natural" (allows reordering both when dragging within and between columns), "priority" (reorder only within columns), and "creation date" (reorder never). Dragged cards around between and within columns, got apparently sensible behavior.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T10578

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

+212 -58
+48 -42
resources/celerity/map.php
··· 10 10 'conpherence.pkg.css' => '3c8a0668', 11 11 'conpherence.pkg.js' => '020aebcf', 12 12 'core.pkg.css' => '34ce1741', 13 - 'core.pkg.js' => '200a0a61', 13 + 'core.pkg.js' => 'f9c2509b', 14 14 'differential.pkg.css' => '8d8360fb', 15 15 'differential.pkg.js' => '67e02996', 16 16 'diffusion.pkg.css' => '42c75c37', ··· 408 408 'rsrc/js/application/phortune/phortune-credit-card-form.js' => 'd12d214f', 409 409 'rsrc/js/application/policy/behavior-policy-control.js' => '0eaa33a9', 410 410 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '9347f172', 411 - 'rsrc/js/application/projects/WorkboardBoard.js' => 'eb55f7e8', 411 + 'rsrc/js/application/projects/WorkboardBoard.js' => '9d59f098', 412 412 'rsrc/js/application/projects/WorkboardCard.js' => '0392a5d8', 413 413 'rsrc/js/application/projects/WorkboardCardTemplate.js' => '2a61f8d4', 414 - 'rsrc/js/application/projects/WorkboardColumn.js' => 'fd4c2069', 414 + 'rsrc/js/application/projects/WorkboardColumn.js' => 'ec5c5ce0', 415 415 'rsrc/js/application/projects/WorkboardController.js' => '42c7a5a7', 416 416 'rsrc/js/application/projects/WorkboardHeader.js' => '111bfd2d', 417 417 'rsrc/js/application/projects/WorkboardHeaderTemplate.js' => 'b65351bd', 418 - 'rsrc/js/application/projects/behavior-project-boards.js' => '285c337a', 418 + 'rsrc/js/application/projects/WorkboardOrderTemplate.js' => '03e8891f', 419 + 'rsrc/js/application/projects/behavior-project-boards.js' => '412af9d4', 419 420 'rsrc/js/application/projects/behavior-project-create.js' => '34c53422', 420 421 'rsrc/js/application/projects/behavior-reorder-columns.js' => '8ac32fd9', 421 422 'rsrc/js/application/releeph/releeph-preview-branch.js' => '75184d68', ··· 436 437 'rsrc/js/application/uiexample/notification-example.js' => '29819b75', 437 438 'rsrc/js/core/Busy.js' => '5202e831', 438 439 'rsrc/js/core/DragAndDropFileUpload.js' => '4370900d', 439 - 'rsrc/js/core/DraggableList.js' => '91f40fbf', 440 + 'rsrc/js/core/DraggableList.js' => '8bc7d797', 440 441 'rsrc/js/core/Favicon.js' => '7930776a', 441 442 'rsrc/js/core/FileUpload.js' => 'ab85e184', 442 443 'rsrc/js/core/Hovercard.js' => '074f0783', ··· 656 657 'javelin-behavior-phuix-example' => 'c2c500a7', 657 658 'javelin-behavior-policy-control' => '0eaa33a9', 658 659 'javelin-behavior-policy-rule-editor' => '9347f172', 659 - 'javelin-behavior-project-boards' => '285c337a', 660 + 'javelin-behavior-project-boards' => '412af9d4', 660 661 'javelin-behavior-project-create' => '34c53422', 661 662 'javelin-behavior-quicksand-blacklist' => '5a6f6a06', 662 663 'javelin-behavior-read-only-warning' => 'b9109f8f', ··· 728 729 'javelin-view-renderer' => '9aae2b66', 729 730 'javelin-view-visitor' => '308f9fe4', 730 731 'javelin-websocket' => 'fdc13e4e', 731 - 'javelin-workboard-board' => 'eb55f7e8', 732 + 'javelin-workboard-board' => '9d59f098', 732 733 'javelin-workboard-card' => '0392a5d8', 733 734 'javelin-workboard-card-template' => '2a61f8d4', 734 - 'javelin-workboard-column' => 'fd4c2069', 735 + 'javelin-workboard-column' => 'ec5c5ce0', 735 736 'javelin-workboard-controller' => '42c7a5a7', 736 737 'javelin-workboard-header' => '111bfd2d', 737 738 'javelin-workboard-header-template' => 'b65351bd', 739 + 'javelin-workboard-order-template' => '03e8891f', 738 740 'javelin-workflow' => '958e9045', 739 741 'maniphest-report-css' => '3d53188b', 740 742 'maniphest-task-edit-css' => '272daa84', ··· 759 761 'phabricator-diff-changeset-list' => '04023d82', 760 762 'phabricator-diff-inline' => 'a4a14a94', 761 763 'phabricator-drag-and-drop-file-upload' => '4370900d', 762 - 'phabricator-draggable-list' => '91f40fbf', 764 + 'phabricator-draggable-list' => '8bc7d797', 763 765 'phabricator-fatal-config-template-css' => '20babf50', 764 766 'phabricator-favicon' => '7930776a', 765 767 'phabricator-feed-css' => 'd8b6e3f8', ··· 910 912 'javelin-util', 911 913 ), 912 914 '0392a5d8' => array( 915 + 'javelin-install', 916 + ), 917 + '03e8891f' => array( 913 918 'javelin-install', 914 919 ), 915 920 '04023d82' => array( ··· 1105 1110 'javelin-json', 1106 1111 'phabricator-prefab', 1107 1112 ), 1108 - '285c337a' => array( 1109 - 'javelin-behavior', 1110 - 'javelin-dom', 1111 - 'javelin-util', 1112 - 'javelin-vector', 1113 - 'javelin-stratcom', 1114 - 'javelin-workflow', 1115 - 'javelin-workboard-controller', 1116 - ), 1117 1113 '289bf236' => array( 1118 1114 'javelin-install', 1119 1115 'javelin-util', ··· 1230 1226 '407ee861' => array( 1231 1227 'javelin-behavior', 1232 1228 'javelin-uri', 1229 + ), 1230 + '412af9d4' => array( 1231 + 'javelin-behavior', 1232 + 'javelin-dom', 1233 + 'javelin-util', 1234 + 'javelin-vector', 1235 + 'javelin-stratcom', 1236 + 'javelin-workflow', 1237 + 'javelin-workboard-controller', 1233 1238 ), 1234 1239 '4234f572' => array( 1235 1240 'syntax-default-css', ··· 1588 1593 'javelin-dom', 1589 1594 'javelin-typeahead-normalizer', 1590 1595 ), 1596 + '8bc7d797' => array( 1597 + 'javelin-install', 1598 + 'javelin-dom', 1599 + 'javelin-stratcom', 1600 + 'javelin-util', 1601 + 'javelin-vector', 1602 + 'javelin-magical-init', 1603 + ), 1591 1604 '8c2ed2bf' => array( 1592 1605 'javelin-behavior', 1593 1606 'javelin-dom', ··· 1634 1647 'javelin-util', 1635 1648 'javelin-workflow', 1636 1649 'javelin-stratcom', 1637 - ), 1638 - '91f40fbf' => array( 1639 - 'javelin-install', 1640 - 'javelin-dom', 1641 - 'javelin-stratcom', 1642 - 'javelin-util', 1643 - 'javelin-vector', 1644 - 'javelin-magical-init', 1645 1650 ), 1646 1651 '92388bae' => array( 1647 1652 'javelin-behavior', ··· 1719 1724 'javelin-dom', 1720 1725 'javelin-uri', 1721 1726 'phabricator-textareautils', 1727 + ), 1728 + '9d59f098' => array( 1729 + 'javelin-install', 1730 + 'javelin-dom', 1731 + 'javelin-util', 1732 + 'javelin-stratcom', 1733 + 'javelin-workflow', 1734 + 'phabricator-draggable-list', 1735 + 'javelin-workboard-column', 1736 + 'javelin-workboard-header-template', 1737 + 'javelin-workboard-card-template', 1738 + 'javelin-workboard-order-template', 1722 1739 ), 1723 1740 '9f081f05' => array( 1724 1741 'javelin-behavior', ··· 2051 2068 'javelin-install', 2052 2069 'javelin-event', 2053 2070 ), 2054 - 'eb55f7e8' => array( 2055 - 'javelin-install', 2056 - 'javelin-dom', 2057 - 'javelin-util', 2058 - 'javelin-stratcom', 2059 - 'javelin-workflow', 2060 - 'phabricator-draggable-list', 2061 - 'javelin-workboard-column', 2062 - 'javelin-workboard-header-template', 2063 - 'javelin-workboard-card-template', 2064 - ), 2065 2071 'ec4e31c0' => array( 2066 2072 'phui-timeline-view-css', 2073 + ), 2074 + 'ec5c5ce0' => array( 2075 + 'javelin-install', 2076 + 'javelin-workboard-card', 2077 + 'javelin-workboard-header', 2067 2078 ), 2068 2079 'ee77366f' => array( 2069 2080 'aphront-dialog-view-css', ··· 2132 2143 'fce5d170' => array( 2133 2144 'javelin-magical-init', 2134 2145 'javelin-util', 2135 - ), 2136 - 'fd4c2069' => array( 2137 - 'javelin-install', 2138 - 'javelin-workboard-card', 2139 - 'javelin-workboard-header', 2140 2146 ), 2141 2147 'fdc13e4e' => array( 2142 2148 'javelin-install',
+2
src/__phutil_library_map__.php
··· 4050 4050 'PhabricatorProjectColorTransaction' => 'applications/project/xaction/PhabricatorProjectColorTransaction.php', 4051 4051 'PhabricatorProjectColorsConfigType' => 'applications/project/config/PhabricatorProjectColorsConfigType.php', 4052 4052 'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php', 4053 + 'PhabricatorProjectColumnCreatedOrder' => 'applications/project/order/PhabricatorProjectColumnCreatedOrder.php', 4053 4054 'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php', 4054 4055 'PhabricatorProjectColumnEditController' => 'applications/project/controller/PhabricatorProjectColumnEditController.php', 4055 4056 'PhabricatorProjectColumnHeader' => 'applications/project/order/PhabricatorProjectColumnHeader.php', ··· 10135 10136 'PhabricatorExtendedPolicyInterface', 10136 10137 'PhabricatorConduitResultInterface', 10137 10138 ), 10139 + 'PhabricatorProjectColumnCreatedOrder' => 'PhabricatorProjectColumnOrder', 10138 10140 'PhabricatorProjectColumnDetailController' => 'PhabricatorProjectBoardController', 10139 10141 'PhabricatorProjectColumnEditController' => 'PhabricatorProjectBoardController', 10140 10142 'PhabricatorProjectColumnHeader' => 'Phobject',
+4
src/applications/project/controller/PhabricatorProjectBoardViewController.php
··· 631 631 632 632 $header_keys = $ordering->getHeaderKeysForObjects($all_tasks); 633 633 634 + $order_maps = array(); 635 + $order_maps[] = $ordering->toDictionary(); 636 + 634 637 $properties = array(); 635 638 636 639 $behavior_config = array( ··· 642 645 643 646 'boardPHID' => $project->getPHID(), 644 647 'order' => $this->sortKey, 648 + 'orders' => $order_maps, 645 649 'headers' => $headers, 646 650 'headerKeys' => $header_keys, 647 651 'templateMap' => $templates,
+31
src/applications/project/order/PhabricatorProjectColumnCreatedOrder.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectColumnCreatedOrder 4 + extends PhabricatorProjectColumnOrder { 5 + 6 + const ORDERKEY = 'created'; 7 + 8 + public function getDisplayName() { 9 + return pht('Sort by Created Date'); 10 + } 11 + 12 + protected function newMenuIconIcon() { 13 + return 'fa-clock-o'; 14 + } 15 + 16 + public function getHasHeaders() { 17 + return false; 18 + } 19 + 20 + public function getCanReorder() { 21 + return false; 22 + } 23 + 24 + protected function newSortVectorForObject($object) { 25 + return array( 26 + (int)-$object->getDateCreated(), 27 + (int)-$object->getID(), 28 + ); 29 + } 30 + 31 + }
+8
src/applications/project/order/PhabricatorProjectColumnNaturalOrder.php
··· 9 9 return pht('Natural'); 10 10 } 11 11 12 + public function getHasHeaders() { 13 + return false; 14 + } 15 + 16 + public function getCanReorder() { 17 + return true; 18 + } 19 + 12 20 }
+10
src/applications/project/order/PhabricatorProjectColumnOrder.php
··· 68 68 } 69 69 70 70 abstract public function getDisplayName(); 71 + abstract public function getHasHeaders(); 72 + abstract public function getCanReorder(); 71 73 72 74 protected function newColumnTransactions($object, array $header) { 73 75 return array(); ··· 171 173 final protected function newHeader() { 172 174 return id(new PhabricatorProjectColumnHeader()) 173 175 ->setOrderKey($this->getColumnOrderKey()); 176 + } 177 + 178 + final public function toDictionary() { 179 + return array( 180 + 'orderKey' => $this->getColumnOrderKey(), 181 + 'hasHeaders' => $this->getHasHeaders(), 182 + 'canReorder' => $this->getCanReorder(), 183 + ); 174 184 } 175 185 176 186 }
+8
src/applications/project/order/PhabricatorProjectColumnOwnerOrder.php
··· 13 13 return 'fa-users'; 14 14 } 15 15 16 + public function getHasHeaders() { 17 + return true; 18 + } 19 + 20 + public function getCanReorder() { 21 + return true; 22 + } 23 + 16 24 protected function newHeaderKeyForObject($object) { 17 25 return $this->newHeaderKeyForOwnerPHID($object->getOwnerPHID()); 18 26 }
+8
src/applications/project/order/PhabricatorProjectColumnPriorityOrder.php
··· 13 13 return 'fa-sort-numeric-asc'; 14 14 } 15 15 16 + public function getHasHeaders() { 17 + return true; 18 + } 19 + 20 + public function getCanReorder() { 21 + return true; 22 + } 23 + 16 24 protected function newHeaderKeyForObject($object) { 17 25 return $this->newHeaderKeyForPriority($object->getPriority()); 18 26 }
+29 -2
webroot/rsrc/js/application/projects/WorkboardBoard.js
··· 9 9 * javelin-workboard-column 10 10 * javelin-workboard-header-template 11 11 * javelin-workboard-card-template 12 + * javelin-workboard-order-template 12 13 * @javelin 13 14 */ 14 15 ··· 21 22 22 23 this._headers = {}; 23 24 this._cards = {}; 25 + this._orders = {}; 24 26 25 27 this._buildColumns(); 26 28 }, ··· 70 72 return this._headers[header_key]; 71 73 }, 72 74 75 + getOrderTemplate: function(order_key) { 76 + if (!this._orders[order_key]) { 77 + this._orders[order_key] = new JX.WorkboardOrderTemplate(order_key); 78 + } 79 + 80 + return this._orders[order_key]; 81 + }, 82 + 73 83 getHeaderTemplatesForOrder: function(order) { 74 84 var templates = []; 75 85 ··· 134 144 _setupDragHandlers: function() { 135 145 var columns = this.getColumns(); 136 146 147 + var order_template = this.getOrderTemplate(this.getOrder()); 148 + var has_headers = order_template.getHasHeaders(); 149 + var can_reorder = order_template.getCanReorder(); 150 + 137 151 var lists = []; 138 152 for (var k in columns) { 139 153 var column = columns[k]; ··· 149 163 list.setGhostHandler( 150 164 JX.bind(column, column.handleDragGhost, default_handler)); 151 165 152 - if (this.getOrder() !== 'natural') { 153 - list.setCompareHandler(JX.bind(column, column.compareHandler)); 166 + // The "compare handler" locks cards into a specific position in the 167 + // column. 168 + list.setCompareHandler(JX.bind(column, column.compareHandler)); 169 + 170 + // If the view has group headers, we lock cards into the right position 171 + // when moving them between columns, but not within a column. 172 + if (has_headers) { 173 + list.setCompareOnMove(true); 174 + } 175 + 176 + // If we can't reorder cards, we always lock them into their current 177 + // position. 178 + if (!can_reorder) { 179 + list.setCompareOnMove(true); 180 + list.setCompareOnReorder(true); 154 181 } 155 182 156 183 list.listen('didDrop', JX.bind(this, this._onmovecard, list));
+1 -9
webroot/rsrc/js/application/projects/WorkboardColumn.js
··· 189 189 var board = this.getBoard(); 190 190 var order = board.getOrder(); 191 191 192 - // TODO: This should be modularized into "ProjectColumnOrder" classes, 193 - // but is currently hard-coded. 194 - 195 - switch (order) { 196 - case 'natural': 197 - return false; 198 - } 199 - 200 - return true; 192 + return board.getOrderTemplate(order).getHasHeaders(); 201 193 }, 202 194 203 195 redraw: function() {
+27
webroot/rsrc/js/application/projects/WorkboardOrderTemplate.js
··· 1 + /** 2 + * @provides javelin-workboard-order-template 3 + * @requires javelin-install 4 + * @javelin 5 + */ 6 + 7 + JX.install('WorkboardOrderTemplate', { 8 + 9 + construct: function(order) { 10 + this._orderKey = order; 11 + }, 12 + 13 + properties: { 14 + hasHeaders: false, 15 + canReorder: false 16 + }, 17 + 18 + members: { 19 + _orderKey: null, 20 + 21 + getOrderKey: function() { 22 + return this._orderKey; 23 + } 24 + 25 + } 26 + 27 + });
+13 -3
webroot/rsrc/js/application/projects/behavior-project-boards.js
··· 87 87 .setNodeHTMLTemplate(templates[k]); 88 88 } 89 89 90 + var ii; 90 91 var column_maps = config.columnMaps; 91 92 for (var column_phid in column_maps) { 92 93 var column = board.getColumn(column_phid); 93 94 var column_map = column_maps[column_phid]; 94 - for (var ii = 0; ii < column_map.length; ii++) { 95 + for (ii = 0; ii < column_map.length; ii++) { 95 96 column.newCard(column_map[ii]); 96 97 } 97 98 } ··· 111 112 } 112 113 113 114 var headers = config.headers; 114 - for (var jj = 0; jj < headers.length; jj++) { 115 - var header = headers[jj]; 115 + for (ii = 0; ii < headers.length; ii++) { 116 + var header = headers[ii]; 116 117 117 118 board.getHeaderTemplate(header.key) 118 119 .setOrder(header.order) 119 120 .setNodeHTMLTemplate(header.template) 120 121 .setVector(header.vector) 121 122 .setEditProperties(header.editProperties); 123 + } 124 + 125 + var orders = config.orders; 126 + for (ii = 0; ii < orders.length; ii++) { 127 + var order = orders[ii]; 128 + 129 + board.getOrderTemplate(order.orderKey) 130 + .setHasHeaders(order.hasHeaders) 131 + .setCanReorder(order.canReorder); 122 132 } 123 133 124 134 var header_keys = config.headerKeys;
+23 -2
webroot/rsrc/js/core/DraggableList.js
··· 43 43 isDropTargetHandler: null, 44 44 canDragX: false, 45 45 outerContainer: null, 46 - hasInfiniteHeight: false 46 + hasInfiniteHeight: false, 47 + compareOnMove: false, 48 + compareOnReorder: false 47 49 }, 48 50 49 51 members : { ··· 501 503 502 504 var cur_target = false; 503 505 if (target_list) { 504 - if (compare_handler && (target_list !== this)) { 506 + // Determine if we're going to use the compare handler or not: the 507 + // compare hander locks items into a specific place in the list. For 508 + // example, on Workboards, some operations permit the user to drag 509 + // items between lists, but not to reorder items within a list. 510 + 511 + var should_compare = false; 512 + 513 + var is_reorder = (target_list === this); 514 + var is_move = (target_list !== this); 515 + 516 + if (compare_handler) { 517 + if (is_reorder && this.getCompareOnReorder()) { 518 + should_compare = true; 519 + } 520 + if (is_move && this.getCompareOnMove()) { 521 + should_compare = true; 522 + } 523 + } 524 + 525 + if (should_compare) { 505 526 cur_target = target_list._getOrderedTarget(this, this._dragging); 506 527 } else { 507 528 cur_target = target_list._getCurrentTarget(p);