1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-23 21:18:19 +01:00

(stable) Promote 2019 Week 11

This commit is contained in:
epriestley 2019-03-16 13:54:52 -07:00
commit c767b045b2
58 changed files with 2325 additions and 1242 deletions

View file

@ -10,13 +10,13 @@ return array(
'conpherence.pkg.css' => '3c8a0668',
'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => '34ce1741',
'core.pkg.js' => '2cda17a4',
'differential.pkg.css' => '1755a478',
'core.pkg.js' => 'f9c2509b',
'differential.pkg.css' => '8d8360fb',
'differential.pkg.js' => '67e02996',
'diffusion.pkg.css' => '42c75c37',
'diffusion.pkg.js' => '91192d85',
'maniphest.pkg.css' => '35995d6d',
'maniphest.pkg.js' => '286955ae',
'maniphest.pkg.js' => 'c9308721',
'rsrc/audio/basic/alert.mp3' => '17889334',
'rsrc/audio/basic/bing.mp3' => 'a817a0c3',
'rsrc/audio/basic/pock.mp3' => '0fa843d0',
@ -61,7 +61,7 @@ return array(
'rsrc/css/application/dashboard/dashboard.css' => '4267d6c6',
'rsrc/css/application/diff/inline-comment-summary.css' => '81eb368d',
'rsrc/css/application/differential/add-comment.css' => '7e5900d9',
'rsrc/css/application/differential/changeset-view.css' => '4193eeff',
'rsrc/css/application/differential/changeset-view.css' => 'bde53589',
'rsrc/css/application/differential/core.css' => '7300a73e',
'rsrc/css/application/differential/phui-inline-comment.css' => '48acce5b',
'rsrc/css/application/differential/revision-comment.css' => '7dbc8d1d',
@ -177,8 +177,8 @@ return array(
'rsrc/css/phui/phui-two-column-view.css' => '01e6991e',
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'e86de308',
'rsrc/css/phui/workboards/phui-workboard.css' => '74fc9d98',
'rsrc/css/phui/workboards/phui-workcard.css' => '8c536f90',
'rsrc/css/phui/workboards/phui-workpanel.css' => 'bd546a49',
'rsrc/css/phui/workboards/phui-workcard.css' => '9e9eb0df',
'rsrc/css/phui/workboards/phui-workpanel.css' => 'c5b408ad',
'rsrc/css/sprite-login.css' => '18b368a6',
'rsrc/css/sprite-tokens.css' => 'f1896dc5',
'rsrc/css/syntax/syntax-default.css' => '055fc231',
@ -395,10 +395,9 @@ return array(
'rsrc/js/application/herald/HeraldRuleEditor.js' => '27daef73',
'rsrc/js/application/herald/PathTypeahead.js' => 'ad486db3',
'rsrc/js/application/herald/herald-rule-editor.js' => '0922e81d',
'rsrc/js/application/maniphest/behavior-batch-selector.js' => 'cffd39b4',
'rsrc/js/application/maniphest/behavior-batch-selector.js' => '139ef688',
'rsrc/js/application/maniphest/behavior-line-chart.js' => 'c8147a20',
'rsrc/js/application/maniphest/behavior-list-edit.js' => 'c687e867',
'rsrc/js/application/maniphest/behavior-subpriorityeditor.js' => '8400307c',
'rsrc/js/application/owners/OwnersPathEditor.js' => '2a8b62d9',
'rsrc/js/application/owners/owners-path-editor.js' => 'ff688a7a',
'rsrc/js/application/passphrase/passphrase-credential-control.js' => '48fe33d0',
@ -409,11 +408,15 @@ return array(
'rsrc/js/application/phortune/phortune-credit-card-form.js' => 'd12d214f',
'rsrc/js/application/policy/behavior-policy-control.js' => '0eaa33a9',
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '9347f172',
'rsrc/js/application/projects/WorkboardBoard.js' => '45d0b2b1',
'rsrc/js/application/projects/WorkboardCard.js' => '9a513421',
'rsrc/js/application/projects/WorkboardColumn.js' => '8573dc1b',
'rsrc/js/application/projects/WorkboardBoard.js' => '9d59f098',
'rsrc/js/application/projects/WorkboardCard.js' => '0392a5d8',
'rsrc/js/application/projects/WorkboardCardTemplate.js' => '2a61f8d4',
'rsrc/js/application/projects/WorkboardColumn.js' => 'ec5c5ce0',
'rsrc/js/application/projects/WorkboardController.js' => '42c7a5a7',
'rsrc/js/application/projects/behavior-project-boards.js' => '05c74d65',
'rsrc/js/application/projects/WorkboardHeader.js' => '111bfd2d',
'rsrc/js/application/projects/WorkboardHeaderTemplate.js' => 'b65351bd',
'rsrc/js/application/projects/WorkboardOrderTemplate.js' => '03e8891f',
'rsrc/js/application/projects/behavior-project-boards.js' => '412af9d4',
'rsrc/js/application/projects/behavior-project-create.js' => '34c53422',
'rsrc/js/application/projects/behavior-reorder-columns.js' => '8ac32fd9',
'rsrc/js/application/releeph/releeph-preview-branch.js' => '75184d68',
@ -434,7 +437,7 @@ return array(
'rsrc/js/application/uiexample/notification-example.js' => '29819b75',
'rsrc/js/core/Busy.js' => '5202e831',
'rsrc/js/core/DragAndDropFileUpload.js' => '4370900d',
'rsrc/js/core/DraggableList.js' => '3c6bd549',
'rsrc/js/core/DraggableList.js' => '8bc7d797',
'rsrc/js/core/Favicon.js' => '7930776a',
'rsrc/js/core/FileUpload.js' => 'ab85e184',
'rsrc/js/core/Hovercard.js' => '074f0783',
@ -540,7 +543,7 @@ return array(
'conpherence-thread-manager' => 'aec8e38c',
'conpherence-transaction-css' => '3a3f5e7e',
'd3' => 'd67475f5',
'differential-changeset-view-css' => '4193eeff',
'differential-changeset-view-css' => 'bde53589',
'differential-core-view-css' => '7300a73e',
'differential-revision-add-comment-css' => '7e5900d9',
'differential-revision-comment-css' => '7dbc8d1d',
@ -617,9 +620,8 @@ return array(
'javelin-behavior-lightbox-attachments' => 'c7e748bf',
'javelin-behavior-line-chart' => 'c8147a20',
'javelin-behavior-linked-container' => '74446546',
'javelin-behavior-maniphest-batch-selector' => 'cffd39b4',
'javelin-behavior-maniphest-batch-selector' => '139ef688',
'javelin-behavior-maniphest-list-editor' => 'c687e867',
'javelin-behavior-maniphest-subpriority-editor' => '8400307c',
'javelin-behavior-owners-path-editor' => 'ff688a7a',
'javelin-behavior-passphrase-credential-control' => '48fe33d0',
'javelin-behavior-phabricator-active-nav' => '7353f43d',
@ -655,7 +657,7 @@ return array(
'javelin-behavior-phuix-example' => 'c2c500a7',
'javelin-behavior-policy-control' => '0eaa33a9',
'javelin-behavior-policy-rule-editor' => '9347f172',
'javelin-behavior-project-boards' => '05c74d65',
'javelin-behavior-project-boards' => '412af9d4',
'javelin-behavior-project-create' => '34c53422',
'javelin-behavior-quicksand-blacklist' => '5a6f6a06',
'javelin-behavior-read-only-warning' => 'b9109f8f',
@ -727,10 +729,14 @@ return array(
'javelin-view-renderer' => '9aae2b66',
'javelin-view-visitor' => '308f9fe4',
'javelin-websocket' => 'fdc13e4e',
'javelin-workboard-board' => '45d0b2b1',
'javelin-workboard-card' => '9a513421',
'javelin-workboard-column' => '8573dc1b',
'javelin-workboard-board' => '9d59f098',
'javelin-workboard-card' => '0392a5d8',
'javelin-workboard-card-template' => '2a61f8d4',
'javelin-workboard-column' => 'ec5c5ce0',
'javelin-workboard-controller' => '42c7a5a7',
'javelin-workboard-header' => '111bfd2d',
'javelin-workboard-header-template' => 'b65351bd',
'javelin-workboard-order-template' => '03e8891f',
'javelin-workflow' => '958e9045',
'maniphest-report-css' => '3d53188b',
'maniphest-task-edit-css' => '272daa84',
@ -755,7 +761,7 @@ return array(
'phabricator-diff-changeset-list' => '04023d82',
'phabricator-diff-inline' => 'a4a14a94',
'phabricator-drag-and-drop-file-upload' => '4370900d',
'phabricator-draggable-list' => '3c6bd549',
'phabricator-draggable-list' => '8bc7d797',
'phabricator-fatal-config-template-css' => '20babf50',
'phabricator-favicon' => '7930776a',
'phabricator-feed-css' => 'd8b6e3f8',
@ -853,8 +859,8 @@ return array(
'phui-two-column-view-css' => '01e6991e',
'phui-workboard-color-css' => 'e86de308',
'phui-workboard-view-css' => '74fc9d98',
'phui-workcard-view-css' => '8c536f90',
'phui-workpanel-view-css' => 'bd546a49',
'phui-workcard-view-css' => '9e9eb0df',
'phui-workpanel-view-css' => 'c5b408ad',
'phuix-action-list-view' => 'c68f183f',
'phuix-action-view' => 'aaa08f3b',
'phuix-autocomplete' => '8f139ef0',
@ -905,6 +911,12 @@ return array(
'javelin-uri',
'javelin-util',
),
'0392a5d8' => array(
'javelin-install',
),
'03e8891f' => array(
'javelin-install',
),
'04023d82' => array(
'javelin-install',
'phuix-button-view',
@ -915,15 +927,6 @@ return array(
'javelin-dom',
'javelin-workflow',
),
'05c74d65' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'javelin-workboard-controller',
),
'05d290ef' => array(
'javelin-install',
'javelin-util',
@ -998,11 +1001,20 @@ return array(
'javelin-workflow',
'phuix-icon-view',
),
'111bfd2d' => array(
'javelin-install',
),
'1325b731' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-keyboard-shortcut',
),
'139ef688' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
),
'1c850a26' => array(
'javelin-install',
'javelin-util',
@ -1107,6 +1119,9 @@ return array(
'javelin-stratcom',
'javelin-behavior',
),
'2a61f8d4' => array(
'javelin-install',
),
'2a8b62d9' => array(
'multirow-row-manager',
'javelin-install',
@ -1192,14 +1207,6 @@ return array(
'javelin-behavior',
'phabricator-prefab',
),
'3c6bd549' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
'javelin-vector',
'javelin-magical-init',
),
'3dc5ad43' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1220,8 +1227,14 @@ return array(
'javelin-behavior',
'javelin-uri',
),
'4193eeff' => array(
'phui-inline-comment-view-css',
'412af9d4' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'javelin-workboard-controller',
),
'4234f572' => array(
'syntax-default-css',
@ -1256,15 +1269,6 @@ return array(
'43bc9360' => array(
'javelin-install',
),
'45d0b2b1' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
'javelin-workboard-column',
),
'46116c01' => array(
'javelin-request',
'javelin-behavior',
@ -1558,17 +1562,6 @@ return array(
'javelin-dom',
'javelin-vector',
),
'8400307c' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
),
'8573dc1b' => array(
'javelin-install',
'javelin-workboard-card',
),
'87428eb2' => array(
'javelin-behavior',
'javelin-diffusion-locate-file-source',
@ -1600,6 +1593,14 @@ return array(
'javelin-dom',
'javelin-typeahead-normalizer',
),
'8bc7d797' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
'javelin-vector',
'javelin-magical-init',
),
'8c2ed2bf' => array(
'javelin-behavior',
'javelin-dom',
@ -1707,9 +1708,6 @@ return array(
'javelin-dom',
'javelin-router',
),
'9a513421' => array(
'javelin-install',
),
'9aae2b66' => array(
'javelin-install',
'javelin-util',
@ -1727,6 +1725,18 @@ return array(
'javelin-uri',
'phabricator-textareautils',
),
'9d59f098' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
'javelin-workboard-column',
'javelin-workboard-header-template',
'javelin-workboard-card-template',
'javelin-workboard-order-template',
),
'9f081f05' => array(
'javelin-behavior',
'javelin-dom',
@ -1875,6 +1885,9 @@ return array(
'javelin-stratcom',
'javelin-dom',
),
'b65351bd' => array(
'javelin-install',
),
'b7b73831' => array(
'javelin-behavior',
'javelin-dom',
@ -1893,9 +1906,6 @@ return array(
'javelin-uri',
'phabricator-notification',
),
'bd546a49' => array(
'phui-workcard-view-css',
),
'bdce4d78' => array(
'javelin-install',
'javelin-util',
@ -1903,6 +1913,9 @@ return array(
'javelin-vector',
'javelin-stratcom',
),
'bde53589' => array(
'phui-inline-comment-view-css',
),
'c03f2fb4' => array(
'javelin-install',
),
@ -1923,6 +1936,9 @@ return array(
'phabricator-phtize',
'javelin-dom',
),
'c5b408ad' => array(
'phui-workcard-view-css',
),
'c687e867' => array(
'javelin-behavior',
'javelin-dom',
@ -1967,12 +1983,6 @@ return array(
'javelin-dom',
'javelin-stratcom',
),
'cffd39b4' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
),
'd0a85a85' => array(
'javelin-dom',
'javelin-util',
@ -2061,6 +2071,11 @@ return array(
'ec4e31c0' => array(
'phui-timeline-view-css',
),
'ec5c5ce0' => array(
'javelin-install',
'javelin-workboard-card',
'javelin-workboard-header',
),
'ee77366f' => array(
'aphront-dialog-view-css',
),
@ -2350,7 +2365,6 @@ return array(
),
'maniphest.pkg.js' => array(
'javelin-behavior-maniphest-batch-selector',
'javelin-behavior-maniphest-subpriority-editor',
'javelin-behavior-maniphest-list-editor',
),
),

View file

@ -218,7 +218,6 @@ return array(
),
'maniphest.pkg.js' => array(
'javelin-behavior-maniphest-batch-selector',
'javelin-behavior-maniphest-subpriority-editor',
'javelin-behavior-maniphest-list-editor',
),
);

View file

@ -1709,7 +1709,6 @@ phutil_register_library_map(array(
'ManiphestStatusEmailCommand' => 'applications/maniphest/command/ManiphestStatusEmailCommand.php',
'ManiphestStatusSearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestStatusSearchConduitAPIMethod.php',
'ManiphestStatusesConfigType' => 'applications/maniphest/config/ManiphestStatusesConfigType.php',
'ManiphestSubpriorityController' => 'applications/maniphest/controller/ManiphestSubpriorityController.php',
'ManiphestSubtypesConfigType' => 'applications/maniphest/config/ManiphestSubtypesConfigType.php',
'ManiphestTask' => 'applications/maniphest/storage/ManiphestTask.php',
'ManiphestTaskAssignHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php',
@ -1783,7 +1782,6 @@ phutil_register_library_map(array(
'ManiphestTaskSubpriorityTransaction' => 'applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php',
'ManiphestTaskSubtaskController' => 'applications/maniphest/controller/ManiphestTaskSubtaskController.php',
'ManiphestTaskSubtypeDatasource' => 'applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php',
'ManiphestTaskTestCase' => 'applications/maniphest/__tests__/ManiphestTaskTestCase.php',
'ManiphestTaskTitleHeraldField' => 'applications/maniphest/herald/ManiphestTaskTitleHeraldField.php',
'ManiphestTaskTitleTransaction' => 'applications/maniphest/xaction/ManiphestTaskTitleTransaction.php',
'ManiphestTaskTransactionType' => 'applications/maniphest/xaction/ManiphestTaskTransactionType.php',
@ -3466,6 +3464,7 @@ phutil_register_library_map(array(
'PhabricatorMacroTransactionType' => 'applications/macro/xaction/PhabricatorMacroTransactionType.php',
'PhabricatorMacroViewController' => 'applications/macro/controller/PhabricatorMacroViewController.php',
'PhabricatorMailAdapter' => 'applications/metamta/adapter/PhabricatorMailAdapter.php',
'PhabricatorMailAdapterTestCase' => 'applications/metamta/adapter/__tests__/PhabricatorMailAdapterTestCase.php',
'PhabricatorMailAmazonSESAdapter' => 'applications/metamta/adapter/PhabricatorMailAmazonSESAdapter.php',
'PhabricatorMailAmazonSNSAdapter' => 'applications/metamta/adapter/PhabricatorMailAmazonSNSAdapter.php',
'PhabricatorMailAttachment' => 'applications/metamta/message/PhabricatorMailAttachment.php',
@ -4052,14 +4051,24 @@ phutil_register_library_map(array(
'PhabricatorProjectColorTransaction' => 'applications/project/xaction/PhabricatorProjectColorTransaction.php',
'PhabricatorProjectColorsConfigType' => 'applications/project/config/PhabricatorProjectColorsConfigType.php',
'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php',
'PhabricatorProjectColumnAuthorOrder' => 'applications/project/order/PhabricatorProjectColumnAuthorOrder.php',
'PhabricatorProjectColumnCreatedOrder' => 'applications/project/order/PhabricatorProjectColumnCreatedOrder.php',
'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php',
'PhabricatorProjectColumnEditController' => 'applications/project/controller/PhabricatorProjectColumnEditController.php',
'PhabricatorProjectColumnHeader' => 'applications/project/order/PhabricatorProjectColumnHeader.php',
'PhabricatorProjectColumnHideController' => 'applications/project/controller/PhabricatorProjectColumnHideController.php',
'PhabricatorProjectColumnNaturalOrder' => 'applications/project/order/PhabricatorProjectColumnNaturalOrder.php',
'PhabricatorProjectColumnOrder' => 'applications/project/order/PhabricatorProjectColumnOrder.php',
'PhabricatorProjectColumnOwnerOrder' => 'applications/project/order/PhabricatorProjectColumnOwnerOrder.php',
'PhabricatorProjectColumnPHIDType' => 'applications/project/phid/PhabricatorProjectColumnPHIDType.php',
'PhabricatorProjectColumnPointsOrder' => 'applications/project/order/PhabricatorProjectColumnPointsOrder.php',
'PhabricatorProjectColumnPosition' => 'applications/project/storage/PhabricatorProjectColumnPosition.php',
'PhabricatorProjectColumnPositionQuery' => 'applications/project/query/PhabricatorProjectColumnPositionQuery.php',
'PhabricatorProjectColumnPriorityOrder' => 'applications/project/order/PhabricatorProjectColumnPriorityOrder.php',
'PhabricatorProjectColumnQuery' => 'applications/project/query/PhabricatorProjectColumnQuery.php',
'PhabricatorProjectColumnSearchEngine' => 'applications/project/query/PhabricatorProjectColumnSearchEngine.php',
'PhabricatorProjectColumnStatusOrder' => 'applications/project/order/PhabricatorProjectColumnStatusOrder.php',
'PhabricatorProjectColumnTitleOrder' => 'applications/project/order/PhabricatorProjectColumnTitleOrder.php',
'PhabricatorProjectColumnTransaction' => 'applications/project/storage/PhabricatorProjectColumnTransaction.php',
'PhabricatorProjectColumnTransactionEditor' => 'applications/project/editor/PhabricatorProjectColumnTransactionEditor.php',
'PhabricatorProjectColumnTransactionQuery' => 'applications/project/query/PhabricatorProjectColumnTransactionQuery.php',
@ -7406,7 +7415,6 @@ phutil_register_library_map(array(
'ManiphestStatusEmailCommand' => 'ManiphestEmailCommand',
'ManiphestStatusSearchConduitAPIMethod' => 'ManiphestConduitAPIMethod',
'ManiphestStatusesConfigType' => 'PhabricatorJSONConfigType',
'ManiphestSubpriorityController' => 'ManiphestController',
'ManiphestSubtypesConfigType' => 'PhabricatorJSONConfigType',
'ManiphestTask' => array(
'ManiphestDAO',
@ -7503,7 +7511,6 @@ phutil_register_library_map(array(
'ManiphestTaskSubpriorityTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskSubtaskController' => 'ManiphestController',
'ManiphestTaskSubtypeDatasource' => 'PhabricatorTypeaheadDatasource',
'ManiphestTaskTestCase' => 'PhabricatorTestCase',
'ManiphestTaskTitleHeraldField' => 'ManiphestTaskHeraldField',
'ManiphestTaskTitleTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskTransactionType' => 'PhabricatorModularTransactionType',
@ -9419,6 +9426,7 @@ phutil_register_library_map(array(
'PhabricatorMacroTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorMacroViewController' => 'PhabricatorMacroController',
'PhabricatorMailAdapter' => 'Phobject',
'PhabricatorMailAdapterTestCase' => 'PhabricatorTestCase',
'PhabricatorMailAmazonSESAdapter' => 'PhabricatorMailAdapter',
'PhabricatorMailAmazonSNSAdapter' => 'PhabricatorMailAdapter',
'PhabricatorMailAttachment' => 'Phobject',
@ -10134,17 +10142,27 @@ phutil_register_library_map(array(
'PhabricatorExtendedPolicyInterface',
'PhabricatorConduitResultInterface',
),
'PhabricatorProjectColumnAuthorOrder' => 'PhabricatorProjectColumnOrder',
'PhabricatorProjectColumnCreatedOrder' => 'PhabricatorProjectColumnOrder',
'PhabricatorProjectColumnDetailController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectColumnEditController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectColumnHeader' => 'Phobject',
'PhabricatorProjectColumnHideController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectColumnNaturalOrder' => 'PhabricatorProjectColumnOrder',
'PhabricatorProjectColumnOrder' => 'Phobject',
'PhabricatorProjectColumnOwnerOrder' => 'PhabricatorProjectColumnOrder',
'PhabricatorProjectColumnPHIDType' => 'PhabricatorPHIDType',
'PhabricatorProjectColumnPointsOrder' => 'PhabricatorProjectColumnOrder',
'PhabricatorProjectColumnPosition' => array(
'PhabricatorProjectDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorProjectColumnPositionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorProjectColumnPriorityOrder' => 'PhabricatorProjectColumnOrder',
'PhabricatorProjectColumnQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorProjectColumnSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorProjectColumnStatusOrder' => 'PhabricatorProjectColumnOrder',
'PhabricatorProjectColumnTitleOrder' => 'PhabricatorProjectColumnOrder',
'PhabricatorProjectColumnTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorProjectColumnTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorProjectColumnTransactionQuery' => 'PhabricatorApplicationTransactionQuery',

View file

@ -401,7 +401,7 @@ final class DrydockLandRepositoryOperation
'body' => pht(
'When this diff was generated, the server was running an older '.
'version of Phabricator which did not support staging areas for '.
'this version control system, so the chagne was not pushed to '.
'this version control system, so the change was not pushed to '.
'staging. Changes must be pushed to staging before they can be '.
'landed from the web.'),
);

View file

@ -1,255 +0,0 @@
<?php
final class ManiphestTaskTestCase extends PhabricatorTestCase {
protected function getPhabricatorTestCaseConfiguration() {
return array(
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
);
}
public function testTaskReordering() {
$viewer = $this->generateNewTestUser();
$t1 = $this->newTask($viewer, pht('Task 1'));
$t2 = $this->newTask($viewer, pht('Task 2'));
$t3 = $this->newTask($viewer, pht('Task 3'));
$auto_base = min(mpull(array($t1, $t2, $t3), 'getID'));
// Default order should be reverse creation.
$tasks = $this->loadTasks($viewer, $auto_base);
$t1 = $tasks[1];
$t2 = $tasks[2];
$t3 = $tasks[3];
$this->assertEqual(array(3, 2, 1), array_keys($tasks));
// Move T3 to the middle.
$this->moveTask($viewer, $t3, $t2, true);
$tasks = $this->loadTasks($viewer, $auto_base);
$t1 = $tasks[1];
$t2 = $tasks[2];
$t3 = $tasks[3];
$this->assertEqual(array(2, 3, 1), array_keys($tasks));
// Move T3 to the end.
$this->moveTask($viewer, $t3, $t1, true);
$tasks = $this->loadTasks($viewer, $auto_base);
$t1 = $tasks[1];
$t2 = $tasks[2];
$t3 = $tasks[3];
$this->assertEqual(array(2, 1, 3), array_keys($tasks));
// Repeat the move above, there should be no overall change in order.
$this->moveTask($viewer, $t3, $t1, true);
$tasks = $this->loadTasks($viewer, $auto_base);
$t1 = $tasks[1];
$t2 = $tasks[2];
$t3 = $tasks[3];
$this->assertEqual(array(2, 1, 3), array_keys($tasks));
// Move T3 to the first slot in the priority.
$this->movePriority($viewer, $t3, $t3->getPriority(), false);
$tasks = $this->loadTasks($viewer, $auto_base);
$t1 = $tasks[1];
$t2 = $tasks[2];
$t3 = $tasks[3];
$this->assertEqual(array(3, 2, 1), array_keys($tasks));
// Move T3 to the last slot in the priority.
$this->movePriority($viewer, $t3, $t3->getPriority(), true);
$tasks = $this->loadTasks($viewer, $auto_base);
$t1 = $tasks[1];
$t2 = $tasks[2];
$t3 = $tasks[3];
$this->assertEqual(array(2, 1, 3), array_keys($tasks));
// Move T3 before T2.
$this->moveTask($viewer, $t3, $t2, false);
$tasks = $this->loadTasks($viewer, $auto_base);
$t1 = $tasks[1];
$t2 = $tasks[2];
$t3 = $tasks[3];
$this->assertEqual(array(3, 2, 1), array_keys($tasks));
// Move T3 before T1.
$this->moveTask($viewer, $t3, $t1, false);
$tasks = $this->loadTasks($viewer, $auto_base);
$t1 = $tasks[1];
$t2 = $tasks[2];
$t3 = $tasks[3];
$this->assertEqual(array(2, 3, 1), array_keys($tasks));
}
public function testTaskAdjacentBlocks() {
$viewer = $this->generateNewTestUser();
$t = array();
for ($ii = 1; $ii < 10; $ii++) {
$t[$ii] = $this->newTask($viewer, pht('Task Block %d', $ii));
// This makes sure this test remains meaningful if we begin assigning
// subpriorities when tasks are created.
$t[$ii]->setSubpriority(0)->save();
}
$auto_base = min(mpull($t, 'getID'));
$tasks = $this->loadTasks($viewer, $auto_base);
$this->assertEqual(
array(9, 8, 7, 6, 5, 4, 3, 2, 1),
array_keys($tasks));
$this->moveTask($viewer, $t[9], $t[8], true);
$tasks = $this->loadTasks($viewer, $auto_base);
$this->assertEqual(
array(8, 9, 7, 6, 5, 4, 3, 2, 1),
array_keys($tasks));
// When there is a large block of tasks which all have the same
// subpriority, they should be assigned distinct subpriorities as a
// side effect of having a task moved into the block.
$subpri = mpull($tasks, 'getSubpriority');
$unique_subpri = array_unique($subpri);
$this->assertEqual(
9,
count($subpri),
pht('Expected subpriorities to be distributed.'));
// Move task 9 to the end.
$this->moveTask($viewer, $t[9], $t[1], true);
$tasks = $this->loadTasks($viewer, $auto_base);
$this->assertEqual(
array(8, 7, 6, 5, 4, 3, 2, 1, 9),
array_keys($tasks));
// Move task 3 to the beginning.
$this->moveTask($viewer, $t[3], $t[8], false);
$tasks = $this->loadTasks($viewer, $auto_base);
$this->assertEqual(
array(3, 8, 7, 6, 5, 4, 2, 1, 9),
array_keys($tasks));
// Move task 3 to the end.
$this->moveTask($viewer, $t[3], $t[9], true);
$tasks = $this->loadTasks($viewer, $auto_base);
$this->assertEqual(
array(8, 7, 6, 5, 4, 2, 1, 9, 3),
array_keys($tasks));
// Move task 5 to before task 4 (this is its current position).
$this->moveTask($viewer, $t[5], $t[4], false);
$tasks = $this->loadTasks($viewer, $auto_base);
$this->assertEqual(
array(8, 7, 6, 5, 4, 2, 1, 9, 3),
array_keys($tasks));
}
private function newTask(PhabricatorUser $viewer, $title) {
$task = ManiphestTask::initializeNewTask($viewer);
$xactions = array();
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE)
->setNewValue($title);
$this->applyTaskTransactions($viewer, $task, $xactions);
return $task;
}
private function loadTasks(PhabricatorUser $viewer, $auto_base) {
$tasks = id(new ManiphestTaskQuery())
->setViewer($viewer)
->setOrder(ManiphestTaskQuery::ORDER_PRIORITY)
->execute();
// NOTE: AUTO_INCREMENT changes survive ROLLBACK, and we can't throw them
// away without committing the current transaction, so we adjust the
// apparent task IDs as though the first one had been ID 1. This makes the
// tests a little easier to understand.
$map = array();
foreach ($tasks as $task) {
$map[($task->getID() - $auto_base) + 1] = $task;
}
return $map;
}
private function moveTask(PhabricatorUser $viewer, $src, $dst, $is_after) {
list($pri, $sub) = ManiphestTransactionEditor::getAdjacentSubpriority(
$dst,
$is_after);
$keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap();
$keyword = head($keyword_map[$pri]);
$xactions = array();
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
->setNewValue($keyword);
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE)
->setNewValue($sub);
return $this->applyTaskTransactions($viewer, $src, $xactions);
}
private function movePriority(
PhabricatorUser $viewer,
$src,
$target_priority,
$is_end) {
list($pri, $sub) = ManiphestTransactionEditor::getEdgeSubpriority(
$target_priority,
$is_end);
$keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap();
$keyword = head($keyword_map[$pri]);
$xactions = array();
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
->setNewValue($keyword);
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE)
->setNewValue($sub);
return $this->applyTaskTransactions($viewer, $src, $xactions);
}
private function applyTaskTransactions(
PhabricatorUser $viewer,
ManiphestTask $task,
array $xactions) {
$content_source = $this->newContentSource();
$editor = id(new ManiphestTransactionEditor())
->setActor($viewer)
->setContentSource($content_source)
->setContinueOnNoEffect(true)
->applyTransactions($task, $xactions);
return $task;
}
}

View file

@ -54,7 +54,6 @@ final class PhabricatorManiphestApplication extends PhabricatorApplication {
=> 'ManiphestTaskEditController',
'subtask/(?P<id>[1-9]\d*)/' => 'ManiphestTaskSubtaskController',
),
'subpriority/' => 'ManiphestSubpriorityController',
'graph/(?P<id>[1-9]\d*)/' => 'ManiphestTaskGraphController',
),
);

View file

@ -37,30 +37,6 @@ abstract class ManiphestController extends PhabricatorController {
return $crumbs;
}
public function renderSingleTask(ManiphestTask $task) {
$request = $this->getRequest();
$user = $request->getUser();
$phids = $task->getProjectPHIDs();
if ($task->getOwnerPHID()) {
$phids[] = $task->getOwnerPHID();
}
$handles = id(new PhabricatorHandleQuery())
->setViewer($user)
->withPHIDs($phids)
->execute();
$view = id(new ManiphestTaskListView())
->setUser($user)
->setShowSubpriorityControls(!$request->getStr('ungrippable'))
->setShowBatchControls(true)
->setHandles($handles)
->setTasks(array($task));
return $view;
}
final protected function newTaskGraphDropdownMenu(
ManiphestTask $task,
$has_parents,

View file

@ -1,70 +0,0 @@
<?php
final class ManiphestSubpriorityController extends ManiphestController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
if (!$request->validateCSRF()) {
return new Aphront403Response();
}
$task = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withIDs(array($request->getInt('task')))
->needProjectPHIDs(true)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$task) {
return new Aphront404Response();
}
if ($request->getInt('after')) {
$after_task = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withIDs(array($request->getInt('after')))
->executeOne();
if (!$after_task) {
return new Aphront404Response();
}
list($pri, $sub) = ManiphestTransactionEditor::getAdjacentSubpriority(
$after_task,
$is_after = true);
} else {
list($pri, $sub) = ManiphestTransactionEditor::getEdgeSubpriority(
$request->getInt('priority'),
$is_end = false);
}
$keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap();
$keyword = head(idx($keyword_map, $pri));
$xactions = array();
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
->setNewValue($keyword);
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE)
->setNewValue($sub);
$editor = id(new ManiphestTransactionEditor())
->setActor($viewer)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request);
$editor->applyTransactions($task, $xactions);
return id(new AphrontAjaxResponse())->setContent(
array(
'tasks' => $this->renderSingleTask($task),
));
}
}

View file

@ -5,7 +5,6 @@ final class ManiphestTaskEditController extends ManiphestController {
public function handleRequest(AphrontRequest $request) {
return id(new ManiphestEditEngine())
->setController($this)
->addContextParameter('ungrippable')
->addContextParameter('responseType')
->addContextParameter('columnPHID')
->addContextParameter('order')

View file

@ -379,7 +379,10 @@ EODOCS
$object,
array $xactions) {
if ($request->isAjax()) {
$response_type = $request->getStr('responseType');
$is_card = ($response_type === 'card');
if ($is_card) {
// Reload the task to make sure we pick up the final task state.
$viewer = $this->getViewer();
$task = id(new ManiphestTaskQuery())
@ -389,29 +392,12 @@ EODOCS
->needProjectPHIDs(true)
->executeOne();
switch ($request->getStr('responseType')) {
case 'card':
return $this->buildCardResponse($task);
default:
return $this->buildListResponse($task);
}
return $this->buildCardResponse($task);
}
return parent::newEditResponse($request, $object, $xactions);
}
private function buildListResponse(ManiphestTask $task) {
$controller = $this->getController();
$payload = array(
'tasks' => $controller->renderSingleTask($task),
'data' => array(),
);
return id(new AphrontAjaxResponse())->setContent($payload);
}
private function buildCardResponse(ManiphestTask $task) {
$controller = $this->getController();
$request = $controller->getRequest();
@ -435,12 +421,26 @@ EODOCS
$board_phid = $column->getProjectPHID();
$object_phid = $task->getPHID();
return id(new PhabricatorBoardResponseEngine())
$order = $request->getStr('order');
if ($order) {
$ordering = PhabricatorProjectColumnOrder::getOrderByKey($order);
$ordering = id(clone $ordering)
->setViewer($viewer);
} else {
$ordering = null;
}
$engine = id(new PhabricatorBoardResponseEngine())
->setViewer($viewer)
->setBoardPHID($board_phid)
->setObjectPHID($object_phid)
->setVisiblePHIDs($visible_phids)
->buildResponse();
->setVisiblePHIDs($visible_phids);
if ($ordering) {
$engine->setOrdering($ordering);
}
return $engine->buildResponse();
}
private function getColumnMap(ManiphestTask $task) {

View file

@ -297,251 +297,6 @@ final class ManiphestTransactionEditor
return $copy;
}
/**
* Get priorities for moving a task to a new priority.
*/
public static function getEdgeSubpriority(
$priority,
$is_end) {
$query = id(new ManiphestTaskQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPriorities(array($priority))
->setLimit(1);
if ($is_end) {
$query->setOrderVector(array('-priority', '-subpriority', '-id'));
} else {
$query->setOrderVector(array('priority', 'subpriority', 'id'));
}
$result = $query->executeOne();
$step = (double)(2 << 32);
if ($result) {
$base = $result->getSubpriority();
if ($is_end) {
$sub = ($base - $step);
} else {
$sub = ($base + $step);
}
} else {
$sub = 0;
}
return array($priority, $sub);
}
/**
* Get priorities for moving a task before or after another task.
*/
public static function getAdjacentSubpriority(
ManiphestTask $dst,
$is_after) {
$query = id(new ManiphestTaskQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->setOrder(ManiphestTaskQuery::ORDER_PRIORITY)
->withPriorities(array($dst->getPriority()))
->setLimit(1);
if ($is_after) {
$query->setAfterID($dst->getID());
} else {
$query->setBeforeID($dst->getID());
}
$adjacent = $query->executeOne();
$base = $dst->getSubpriority();
$step = (double)(2 << 32);
// If we find an adjacent task, we average the two subpriorities and
// return the result.
if ($adjacent) {
$epsilon = 1.0;
// If the adjacent task has a subpriority that is identical or very
// close to the task we're looking at, we're going to spread out all
// the nearby tasks.
$adjacent_sub = $adjacent->getSubpriority();
if ((abs($adjacent_sub - $base) < $epsilon)) {
$base = self::disperseBlock(
$dst,
$epsilon * 2);
if ($is_after) {
$sub = $base - $epsilon;
} else {
$sub = $base + $epsilon;
}
} else {
$sub = ($adjacent_sub + $base) / 2;
}
} else {
// Otherwise, we take a step away from the target's subpriority and
// use that.
if ($is_after) {
$sub = ($base - $step);
} else {
$sub = ($base + $step);
}
}
return array($dst->getPriority(), $sub);
}
/**
* Distribute a cluster of tasks with similar subpriorities.
*/
private static function disperseBlock(
ManiphestTask $task,
$spacing) {
$conn = $task->establishConnection('w');
// Find a block of subpriority space which is, on average, sparse enough
// to hold all the tasks that are inside it with a reasonable level of
// separation between them.
// We'll start by looking near the target task for a range of numbers
// which has more space available than tasks. For example, if the target
// task has subpriority 33 and we want to separate each task by at least 1,
// we might start by looking in the range [23, 43].
// If we find fewer than 20 tasks there, we have room to reassign them
// with the desired level of separation. We space them out, then we're
// done.
// However: if we find more than 20 tasks, we don't have enough room to
// distribute them. We'll widen our search and look in a bigger range,
// maybe [13, 53]. This range has more space, so if we find fewer than
// 40 tasks in this range we can spread them out. If we still find too
// many tasks, we keep widening the search.
$base = $task->getSubpriority();
$scale = 4.0;
while (true) {
$range = ($spacing * $scale) / 2.0;
$min = ($base - $range);
$max = ($base + $range);
$result = queryfx_one(
$conn,
'SELECT COUNT(*) N FROM %T WHERE priority = %d AND
subpriority BETWEEN %f AND %f',
$task->getTableName(),
$task->getPriority(),
$min,
$max);
$count = $result['N'];
if ($count < $scale) {
// We have found a block which we can make sparse enough, so bail and
// continue below with our selection.
break;
}
// This block had too many tasks for its size, so try again with a
// bigger block.
$scale *= 2.0;
}
$rows = queryfx_all(
$conn,
'SELECT id FROM %T WHERE priority = %d AND
subpriority BETWEEN %f AND %f
ORDER BY priority, subpriority, id',
$task->getTableName(),
$task->getPriority(),
$min,
$max);
$task_id = $task->getID();
$result = null;
// NOTE: In strict mode (which we encourage enabling) we can't structure
// this bulk update as an "INSERT ... ON DUPLICATE KEY UPDATE" unless we
// provide default values for ALL of the columns that don't have defaults.
// This is gross, but we may be moving enough rows that individual
// queries are unreasonably slow. An alternate construction which might
// be worth evaluating is to use "CASE". Another approach is to disable
// strict mode for this query.
$default_str = qsprintf($conn, '%s', '');
$default_int = qsprintf($conn, '%d', 0);
$extra_columns = array(
'phid' => $default_str,
'authorPHID' => $default_str,
'status' => $default_str,
'priority' => $default_int,
'title' => $default_str,
'description' => $default_str,
'dateCreated' => $default_int,
'dateModified' => $default_int,
'mailKey' => $default_str,
'viewPolicy' => $default_str,
'editPolicy' => $default_str,
'ownerOrdering' => $default_str,
'spacePHID' => $default_str,
'bridgedObjectPHID' => $default_str,
'properties' => $default_str,
'points' => $default_int,
'subtype' => $default_str,
);
$sql = array();
$offset = 0;
// Often, we'll have more room than we need in the range. Distribute the
// tasks evenly over the whole range so that we're less likely to end up
// with tasks spaced exactly the minimum distance apart, which may
// get shifted again later. We have one fewer space to distribute than we
// have tasks.
$divisor = (double)(count($rows) - 1.0);
if ($divisor > 0) {
$available_distance = (($max - $min) / $divisor);
} else {
$available_distance = 0.0;
}
foreach ($rows as $row) {
$subpriority = $min + ($offset * $available_distance);
// If this is the task that we're spreading out relative to, keep track
// of where it is ending up so we can return the new subpriority.
$id = $row['id'];
if ($id == $task_id) {
$result = $subpriority;
}
$sql[] = qsprintf(
$conn,
'(%d, %LQ, %f)',
$id,
$extra_columns,
$subpriority);
$offset++;
}
foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) {
queryfx(
$conn,
'INSERT INTO %T (id, %LC, subpriority) VALUES %LQ
ON DUPLICATE KEY UPDATE subpriority = VALUES(subpriority)',
$task->getTableName(),
array_keys($extra_columns),
$chunk);
}
return $result;
}
protected function validateAllTransactions(
PhabricatorLiskDAO $object,
array $xactions) {

View file

@ -14,7 +14,6 @@ final class PhabricatorManiphestTaskTestDataGenerator
$author = id(new PhabricatorUser())
->loadOneWhere('phid = %s', $author_phid);
$task = ManiphestTask::initializeNewTask($author)
->setSubPriority($this->generateTaskSubPriority())
->setTitle($this->generateTitle());
$content_source = $this->getLipsumContentSource();
@ -106,10 +105,6 @@ final class PhabricatorManiphestTaskTestDataGenerator
return $keyword;
}
public function generateTaskSubPriority() {
return rand(2 << 16, 2 << 32);
}
public function generateTaskStatus() {
$statuses = array_keys(ManiphestTaskStatus::getTaskStatusMap());
// Make sure 4/5th of all generated Tasks are open

View file

@ -435,13 +435,6 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$this->priorities);
}
if ($this->subpriorities !== null) {
$where[] = qsprintf(
$conn,
'task.subpriority IN (%Lf)',
$this->subpriorities);
}
if ($this->bridgedObjectPHIDs !== null) {
$where[] = qsprintf(
$conn,
@ -844,7 +837,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
public function getBuiltinOrders() {
$orders = array(
'priority' => array(
'vector' => array('priority', 'subpriority', 'id'),
'vector' => array('priority', 'id'),
'name' => pht('Priority'),
'aliases' => array(self::ORDER_PRIORITY),
),
@ -919,11 +912,6 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
'type' => 'string',
'reverse' => true,
),
'subpriority' => array(
'table' => 'task',
'column' => 'subpriority',
'type' => 'float',
),
'updated' => array(
'table' => 'task',
'column' => 'dateModified',
@ -948,7 +936,6 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$map = array(
'id' => $task->getID(),
'priority' => $task->getPriority(),
'subpriority' => $task->getSubpriority(),
'owner' => $task->getOwnerOrdering(),
'status' => $task->getStatus(),
'title' => $task->getTitle(),

View file

@ -366,10 +366,8 @@ final class ManiphestTaskSearchEngine
$viewer = $this->requireViewer();
if ($this->isPanelContext()) {
$can_edit_priority = false;
$can_bulk_edit = false;
} else {
$can_edit_priority = true;
$can_bulk_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$this->getApplication(),
@ -380,7 +378,6 @@ final class ManiphestTaskSearchEngine
->setUser($viewer)
->setTasks($tasks)
->setSavedQuery($saved)
->setCanEditPriority($can_edit_priority)
->setCanBatchEdit($can_bulk_edit)
->setShowBatchControls($this->showBatchControls);

View file

@ -248,16 +248,6 @@ final class ManiphestTask extends ManiphestDAO
return idx($this->properties, 'cover.thumbnailPHID');
}
public function getWorkboardOrderVectors() {
return array(
PhabricatorProjectColumn::ORDER_PRIORITY => array(
(int)-$this->getPriority(),
(double)-$this->getSubpriority(),
(int)-$this->getID(),
),
);
}
public function getPriorityKeyword() {
$priority = $this->getPriority();
@ -269,46 +259,6 @@ final class ManiphestTask extends ManiphestDAO
return ManiphestTaskPriority::UNKNOWN_PRIORITY_KEYWORD;
}
private function comparePriorityTo(ManiphestTask $other) {
$upri = $this->getPriority();
$vpri = $other->getPriority();
if ($upri != $vpri) {
return ($upri - $vpri);
}
$usub = $this->getSubpriority();
$vsub = $other->getSubpriority();
if ($usub != $vsub) {
return ($usub - $vsub);
}
$uid = $this->getID();
$vid = $other->getID();
if ($uid != $vid) {
return ($uid - $vid);
}
return 0;
}
public function isLowerPriorityThan(ManiphestTask $other) {
return ($this->comparePriorityTo($other) < 0);
}
public function isHigherPriorityThan(ManiphestTask $other) {
return ($this->comparePriorityTo($other) > 0);
}
public function getWorkboardProperties() {
return array(
'status' => $this->getStatus(),
'points' => (double)$this->getPoints(),
);
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
@ -541,7 +491,6 @@ final class ManiphestTask extends ManiphestDAO
$priority_value = (int)$this->getPriority();
$priority_info = array(
'value' => $priority_value,
'subpriority' => (double)$this->getSubpriority(),
'name' => ManiphestTaskPriority::getTaskPriorityName($priority_value),
'color' => ManiphestTaskPriority::getTaskPriorityColor($priority_value),
);

View file

@ -5,7 +5,6 @@ final class ManiphestTaskListView extends ManiphestView {
private $tasks;
private $handles;
private $showBatchControls;
private $showSubpriorityControls;
private $noDataString;
public function setTasks(array $tasks) {
@ -25,11 +24,6 @@ final class ManiphestTaskListView extends ManiphestView {
return $this;
}
public function setShowSubpriorityControls($show_subpriority_controls) {
$this->showSubpriorityControls = $show_subpriority_controls;
return $this;
}
public function setNoDataString($text) {
$this->noDataString = $text;
return $this;
@ -102,10 +96,7 @@ final class ManiphestTaskListView extends ManiphestView {
phabricator_datetime($task->getDateModified(), $this->getUser()));
}
if ($this->showSubpriorityControls) {
$item->setGrippable(true);
}
if ($this->showSubpriorityControls || $this->showBatchControls) {
if ($this->showBatchControls) {
$item->addSigil('maniphest-task');
}
@ -134,9 +125,6 @@ final class ManiphestTaskListView extends ManiphestView {
if ($this->showBatchControls) {
$href = new PhutilURI('/maniphest/task/edit/'.$task->getID().'/');
if (!$this->showSubpriorityControls) {
$href->replaceQueryParam('ungrippable', 'true');
}
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-pencil')

View file

@ -4,7 +4,6 @@ final class ManiphestTaskResultListView extends ManiphestView {
private $tasks;
private $savedQuery;
private $canEditPriority;
private $canBatchEdit;
private $showBatchControls;
@ -18,11 +17,6 @@ final class ManiphestTaskResultListView extends ManiphestView {
return $this;
}
public function setCanEditPriority($can_edit_priority) {
$this->canEditPriority = $can_edit_priority;
return $this;
}
public function setCanBatchEdit($can_batch_edit) {
$this->canBatchEdit = $can_batch_edit;
return $this;
@ -54,28 +48,12 @@ final class ManiphestTaskResultListView extends ManiphestView {
$group_parameter,
$handles);
$can_edit_priority = $this->canEditPriority;
$can_drag = ($order_parameter == 'priority') &&
($can_edit_priority) &&
($group_parameter == 'none' || $group_parameter == 'priority');
if (!$viewer->isLoggedIn()) {
// TODO: (T7131) Eventually, we conceivably need to make each task
// draggable individually, since the user may be able to edit some but
// not others.
$can_drag = false;
}
$result = array();
$lists = array();
foreach ($groups as $group => $list) {
$task_list = new ManiphestTaskListView();
$task_list->setShowBatchControls($this->showBatchControls);
if ($can_drag) {
$task_list->setShowSubpriorityControls(true);
}
$task_list->setUser($viewer);
$task_list->setTasks($list);
$task_list->setHandles($handles);
@ -91,14 +69,6 @@ final class ManiphestTaskResultListView extends ManiphestView {
}
if ($can_drag) {
Javelin::initBehavior(
'maniphest-subpriority-editor',
array(
'uri' => '/maniphest/subpriority/',
));
}
return array(
$lists,
$this->showBatchControls ? $this->renderBatchEditor($query) : null,

View file

@ -6,16 +6,17 @@ final class ManiphestTaskSubpriorityTransaction
const TRANSACTIONTYPE = 'subpriority';
public function generateOldValue($object) {
return $object->getSubpriority();
return null;
}
public function applyInternalEffects($object, $value) {
$object->setSubpriority($value);
// This transaction is obsolete, but we're keeping the class around so it
// is hidden from timelines until we destroy the actual transaction data.
throw new PhutilMethodNotImplementedException();
}
public function shouldHide() {
return true;
}
}

View file

@ -137,4 +137,37 @@ abstract class PhabricatorMailAdapter
abstract public function newDefaultOptions();
final protected function guessIfHostSupportsMessageID($config, $host) {
// See T13265. Mailers like "SMTP" and "sendmail" usually allow us to
// set the "Message-ID" header to a value we choose, but we may not be
// able to if the mailer is being used as API glue and the outbound
// pathway ends up routing to a service with an SMTP API that selects
// its own "Message-ID" header, like Amazon SES.
// If users configured a behavior explicitly, use that behavior.
if ($config !== null) {
return $config;
}
// If the server we're connecting to is part of a service that we know
// does not support "Message-ID", guess that we don't support "Message-ID".
if ($host !== null) {
$host_blocklist = array(
'/\.amazonaws\.com\z/',
'/\.postmarkapp\.com\z/',
'/\.sendgrid\.net\z/',
);
$host = phutil_utf8_strtolower($host);
foreach ($host_blocklist as $regexp) {
if (preg_match($regexp, $host)) {
return false;
}
}
}
return true;
}
}

View file

@ -11,10 +11,6 @@ final class PhabricatorMailAmazonSESAdapter
);
}
public function supportsMessageIDHeader() {
return false;
}
protected function validateOptions(array $options) {
PhutilTypeSpec::checkMap(
$options,

View file

@ -11,10 +11,6 @@ final class PhabricatorMailPostmarkAdapter
);
}
public function supportsMessageIDHeader() {
return true;
}
protected function validateOptions(array $options) {
PhutilTypeSpec::checkMap(
$options,

View file

@ -12,7 +12,9 @@ final class PhabricatorMailSMTPAdapter
}
public function supportsMessageIDHeader() {
return true;
return $this->guessIfHostSupportsMessageID(
$this->getOption('message-id'),
$this->getOption('host'));
}
protected function validateOptions(array $options) {
@ -24,6 +26,7 @@ final class PhabricatorMailSMTPAdapter
'user' => 'string|null',
'password' => 'string|null',
'protocol' => 'string|null',
'message-id' => 'bool|null',
));
}
@ -34,6 +37,7 @@ final class PhabricatorMailSMTPAdapter
'user' => null,
'password' => null,
'protocol' => null,
'message-id' => null,
);
}

View file

@ -5,7 +5,6 @@ final class PhabricatorMailSendmailAdapter
const ADAPTERTYPE = 'sendmail';
public function getSupportedMessageTypes() {
return array(
PhabricatorMailEmailMessage::MESSAGETYPE,
@ -13,20 +12,22 @@ final class PhabricatorMailSendmailAdapter
}
public function supportsMessageIDHeader() {
return true;
return $this->guessIfHostSupportsMessageID(
$this->getOption('message-id'),
null);
}
protected function validateOptions(array $options) {
PhutilTypeSpec::checkMap(
$options,
array(
'encoding' => 'string',
'message-id' => 'bool|null',
));
}
public function newDefaultOptions() {
return array(
'encoding' => 'base64',
'message-id' => null,
);
}

View file

@ -0,0 +1,96 @@
<?php
final class PhabricatorMailAdapterTestCase
extends PhabricatorTestCase {
public function testSupportsMessageID() {
$cases = array(
array(
pht('Amazon SES'),
false,
new PhabricatorMailAmazonSESAdapter(),
array(
'access-key' => 'test',
'secret-key' => 'test',
'endpoint' => 'test',
),
),
array(
pht('Mailgun'),
true,
new PhabricatorMailMailgunAdapter(),
array(
'api-key' => 'test',
'domain' => 'test',
'api-hostname' => 'test',
),
),
array(
pht('Sendmail'),
true,
new PhabricatorMailSendmailAdapter(),
array(),
),
array(
pht('Sendmail (Explicit Config)'),
false,
new PhabricatorMailSendmailAdapter(),
array(
'message-id' => false,
),
),
array(
pht('SMTP (Local)'),
true,
new PhabricatorMailSMTPAdapter(),
array(),
),
array(
pht('SMTP (Local + Explicit)'),
false,
new PhabricatorMailSMTPAdapter(),
array(
'message-id' => false,
),
),
array(
pht('SMTP (AWS)'),
false,
new PhabricatorMailSMTPAdapter(),
array(
'host' => 'test.amazonaws.com',
),
),
array(
pht('SMTP (AWS + Explicit)'),
true,
new PhabricatorMailSMTPAdapter(),
array(
'host' => 'test.amazonaws.com',
'message-id' => true,
),
),
);
foreach ($cases as $case) {
list($label, $expect, $mailer, $options) = $case;
$defaults = $mailer->newDefaultOptions();
$mailer->setOptions($options + $defaults);
$actual = $mailer->supportsMessageIDHeader();
$this->assertEqual($expect, $actual, pht('Message-ID: %s', $label));
}
}
}

View file

@ -328,7 +328,7 @@ final class PhabricatorProjectBoardViewController
$columns = null;
$errors = array();
if ($request->isFormPost()) {
if ($request->isFormOrHiSecPost()) {
$move_project_phid = head($request->getArr('moveProjectPHID'));
if (!$move_project_phid) {
$move_project_phid = $request->getStr('moveProjectPHID');
@ -425,7 +425,8 @@ final class PhabricatorProjectBoardViewController
->setActor($viewer)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request);
->setContentSourceFromRequest($request)
->setCancelURI($cancel_uri);
$editor->applyTransactions($move_task, $xactions);
}
@ -522,13 +523,6 @@ final class PhabricatorProjectBoardViewController
$column->getPHID());
$column_tasks = array_select_keys($tasks, $task_phids);
// If we aren't using "natural" order, reorder the column by the original
// query order.
if ($this->sortKey != PhabricatorProjectColumn::ORDER_NATURAL) {
$column_tasks = array_select_keys($column_tasks, array_keys($tasks));
}
$column_phid = $column->getPHID();
$visible_columns[$column_phid] = $column;
@ -621,6 +615,34 @@ final class PhabricatorProjectBoardViewController
$board->addPanel($panel);
}
$order_key = $this->sortKey;
$ordering_map = PhabricatorProjectColumnOrder::getEnabledOrders();
$ordering = id(clone $ordering_map[$order_key])
->setViewer($viewer);
$headers = $ordering->getHeadersForObjects($all_tasks);
$headers = mpull($headers, 'toDictionary');
$vectors = $ordering->getSortVectorsForObjects($all_tasks);
$vector_map = array();
foreach ($vectors as $task_phid => $vector) {
$vector_map[$task_phid][$order_key] = $vector;
}
$header_keys = $ordering->getHeaderKeysForObjects($all_tasks);
$order_maps = array();
$order_maps[] = $ordering->toDictionary();
$properties = array();
foreach ($all_tasks as $task) {
$properties[$task->getPHID()] = array(
'points' => (double)$task->getPoints(),
'status' => $task->getStatus(),
);
}
$behavior_config = array(
'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'),
'uploadURI' => '/file/dropupload/',
@ -630,21 +652,24 @@ final class PhabricatorProjectBoardViewController
'boardPHID' => $project->getPHID(),
'order' => $this->sortKey,
'orders' => $order_maps,
'headers' => $headers,
'headerKeys' => $header_keys,
'templateMap' => $templates,
'columnMaps' => $column_maps,
'orderMaps' => mpull($all_tasks, 'getWorkboardOrderVectors'),
'propertyMaps' => mpull($all_tasks, 'getWorkboardProperties'),
'orderMaps' => $vector_map,
'propertyMaps' => $properties,
'boardID' => $board_id,
'projectPHID' => $project->getPHID(),
);
$this->initBehavior('project-boards', $behavior_config);
$sort_menu = $this->buildSortMenu(
$viewer,
$project,
$this->sortKey);
$this->sortKey,
$ordering_map);
$filter_menu = $this->buildFilterMenu(
$viewer,
@ -739,7 +764,7 @@ final class PhabricatorProjectBoardViewController
return $default_sort;
}
return PhabricatorProjectColumn::DEFAULT_ORDER;
return PhabricatorProjectColumnNaturalOrder::ORDERKEY;
}
private function getDefaultFilter(PhabricatorProject $project) {
@ -753,41 +778,37 @@ final class PhabricatorProjectBoardViewController
}
private function isValidSort($sort) {
switch ($sort) {
case PhabricatorProjectColumn::ORDER_NATURAL:
case PhabricatorProjectColumn::ORDER_PRIORITY:
return true;
}
return false;
$map = PhabricatorProjectColumnOrder::getEnabledOrders();
return isset($map[$sort]);
}
private function buildSortMenu(
PhabricatorUser $viewer,
PhabricatorProject $project,
$sort_key) {
$sort_icon = id(new PHUIIconView())
->setIcon('fa-sort-amount-asc bluegrey');
$named = array(
PhabricatorProjectColumn::ORDER_NATURAL => pht('Natural'),
PhabricatorProjectColumn::ORDER_PRIORITY => pht('Sort by Priority'),
);
$sort_key,
array $ordering_map) {
$base_uri = $this->getURIWithState();
$items = array();
foreach ($named as $key => $name) {
$is_selected = ($key == $sort_key);
foreach ($ordering_map as $key => $ordering) {
// TODO: It would be desirable to build a real "PHUIIconView" here, but
// the pathway for threading that through all the view classes ends up
// being fairly complex, since some callers read the icon out of other
// views. For now, just stick with a string.
$ordering_icon = $ordering->getMenuIconIcon();
$ordering_name = $ordering->getDisplayName();
$is_selected = ($key === $sort_key);
if ($is_selected) {
$active_order = $name;
$active_name = $ordering_name;
$active_icon = $ordering_icon;
}
$item = id(new PhabricatorActionView())
->setIcon('fa-sort-amount-asc')
->setIcon($ordering_icon)
->setSelected($is_selected)
->setName($name);
->setName($ordering_name);
$uri = $base_uri->alter('order', $key);
$item->setHref($uri);
@ -806,6 +827,9 @@ final class PhabricatorProjectBoardViewController
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$items[] = id(new PhabricatorActionView())
->setType(PhabricatorActionView::TYPE_DIVIDER);
$items[] = id(new PhabricatorActionView())
->setIcon('fa-floppy-o')
->setName(pht('Save as Default'))
@ -820,8 +844,8 @@ final class PhabricatorProjectBoardViewController
}
$sort_button = id(new PHUIListItemView())
->setName($active_order)
->setIcon('fa-sort-amount-asc')
->setName($active_name)
->setIcon($active_icon)
->setHref('#')
->addSigil('boards-dropdown-menu')
->setMetadata(
@ -904,6 +928,9 @@ final class PhabricatorProjectBoardViewController
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$items[] = id(new PhabricatorActionView())
->setType(PhabricatorActionView::TYPE_DIVIDER);
$items[] = id(new PhabricatorActionView())
->setIcon('fa-floppy-o')
->setName(pht('Save as Default'))

View file

@ -149,7 +149,11 @@ abstract class PhabricatorProjectController extends PhabricatorController {
return $this;
}
protected function newCardResponse($board_phid, $object_phid) {
protected function newCardResponse(
$board_phid,
$object_phid,
PhabricatorProjectColumnOrder $ordering = null) {
$viewer = $this->getViewer();
$request = $this->getRequest();
@ -158,12 +162,17 @@ abstract class PhabricatorProjectController extends PhabricatorController {
$visible_phids = array();
}
return id(new PhabricatorBoardResponseEngine())
$engine = id(new PhabricatorBoardResponseEngine())
->setViewer($viewer)
->setBoardPHID($board_phid)
->setObjectPHID($object_phid)
->setVisiblePHIDs($visible_phids)
->buildResponse();
->setVisiblePHIDs($visible_phids);
if ($ordering) {
$engine->setOrdering($ordering);
}
return $engine->buildResponse();
}
public function renderHashtags(array $tags) {

View file

@ -13,7 +13,23 @@ final class PhabricatorProjectMoveController
$object_phid = $request->getStr('objectPHID');
$after_phid = $request->getStr('afterPHID');
$before_phid = $request->getStr('beforePHID');
$order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER);
$order = $request->getStr('order');
if (!strlen($order)) {
$order = PhabricatorProjectColumnNaturalOrder::ORDERKEY;
}
$ordering = PhabricatorProjectColumnOrder::getOrderByKey($order);
$ordering = id(clone $ordering)
->setViewer($viewer);
$edit_header = null;
$raw_header = $request->getStr('header');
if (strlen($raw_header)) {
$edit_header = phutil_json_decode($raw_header);
} else {
$edit_header = array();
}
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
@ -27,6 +43,13 @@ final class PhabricatorProjectMoveController
return new Aphront404Response();
}
$cancel_uri = $this->getApplicationURI(
new PhutilURI(
urisprintf('board/%d/', $project->getID()),
array(
'order' => $order,
)));
$board_phid = $project->getPHID();
$object = id(new ManiphestTaskQuery())
@ -63,20 +86,14 @@ final class PhabricatorProjectMoveController
->setObjectPHIDs(array($object_phid))
->executeLayout();
$columns = $engine->getObjectColumns($board_phid, $object_phid);
$old_column_phids = mpull($columns, 'getPHID');
$xactions = array();
$order_params = array();
if ($order == PhabricatorProjectColumn::ORDER_NATURAL) {
if ($after_phid) {
$order_params['afterPHID'] = $after_phid;
} else if ($before_phid) {
$order_params['beforePHID'] = $before_phid;
}
if ($after_phid) {
$order_params['afterPHID'] = $after_phid;
} else if ($before_phid) {
$order_params['beforePHID'] = $before_phid;
}
$xactions = array();
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS)
->setNewValue(
@ -86,125 +103,23 @@ final class PhabricatorProjectMoveController
) + $order_params,
));
if ($order == PhabricatorProjectColumn::ORDER_PRIORITY) {
$priority_xactions = $this->getPriorityTransactions(
$object,
$after_phid,
$before_phid);
foreach ($priority_xactions as $xaction) {
$xactions[] = $xaction;
}
$header_xactions = $ordering->getColumnTransactions(
$object,
$edit_header);
foreach ($header_xactions as $header_xaction) {
$xactions[] = $header_xaction;
}
$editor = id(new ManiphestTransactionEditor())
->setActor($viewer)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request);
->setContentSourceFromRequest($request)
->setCancelURI($cancel_uri);
$editor->applyTransactions($object, $xactions);
return $this->newCardResponse($board_phid, $object_phid);
}
private function getPriorityTransactions(
ManiphestTask $task,
$after_phid,
$before_phid) {
list($after_task, $before_task) = $this->loadPriorityTasks(
$after_phid,
$before_phid);
$must_move = false;
if ($after_task && !$task->isLowerPriorityThan($after_task)) {
$must_move = true;
}
if ($before_task && !$task->isHigherPriorityThan($before_task)) {
$must_move = true;
}
// The move doesn't require a priority change to be valid, so don't
// change the priority since we are not being forced to.
if (!$must_move) {
return array();
}
$try = array(
array($after_task, true),
array($before_task, false),
);
$pri = null;
$sub = null;
foreach ($try as $spec) {
list($task, $is_after) = $spec;
if (!$task) {
continue;
}
list($pri, $sub) = ManiphestTransactionEditor::getAdjacentSubpriority(
$task,
$is_after);
// If we find a priority on the first try, don't keep going.
break;
}
$keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap();
$keyword = head(idx($keyword_map, $pri));
$xactions = array();
if ($pri !== null) {
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
->setNewValue($keyword);
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(
ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE)
->setNewValue($sub);
}
return $xactions;
}
private function loadPriorityTasks($after_phid, $before_phid) {
$viewer = $this->getViewer();
$task_phids = array();
if ($after_phid) {
$task_phids[] = $after_phid;
}
if ($before_phid) {
$task_phids[] = $before_phid;
}
if (!$task_phids) {
return array(null, null);
}
$tasks = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withPHIDs($task_phids)
->execute();
$tasks = mpull($tasks, null, 'getPHID');
if ($after_phid) {
$after_task = idx($tasks, $after_phid);
} else {
$after_task = null;
}
if ($before_phid) {
$before_task = idx($tasks, $before_phid);
} else {
$before_task = null;
}
return array($after_task, $before_task);
return $this->newCardResponse($board_phid, $object_phid, $ordering);
}
}

View file

@ -6,6 +6,7 @@ final class PhabricatorBoardResponseEngine extends Phobject {
private $boardPHID;
private $objectPHID;
private $visiblePHIDs;
private $ordering;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
@ -43,10 +44,20 @@ final class PhabricatorBoardResponseEngine extends Phobject {
return $this->visiblePHIDs;
}
public function setOrdering(PhabricatorProjectColumnOrder $ordering) {
$this->ordering = $ordering;
return $this;
}
public function getOrdering() {
return $this->ordering;
}
public function buildResponse() {
$viewer = $this->getViewer();
$object_phid = $this->getObjectPHID();
$board_phid = $this->getBoardPHID();
$ordering = $this->getOrdering();
// Load all the other tasks that are visible in the affected columns and
// perform layout for them.
@ -74,10 +85,17 @@ final class PhabricatorBoardResponseEngine extends Phobject {
->setViewer($viewer)
->withPHIDs($visible_phids)
->execute();
$all_visible = mpull($all_visible, null, 'getPHID');
$order_maps = array();
foreach ($all_visible as $visible) {
$order_maps[$visible->getPHID()] = $visible->getWorkboardOrderVectors();
if ($ordering) {
$vectors = $ordering->getSortVectorsForObjects($all_visible);
$header_keys = $ordering->getHeaderKeysForObjects($all_visible);
$headers = $ordering->getHeadersForObjects($all_visible);
$headers = mpull($headers, 'toDictionary');
} else {
$vectors = array();
$header_keys = array();
$headers = array();
}
$object = id(new ManiphestTaskQuery())
@ -91,14 +109,50 @@ final class PhabricatorBoardResponseEngine extends Phobject {
$template = $this->buildTemplate($object);
$cards = array();
foreach ($all_visible as $card_phid => $object) {
$card = array(
'vectors' => array(),
'headers' => array(),
'properties' => array(),
'nodeHTMLTemplate' => null,
);
if ($ordering) {
$order_key = $ordering->getColumnOrderKey();
$vector = idx($vectors, $card_phid);
if ($vector !== null) {
$card['vectors'][$order_key] = $vector;
}
$header = idx($header_keys, $card_phid);
if ($header !== null) {
$card['headers'][$order_key] = $header;
}
$card['properties'] = array(
'points' => (double)$object->getPoints(),
'status' => $object->getStatus(),
);
}
if ($card_phid === $object_phid) {
$card['nodeHTMLTemplate'] = hsprintf('%s', $template);
}
$card['vectors'] = (object)$card['vectors'];
$card['headers'] = (object)$card['headers'];
$card['properties'] = (object)$card['properties'];
$cards[$card_phid] = $card;
}
$payload = array(
'objectPHID' => $object_phid,
'cardHTML' => $template,
'columnMaps' => $natural,
'orderMaps' => $order_maps,
'propertyMaps' => array(
$object_phid => $object->getWorkboardProperties(),
),
'cards' => $cards,
'headers' => $headers,
);
return id(new AphrontAjaxResponse())

View file

@ -0,0 +1,139 @@
<?php
final class PhabricatorProjectColumnAuthorOrder
extends PhabricatorProjectColumnOrder {
const ORDERKEY = 'author';
public function getDisplayName() {
return pht('Group by Author');
}
protected function newMenuIconIcon() {
return 'fa-user-plus';
}
public function getHasHeaders() {
return true;
}
public function getCanReorder() {
return false;
}
public function getMenuOrder() {
return 3000;
}
protected function newHeaderKeyForObject($object) {
return $this->newHeaderKeyForAuthorPHID($object->getAuthorPHID());
}
private function newHeaderKeyForAuthorPHID($author_phid) {
return sprintf('author(%s)', $author_phid);
}
protected function newSortVectorsForObjects(array $objects) {
$author_phids = mpull($objects, null, 'getAuthorPHID');
$author_phids = array_keys($author_phids);
$author_phids = array_filter($author_phids);
if ($author_phids) {
$author_users = id(new PhabricatorPeopleQuery())
->setViewer($this->getViewer())
->withPHIDs($author_phids)
->execute();
$author_users = mpull($author_users, null, 'getPHID');
} else {
$author_users = array();
}
$vectors = array();
foreach ($objects as $vector_key => $object) {
$author_phid = $object->getAuthorPHID();
$author = idx($author_users, $author_phid);
if ($author) {
$vector = $this->newSortVectorForAuthor($author);
} else {
$vector = $this->newSortVectorForAuthorPHID($author_phid);
}
$vectors[$vector_key] = $vector;
}
return $vectors;
}
private function newSortVectorForAuthor(PhabricatorUser $user) {
return array(
1,
$user->getUsername(),
);
}
private function newSortVectorForAuthorPHID($author_phid) {
return array(
2,
$author_phid,
);
}
protected function newHeadersForObjects(array $objects) {
$author_phids = mpull($objects, null, 'getAuthorPHID');
$author_phids = array_keys($author_phids);
$author_phids = array_filter($author_phids);
if ($author_phids) {
$author_users = id(new PhabricatorPeopleQuery())
->setViewer($this->getViewer())
->withPHIDs($author_phids)
->needProfileImage(true)
->execute();
$author_users = mpull($author_users, null, 'getPHID');
} else {
$author_users = array();
}
$headers = array();
foreach ($author_phids as $author_phid) {
$header_key = $this->newHeaderKeyForAuthorPHID($author_phid);
$author = idx($author_users, $author_phid);
if ($author) {
$sort_vector = $this->newSortVectorForAuthor($author);
$author_name = $author->getUsername();
$author_image = $author->getProfileImageURI();
} else {
$sort_vector = $this->newSortVectorForAuthorPHID($author_phid);
$author_name = pht('Unknown User ("%s")', $author_phid);
$author_image = null;
}
$author_icon = 'fa-user';
$author_color = 'bluegrey';
$icon_view = id(new PHUIIconView());
if ($author_image) {
$icon_view->setImage($author_image);
} else {
$icon_view->setIcon($author_icon, $author_color);
}
$header = $this->newHeader()
->setHeaderKey($header_key)
->setSortVector($sort_vector)
->setName($author_name)
->setIcon($icon_view)
->setEditProperties(
array(
'value' => $author_phid,
));
$headers[] = $header;
}
return $headers;
}
}

View file

@ -0,0 +1,35 @@
<?php
final class PhabricatorProjectColumnCreatedOrder
extends PhabricatorProjectColumnOrder {
const ORDERKEY = 'created';
public function getDisplayName() {
return pht('Sort by Created Date');
}
protected function newMenuIconIcon() {
return 'fa-clock-o';
}
public function getHasHeaders() {
return false;
}
public function getCanReorder() {
return false;
}
public function getMenuOrder() {
return 5000;
}
protected function newSortVectorForObject($object) {
return array(
-1 * (int)$object->getDateCreated(),
-1 * (int)$object->getID(),
);
}
}

View file

@ -0,0 +1,99 @@
<?php
final class PhabricatorProjectColumnHeader
extends Phobject {
private $orderKey;
private $headerKey;
private $sortVector;
private $name;
private $icon;
private $editProperties;
public function setOrderKey($order_key) {
$this->orderKey = $order_key;
return $this;
}
public function getOrderKey() {
return $this->orderKey;
}
public function setHeaderKey($header_key) {
$this->headerKey = $header_key;
return $this;
}
public function getHeaderKey() {
return $this->headerKey;
}
public function setSortVector(array $sort_vector) {
$this->sortVector = $sort_vector;
return $this;
}
public function getSortVector() {
return $this->sortVector;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setIcon(PHUIIconView$icon) {
$this->icon = $icon;
return $this;
}
public function getIcon() {
return $this->icon;
}
public function setEditProperties(array $edit_properties) {
$this->editProperties = $edit_properties;
return $this;
}
public function getEditProperties() {
return $this->editProperties;
}
public function toDictionary() {
return array(
'order' => $this->getOrderKey(),
'key' => $this->getHeaderKey(),
'template' => hsprintf('%s', $this->newView()),
'vector' => $this->getSortVector(),
'editProperties' => $this->getEditProperties(),
);
}
private function newView() {
$icon_view = $this->getIcon();
$name = $this->getName();
$template = phutil_tag(
'li',
array(
'class' => 'workboard-group-header',
),
array(
$icon_view,
phutil_tag(
'span',
array(
'class' => 'workboard-group-header-name',
),
$name),
));
return $template;
}
}

View file

@ -0,0 +1,24 @@
<?php
final class PhabricatorProjectColumnNaturalOrder
extends PhabricatorProjectColumnOrder {
const ORDERKEY = 'natural';
public function getDisplayName() {
return pht('Natural');
}
public function getHasHeaders() {
return false;
}
public function getCanReorder() {
return true;
}
public function getMenuOrder() {
return 0;
}
}

View file

@ -0,0 +1,207 @@
<?php
abstract class PhabricatorProjectColumnOrder
extends Phobject {
private $viewer;
final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
final public function getViewer() {
return $this->viewer;
}
final public function getColumnOrderKey() {
return $this->getPhobjectClassConstant('ORDERKEY');
}
final public static function getAllOrders() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getColumnOrderKey')
->setSortMethod('getMenuOrder')
->execute();
}
final public static function getEnabledOrders() {
$map = self::getAllOrders();
foreach ($map as $key => $order) {
if (!$order->isEnabled()) {
unset($map[$key]);
}
}
return $map;
}
final public static function getOrderByKey($key) {
$map = self::getAllOrders();
if (!isset($map[$key])) {
throw new Exception(
pht(
'No column ordering exists with key "%s".',
$key));
}
return $map[$key];
}
final public function getColumnTransactions($object, array $header) {
$result = $this->newColumnTransactions($object, $header);
if (!is_array($result) && !is_null($result)) {
throw new Exception(
pht(
'Expected "newColumnTransactions()" on "%s" to return "null" or a '.
'list of transactions, but got "%s".',
get_class($this),
phutil_describe_type($result)));
}
if ($result === null) {
$result = array();
}
assert_instances_of($result, 'PhabricatorApplicationTransaction');
return $result;
}
final public function getMenuIconIcon() {
return $this->newMenuIconIcon();
}
protected function newMenuIconIcon() {
return 'fa-sort-amount-asc';
}
abstract public function getDisplayName();
abstract public function getHasHeaders();
abstract public function getCanReorder();
public function getMenuOrder() {
return 9000;
}
public function isEnabled() {
return true;
}
protected function newColumnTransactions($object, array $header) {
return array();
}
final public function getHeadersForObjects(array $objects) {
$headers = $this->newHeadersForObjects($objects);
if (!is_array($headers)) {
throw new Exception(
pht(
'Expected "newHeadersForObjects()" on "%s" to return a list '.
'of headers, but got "%s".',
get_class($this),
phutil_describe_type($headers)));
}
assert_instances_of($headers, 'PhabricatorProjectColumnHeader');
// Add a "0" to the end of each header. This makes them sort above object
// cards in the same group.
foreach ($headers as $header) {
$vector = $header->getSortVector();
$vector[] = 0;
$header->setSortVector($vector);
}
return $headers;
}
protected function newHeadersForObjects(array $objects) {
return array();
}
final public function getSortVectorsForObjects(array $objects) {
$vectors = $this->newSortVectorsForObjects($objects);
if (!is_array($vectors)) {
throw new Exception(
pht(
'Expected "newSortVectorsForObjects()" on "%s" to return a '.
'map of vectors, but got "%s".',
get_class($this),
phutil_describe_type($vectors)));
}
assert_same_keys($objects, $vectors);
return $vectors;
}
protected function newSortVectorsForObjects(array $objects) {
$vectors = array();
foreach ($objects as $key => $object) {
$vectors[$key] = $this->newSortVectorForObject($object);
}
return $vectors;
}
protected function newSortVectorForObject($object) {
return array();
}
final public function getHeaderKeysForObjects(array $objects) {
$header_keys = $this->newHeaderKeysForObjects($objects);
if (!is_array($header_keys)) {
throw new Exception(
pht(
'Expected "newHeaderKeysForObject()" on "%s" to return a '.
'map of header keys, but got "%s".',
get_class($this),
phutil_describe_type($header_keys)));
}
assert_same_keys($objects, $header_keys);
return $header_keys;
}
protected function newHeaderKeysForObjects(array $objects) {
$header_keys = array();
foreach ($objects as $key => $object) {
$header_keys[$key] = $this->newHeaderKeyForObject($object);
}
return $header_keys;
}
protected function newHeaderKeyForObject($object) {
return null;
}
final protected function newTransaction($object) {
return $object->getApplicationTransactionTemplate();
}
final protected function newHeader() {
return id(new PhabricatorProjectColumnHeader())
->setOrderKey($this->getColumnOrderKey());
}
final public function toDictionary() {
return array(
'orderKey' => $this->getColumnOrderKey(),
'hasHeaders' => $this->getHasHeaders(),
'canReorder' => $this->getCanReorder(),
);
}
}

View file

@ -0,0 +1,183 @@
<?php
final class PhabricatorProjectColumnOwnerOrder
extends PhabricatorProjectColumnOrder {
const ORDERKEY = 'owner';
public function getDisplayName() {
return pht('Group by Owner');
}
protected function newMenuIconIcon() {
return 'fa-users';
}
public function getHasHeaders() {
return true;
}
public function getCanReorder() {
return true;
}
public function getMenuOrder() {
return 2000;
}
protected function newHeaderKeyForObject($object) {
return $this->newHeaderKeyForOwnerPHID($object->getOwnerPHID());
}
private function newHeaderKeyForOwnerPHID($owner_phid) {
if ($owner_phid === null) {
$owner_phid = '<null>';
}
return sprintf('owner(%s)', $owner_phid);
}
protected function newSortVectorsForObjects(array $objects) {
$owner_phids = mpull($objects, null, 'getOwnerPHID');
$owner_phids = array_keys($owner_phids);
$owner_phids = array_filter($owner_phids);
if ($owner_phids) {
$owner_users = id(new PhabricatorPeopleQuery())
->setViewer($this->getViewer())
->withPHIDs($owner_phids)
->execute();
$owner_users = mpull($owner_users, null, 'getPHID');
} else {
$owner_users = array();
}
$vectors = array();
foreach ($objects as $vector_key => $object) {
$owner_phid = $object->getOwnerPHID();
if (!$owner_phid) {
$vector = $this->newSortVectorForUnowned();
} else {
$owner = idx($owner_users, $owner_phid);
if ($owner) {
$vector = $this->newSortVectorForOwner($owner);
} else {
$vector = $this->newSortVectorForOwnerPHID($owner_phid);
}
}
$vectors[$vector_key] = $vector;
}
return $vectors;
}
private function newSortVectorForUnowned() {
// Always put unasssigned tasks at the top.
return array(
0,
);
}
private function newSortVectorForOwner(PhabricatorUser $user) {
// Put assigned tasks with a valid owner after "Unassigned", but above
// assigned tasks with an invalid owner. Sort these tasks by the owner's
// username.
return array(
1,
$user->getUsername(),
);
}
private function newSortVectorForOwnerPHID($owner_phid) {
// If we have tasks with a nonempty owner but can't load the associated
// "User" object, move them to the bottom. We can only sort these by the
// PHID.
return array(
2,
$owner_phid,
);
}
protected function newHeadersForObjects(array $objects) {
$owner_phids = mpull($objects, null, 'getOwnerPHID');
$owner_phids = array_keys($owner_phids);
$owner_phids = array_filter($owner_phids);
if ($owner_phids) {
$owner_users = id(new PhabricatorPeopleQuery())
->setViewer($this->getViewer())
->withPHIDs($owner_phids)
->needProfileImage(true)
->execute();
$owner_users = mpull($owner_users, null, 'getPHID');
} else {
$owner_users = array();
}
array_unshift($owner_phids, null);
$headers = array();
foreach ($owner_phids as $owner_phid) {
$header_key = $this->newHeaderKeyForOwnerPHID($owner_phid);
$owner_image = null;
if ($owner_phid === null) {
$owner = null;
$sort_vector = $this->newSortVectorForUnowned();
$owner_name = pht('Not Assigned');
} else {
$owner = idx($owner_users, $owner_phid);
if ($owner) {
$sort_vector = $this->newSortVectorForOwner($owner);
$owner_name = $owner->getUsername();
$owner_image = $owner->getProfileImageURI();
} else {
$sort_vector = $this->newSortVectorForOwnerPHID($owner_phid);
$owner_name = pht('Unknown User ("%s")', $owner_phid);
}
}
$owner_icon = 'fa-user';
$owner_color = 'bluegrey';
$icon_view = id(new PHUIIconView());
if ($owner_image) {
$icon_view->setImage($owner_image);
} else {
$icon_view->setIcon($owner_icon, $owner_color);
}
$header = $this->newHeader()
->setHeaderKey($header_key)
->setSortVector($sort_vector)
->setName($owner_name)
->setIcon($icon_view)
->setEditProperties(
array(
'value' => $owner_phid,
));
$headers[] = $header;
}
return $headers;
}
protected function newColumnTransactions($object, array $header) {
$new_owner = idx($header, 'value');
if ($object->getOwnerPHID() === $new_owner) {
return null;
}
$xactions = array();
$xactions[] = $this->newTransaction($object)
->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE)
->setNewValue($new_owner);
return $xactions;
}
}

View file

@ -0,0 +1,50 @@
<?php
final class PhabricatorProjectColumnPointsOrder
extends PhabricatorProjectColumnOrder {
const ORDERKEY = 'points';
public function getDisplayName() {
return pht('Sort by Points');
}
protected function newMenuIconIcon() {
return 'fa-map-pin';
}
public function isEnabled() {
return ManiphestTaskPoints::getIsEnabled();
}
public function getHasHeaders() {
return false;
}
public function getCanReorder() {
return false;
}
public function getMenuOrder() {
return 6000;
}
protected function newSortVectorForObject($object) {
$points = $object->getPoints();
// Put cards with no points on top.
$has_points = ($points !== null);
if (!$has_points) {
$overall_order = 0;
} else {
$overall_order = 1;
}
return array(
$overall_order,
-1.0 * (double)$points,
-1 * (int)$object->getID(),
);
}
}

View file

@ -0,0 +1,103 @@
<?php
final class PhabricatorProjectColumnPriorityOrder
extends PhabricatorProjectColumnOrder {
const ORDERKEY = 'priority';
public function getDisplayName() {
return pht('Group by Priority');
}
protected function newMenuIconIcon() {
return 'fa-sort-numeric-asc';
}
public function getHasHeaders() {
return true;
}
public function getCanReorder() {
return true;
}
public function getMenuOrder() {
return 1000;
}
protected function newHeaderKeyForObject($object) {
return $this->newHeaderKeyForPriority($object->getPriority());
}
private function newHeaderKeyForPriority($priority) {
return sprintf('priority(%d)', $priority);
}
protected function newSortVectorForObject($object) {
return $this->newSortVectorForPriority($object->getPriority());
}
private function newSortVectorForPriority($priority) {
return array(
-1 * (int)$priority,
);
}
protected function newHeadersForObjects(array $objects) {
$priorities = ManiphestTaskPriority::getTaskPriorityMap();
// It's possible for tasks to have an invalid/unknown priority in the
// database. We still want to generate a header for these tasks so we
// don't break the workboard.
$priorities = $priorities + mpull($objects, null, 'getPriority');
$priorities = array_keys($priorities);
$headers = array();
foreach ($priorities as $priority) {
$header_key = $this->newHeaderKeyForPriority($priority);
$sort_vector = $this->newSortVectorForPriority($priority);
$priority_name = ManiphestTaskPriority::getTaskPriorityName($priority);
$priority_color = ManiphestTaskPriority::getTaskPriorityColor($priority);
$priority_icon = ManiphestTaskPriority::getTaskPriorityIcon($priority);
$icon_view = id(new PHUIIconView())
->setIcon($priority_icon, $priority_color);
$header = $this->newHeader()
->setHeaderKey($header_key)
->setSortVector($sort_vector)
->setName($priority_name)
->setIcon($icon_view)
->setEditProperties(
array(
'value' => (int)$priority,
));
$headers[] = $header;
}
return $headers;
}
protected function newColumnTransactions($object, array $header) {
$new_priority = idx($header, 'value');
if ($object->getPriority() === $new_priority) {
return null;
}
$keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap();
$keyword = head(idx($keyword_map, $new_priority));
$xactions = array();
$xactions[] = $this->newTransaction($object)
->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
->setNewValue($keyword);
return $xactions;
}
}

View file

@ -0,0 +1,106 @@
<?php
final class PhabricatorProjectColumnStatusOrder
extends PhabricatorProjectColumnOrder {
const ORDERKEY = 'status';
public function getDisplayName() {
return pht('Group by Status');
}
protected function newMenuIconIcon() {
return 'fa-check';
}
public function getHasHeaders() {
return true;
}
public function getCanReorder() {
return true;
}
public function getMenuOrder() {
return 4000;
}
protected function newHeaderKeyForObject($object) {
return $this->newHeaderKeyForStatus($object->getStatus());
}
private function newHeaderKeyForStatus($status) {
return sprintf('status(%s)', $status);
}
protected function newSortVectorsForObjects(array $objects) {
$status_sequence = $this->newStatusSequence();
$vectors = array();
foreach ($objects as $object_key => $object) {
$vectors[$object_key] = array(
(int)idx($status_sequence, $object->getStatus(), 0),
);
}
return $vectors;
}
private function newStatusSequence() {
$statuses = ManiphestTaskStatus::getTaskStatusMap();
return array_combine(
array_keys($statuses),
range(1, count($statuses)));
}
protected function newHeadersForObjects(array $objects) {
$headers = array();
$statuses = ManiphestTaskStatus::getTaskStatusMap();
$sequence = $this->newStatusSequence();
foreach ($statuses as $status_key => $status_name) {
$header_key = $this->newHeaderKeyForStatus($status_key);
$sort_vector = array(
(int)idx($sequence, $status_key, 0),
);
$status_icon = ManiphestTaskStatus::getStatusIcon($status_key);
$status_color = ManiphestTaskStatus::getStatusColor($status_key);
$icon_view = id(new PHUIIconView())
->setIcon($status_icon, $status_color);
$header = $this->newHeader()
->setHeaderKey($header_key)
->setSortVector($sort_vector)
->setName($status_name)
->setIcon($icon_view)
->setEditProperties(
array(
'value' => $status_key,
));
$headers[] = $header;
}
return $headers;
}
protected function newColumnTransactions($object, array $header) {
$new_status = idx($header, 'value');
if ($object->getStatus() === $new_status) {
return null;
}
$xactions = array();
$xactions[] = $this->newTransaction($object)
->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE)
->setNewValue($new_status);
return $xactions;
}
}

View file

@ -0,0 +1,34 @@
<?php
final class PhabricatorProjectColumnTitleOrder
extends PhabricatorProjectColumnOrder {
const ORDERKEY = 'title';
public function getDisplayName() {
return pht('Sort by Title');
}
protected function newMenuIconIcon() {
return 'fa-sort-alpha-asc';
}
public function getHasHeaders() {
return false;
}
public function getCanReorder() {
return false;
}
public function getMenuOrder() {
return 7000;
}
protected function newSortVectorForObject($object) {
return array(
$object->getTitle(),
);
}
}

View file

@ -12,10 +12,6 @@ final class PhabricatorProjectColumn
const STATUS_ACTIVE = 0;
const STATUS_HIDDEN = 1;
const DEFAULT_ORDER = 'natural';
const ORDER_NATURAL = 'natural';
const ORDER_PRIORITY = 'priority';
protected $name;
protected $status;
protected $projectPHID;

View file

@ -82,20 +82,32 @@ final class ProjectBoardTaskCard extends Phobject {
$card = id(new PHUIObjectItemView())
->setObject($task)
->setUser($viewer)
->setObjectName('T'.$task->getID())
->setObjectName($task->getMonogram())
->setHeader($task->getTitle())
->setGrippable($can_edit)
->setHref('/T'.$task->getID())
->setHref($task->getURI())
->addSigil('project-card')
->setDisabled($task->isClosed())
->addAction(
id(new PHUIListItemView())
->setName(pht('Edit'))
->setIcon('fa-pencil')
->addSigil('edit-project-card')
->setHref('/maniphest/task/edit/'.$task->getID().'/'))
->setBarColor($bar_color);
if ($can_edit) {
$card
->addSigil('draggable-card')
->addClass('draggable-card');
$edit_icon = 'fa-pencil';
} else {
$card
->addClass('not-editable')
->addClass('undraggable-card');
$edit_icon = 'fa-lock red';
}
$card->addAction(
id(new PHUIListItemView())
->setName(pht('Edit'))
->setIcon($edit_icon)
->addSigil('edit-project-card')
->setHref('/maniphest/task/edit/'.$task->getID().'/'));
if ($owner) {
$card->addHandleIcon($owner, $owner->getName());
}

View file

@ -403,7 +403,7 @@ EOTEXT
$info = pht(<<<EOTEXT
Objects matching your query are returned as a list of dictionaries in the
`data` property of the results. Each dictionary has some metadata and a
`fields` key, which contains the information abou the object that most callers
`fields` key, which contains the information about the object that most callers
will be interested in.
For example, the results may look something like this:

View file

@ -1816,31 +1816,52 @@ abstract class PhabricatorApplicationTransactionEditor
$users = mpull($users, null, 'getPHID');
foreach ($phids as $key => $phid) {
// Do not subscribe mentioned users
// who do not have VIEW Permissions
if ($object instanceof PhabricatorPolicyInterface
&& !PhabricatorPolicyFilter::hasCapability(
$users[$phid],
$object,
PhabricatorPolicyCapability::CAN_VIEW)
) {
$user = idx($users, $phid);
// Don't subscribe invalid users.
if (!$user) {
unset($phids[$key]);
} else {
if ($object->isAutomaticallySubscribed($phid)) {
continue;
}
// Don't subscribe bots that get mentioned. If users truly intend
// to subscribe them, they can add them explicitly, but it's generally
// not useful to subscribe bots to objects.
if ($user->getIsSystemAgent()) {
unset($phids[$key]);
continue;
}
// Do not subscribe mentioned users who do not have permission to see
// the object.
if ($object instanceof PhabricatorPolicyInterface) {
$can_view = PhabricatorPolicyFilter::hasCapability(
$user,
$object,
PhabricatorPolicyCapability::CAN_VIEW);
if (!$can_view) {
unset($phids[$key]);
continue;
}
}
// Don't subscribe users who are already automatically subscribed.
if ($object->isAutomaticallySubscribed($phid)) {
unset($phids[$key]);
continue;
}
}
$phids = array_values($phids);
}
// No else here to properly return null should we unset all subscriber
if (!$phids) {
return null;
}
$xaction = newv(get_class(head($xactions)), array());
$xaction->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS);
$xaction->setNewValue(array('+' => $phids));
$xaction = $object->getApplicationTransactionTemplate()
->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)
->setNewValue(array('+' => $phids));
return $xaction;
}
@ -2876,6 +2897,24 @@ abstract class PhabricatorApplicationTransactionEditor
}
}
$actor = $this->getActor();
$user = id(new PhabricatorPeopleQuery())
->setViewer($actor)
->withPHIDs(array($actor_phid))
->executeOne();
if (!$user) {
return $xactions;
}
// When a bot acts (usually via the API), don't automatically subscribe
// them as a side effect. They can always subscribe explicitly if they
// want, and bot subscriptions normally just clutter things up since bots
// usually do not read email.
if ($user->getIsSystemAgent()) {
return $xactions;
}
$xaction = newv(get_class(head($xactions)), array());
$xaction->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS);
$xaction->setNewValue(array('+' => array($actor_phid)));

View file

@ -339,9 +339,11 @@ document. If you can already send outbound email from the command line or know
how to configure it, this option is straightforward. If you have no idea how to
do any of this, strongly consider using Postmark or Mailgun instead.
To use this mailer, set `type` to `sendmail`. There are no `options` to
configure.
To use this mailer, set `type` to `sendmail`, then configure these `options`:
- `message-id`: Optional bool. Set to `false` if Phabricator will not be
able to select a custom "Message-ID" header when sending mail via this
mailer. See "Message-ID Headers" below.
Mailer: SMTP
============
@ -361,6 +363,9 @@ To use this mailer, set `type` to `smtp`, then configure these `options`:
- `password`: Optional string. Password for authentication.
- `protocol`: Optional string. Set to `tls` or `ssl` if necessary. Use
`ssl` for Gmail.
- `message-id`: Optional bool. Set to `false` if Phabricator will not be
able to select a custom "Message-ID" header when sending mail via this
mailer. See "Message-ID Headers" below.
Disable Mail
@ -446,6 +451,54 @@ in any priority group, in the configured order. In this example there is
only one such server, so it will try to send via Mailgun.
Message-ID Headers
==================
Email has a "Message-ID" header which is important for threading messages
correctly in mail clients. Normally, Phabricator is free to select its own
"Message-ID" header values for mail it sends.
However, some mailers (including Amazon SES) do not allow selection of custom
"Message-ID" values and will ignore or replace the "Message-ID" in mail that
is submitted through them.
When Phabricator adds other mail headers which affect threading, like
"In-Reply-To", it needs to know if its "Message-ID" headers will be respected
or not to select header values which will produce good threading behavior. If
we guess wrong and think we can set a "Message-ID" header when we can't, you
may get poor threading behavior in mail clients.
For most mailers (like Postmark, Mailgun, and Amazon SES), the correct setting
will be selected for you automatically, because the behavior of the mailer
is knowable ahead of time. For example, we know Amazon SES will never respect
our "Message-ID" headers.
However, if you're sending mail indirectly through a mailer like SMTP or
Sendmail, the mail might or might not be routing through some mail service
which will ignore or replace the "Message-ID" header.
For example, your local mailer might submit mail to Mailgun (so "Message-ID"
will work), or to Amazon SES (so "Message-ID" will not work), or to some other
mail service (which we may not know anything about). We can't make a reliable
guess about whether "Message-ID" will be respected or not based only on
the local mailer configuration.
By default, we check if the mailer has a hostname we recognize as belonging
to a service which does not allow us to set a "Message-ID" header. If we don't
recognize the hostname (which is very common, since these services are most
often configured against the localhost or some other local machine), we assume
we can set a "Message-ID" header.
If the outbound pathway does not actually allow selection of a "Message-ID"
header, you can set the `message-id` option on the mailer to `false` to tell
Phabricator that it should not assume it can select a value for this header.
For example, if you are sending mail via a local Postfix server which then
forwards the mail to Amazon SES (a service which does not allow selection of
a "Message-ID" header), your `smtp` configuration in Phabricator should
specify `"message-id": false`.
Next Steps
==========

View file

@ -414,25 +414,17 @@ final class PHUIObjectItemView extends AphrontTagView {
));
}
// Wrap the header content in a <span> with the "slippery" sigil. This
// prevents us from beginning a drag if you click the text (like "T123"),
// but not if you click the white space after the header.
$header = phutil_tag(
'div',
array(
'class' => 'phui-oi-name',
),
javelin_tag(
'span',
array(
'sigil' => 'slippery',
),
array(
$this->headIcons,
$header_name,
$header_link,
$description_tag,
)));
array(
$this->headIcons,
$header_name,
$header_link,
$description_tag,
));
$icons = array();
if ($this->icons) {

View file

@ -451,7 +451,6 @@ unselectable. */
-ms-user-select: none;
-webkit-user-select: none;
user-select: none;
opacity: 0.5;
}
.differential-diff.copy-l > tbody > tr > td:nth-child(2) {
@ -459,7 +458,6 @@ unselectable. */
-ms-user-select: auto;
-webkit-user-select: auto;
user-select: auto;
opacity: 1;
}
.differential-diff.copy-l > tbody > tr > td.show-more:nth-child(2) {
@ -467,7 +465,6 @@ unselectable. */
-ms-user-select: none;
-webkit-user-select: none;
user-select: none;
opacity: 0.5;
}
.differential-diff.copy-r > tbody > tr > td:nth-child(5) {
@ -475,7 +472,6 @@ unselectable. */
-ms-user-select: auto;
-webkit-user-select: auto;
user-select: auto;
opacity: 1;
}
.differential-diff.copy-l > tbody > tr.inline > td,
@ -484,5 +480,4 @@ unselectable. */
-ms-user-select: none;
-webkit-user-select: none;
user-select: none;
opacity: 0.5;
}

View file

@ -59,14 +59,6 @@
vertical-align: top;
}
.phui-workcard.phui-oi-grippable .phui-oi-frame {
padding-left: 0;
}
.phui-workcard .phui-oi-grip {
display: none;
}
.device-desktop .phui-workcard .phui-list-item-icon {
display: none;
}
@ -88,6 +80,33 @@
opacity: 1;
}
.device-desktop .phui-workcard.draggable-card {
cursor: grab;
}
.jx-dragging .phui-workcard.draggable-card {
cursor: grabbing;
}
.device-desktop .phui-workcard.undraggable-card {
cursor: not-allowed;
}
.device-desktop .phui-workcard.phui-oi.not-editable:hover {
background: {$sh-redbackground};
}
.device-desktop .phui-workcard.phui-oi.not-editable:hover
.phui-list-item-href {
border-radius: 3px;
background: {$red};
}
.device-desktop .phui-workcard.phui-oi.not-editable:hover
.phui-list-item-href .phui-icon-view {
color: #fff;
}
.phui-workcard.phui-oi:hover .phui-list-item-icon {
display: block;
}

View file

@ -137,3 +137,44 @@
.phui-workpanel-view.project-panel-over-limit .phui-header-shell {
border-color: {$red};
}
.phui-workpanel-view .phui-box-grey {
border: 1px solid transparent;
}
.phui-workpanel-view.workboard-column-drop-target .phui-box-grey {
border-color: {$lightblueborder};
}
.workboard-group-header {
background: rgba({$alphablue}, 0.10);
padding: 6px 8px;
margin: 0 0 8px -8px;
border-bottom: 1px solid {$lightgreyborder};
font-weight: bold;
color: {$darkgreytext};
position: relative;
}
.workboard-group-header .phui-icon-view {
position: absolute;
display: inline-block;
width: 24px;
padding: 5px 0 0 0;
height: 19px;
background-size: 100%;
border-radius: 3px;
background-repeat: no-repeat;
text-align: center;
background-color: {$lightgreybackground};
border: 1px solid {$lightgreybackground};
}
.workboard-group-header .workboard-group-header-name {
display: block;
position: relative;
height: 24px;
line-height: 24px;
margin-left: 36px;
overflow: hidden;
}

View file

@ -41,19 +41,6 @@ JX.behavior('maniphest-batch-selector', function(config) {
update();
};
var redraw = function (task) {
var selected = is_selected(task);
change(task, selected);
};
JX.Stratcom.listen(
'subpriority-changed',
null,
function (e) {
e.kill();
var data = e.getData();
redraw(data.task);
});
// Change all tasks to some state (used by "select all" / "clear selection"
// buttons).

View file

@ -1,72 +0,0 @@
/**
* @provides javelin-behavior-maniphest-subpriority-editor
* @requires javelin-behavior
* javelin-dom
* javelin-stratcom
* javelin-workflow
* phabricator-draggable-list
*/
JX.behavior('maniphest-subpriority-editor', function(config) {
var draggable = new JX.DraggableList('maniphest-task')
.setFindItemsHandler(function() {
var tasks = JX.DOM.scry(document.body, 'li', 'maniphest-task');
var heads = JX.DOM.scry(document.body, 'div', 'task-group');
return tasks.concat(heads);
})
.setGhostHandler(function(ghost, target) {
if (!target) {
// The user is trying to drag a task above the first group header;
// don't permit that since it doesn't make sense.
return false;
}
if (target.nextSibling) {
if (JX.DOM.isType(target, 'div')) {
target.nextSibling.insertBefore(ghost, target.nextSibling.firstChild);
} else {
target.parentNode.insertBefore(ghost, target.nextSibling);
}
} else {
target.parentNode.appendChild(ghost);
}
});
draggable.listen('shouldBeginDrag', function(e) {
if (e.getNode('slippery') || e.getNode('maniphest-edit-task')) {
JX.Stratcom.context().kill();
}
});
draggable.listen('didDrop', function(node, after) {
var data = {
task: JX.Stratcom.getData(node).taskID
};
if (JX.DOM.isType(after, 'div')) {
data.priority = JX.Stratcom.getData(after).priority;
} else {
data.after = JX.Stratcom.getData(after).taskID;
}
draggable.lock();
JX.DOM.alterClass(node, 'drag-sending', true);
var onresponse = function(r) {
var nodes = JX.$H(r.tasks).getFragment().firstChild;
var task = JX.DOM.find(nodes, 'li', 'maniphest-task');
JX.DOM.replace(node, task);
draggable.unlock();
JX.Stratcom.invoke(
'subpriority-changed',
null,
{ 'task' : task });
};
new JX.Workflow(config.uri, data)
.setHandler(onresponse)
.start();
});
});

View file

@ -7,6 +7,9 @@
* javelin-workflow
* phabricator-draggable-list
* javelin-workboard-column
* javelin-workboard-header-template
* javelin-workboard-card-template
* javelin-workboard-order-template
* @javelin
*/
@ -17,9 +20,10 @@ JX.install('WorkboardBoard', {
this._phid = phid;
this._root = root;
this._templates = {};
this._orderMaps = {};
this._propertiesMap = {};
this._headers = {};
this._cards = {};
this._orders = {};
this._buildColumns();
},
@ -33,9 +37,8 @@ JX.install('WorkboardBoard', {
_phid: null,
_root: null,
_columns: null,
_templates: null,
_orderMaps: null,
_propertiesMap: null,
_headers: null,
_cards: null,
getRoot: function() {
return this._root;
@ -53,35 +56,68 @@ JX.install('WorkboardBoard', {
return this._phid;
},
setCardTemplate: function(phid, template) {
this._templates[phid] = template;
return this;
},
setObjectProperties: function(phid, properties) {
this._propertiesMap[phid] = properties;
return this;
},
getObjectProperties: function(phid) {
return this._propertiesMap[phid];
},
getCardTemplate: function(phid) {
return this._templates[phid];
if (!this._cards[phid]) {
this._cards[phid] = new JX.WorkboardCardTemplate(phid);
}
return this._cards[phid];
},
getHeaderTemplate: function(header_key) {
if (!this._headers[header_key]) {
this._headers[header_key] = new JX.WorkboardHeaderTemplate(header_key);
}
return this._headers[header_key];
},
getOrderTemplate: function(order_key) {
if (!this._orders[order_key]) {
this._orders[order_key] = new JX.WorkboardOrderTemplate(order_key);
}
return this._orders[order_key];
},
getHeaderTemplatesForOrder: function(order) {
var templates = [];
for (var k in this._headers) {
var header = this._headers[k];
if (header.getOrder() !== order) {
continue;
}
templates.push(header);
}
templates.sort(JX.bind(this, this._sortHeaderTemplates));
return templates;
},
_sortHeaderTemplates: function(u, v) {
return this.compareVectors(u.getVector(), v.getVector());
},
getController: function() {
return this._controller;
},
setOrderMap: function(phid, map) {
this._orderMaps[phid] = map;
return this;
},
compareVectors: function(u_vec, v_vec) {
for (var ii = 0; ii < u_vec.length; ii++) {
if (u_vec[ii] > v_vec[ii]) {
return 1;
}
getOrderVector: function(phid, key) {
return this._orderMaps[phid][key];
if (u_vec[ii] < v_vec[ii]) {
return -1;
}
}
return 0;
},
start: function() {
@ -108,15 +144,41 @@ JX.install('WorkboardBoard', {
_setupDragHandlers: function() {
var columns = this.getColumns();
var order_template = this.getOrderTemplate(this.getOrder());
var has_headers = order_template.getHasHeaders();
var can_reorder = order_template.getCanReorder();
var lists = [];
for (var k in columns) {
var column = columns[k];
var list = new JX.DraggableList('project-card', column.getRoot())
var list = new JX.DraggableList('draggable-card', column.getRoot())
.setOuterContainer(this.getRoot())
.setFindItemsHandler(JX.bind(column, column.getCardNodes))
.setFindItemsHandler(JX.bind(column, column.getDropTargetNodes))
.setCanDragX(true)
.setHasInfiniteHeight(true);
.setHasInfiniteHeight(true)
.setIsDropTargetHandler(JX.bind(column, column.setIsDropTarget));
var default_handler = list.getGhostHandler();
list.setGhostHandler(
JX.bind(column, column.handleDragGhost, default_handler));
// The "compare handler" locks cards into a specific position in the
// column.
list.setCompareHandler(JX.bind(column, column.compareHandler));
// If the view has group headers, we lock cards into the right position
// when moving them between columns, but not within a column.
if (has_headers) {
list.setCompareOnMove(true);
}
// If we can't reorder cards, we always lock them into their current
// position.
if (!can_reorder) {
list.setCompareOnMove(true);
list.setCompareOnReorder(true);
}
list.listen('didDrop', JX.bind(this, this._onmovecard, list));
@ -146,15 +208,68 @@ JX.install('WorkboardBoard', {
order: this.getOrder()
};
if (after_node) {
data.afterPHID = JX.Stratcom.getData(after_node).objectPHID;
// We're going to send an "afterPHID" and a "beforePHID" if the card
// was dropped immediately adjacent to another card. If a card was
// dropped before or after a header, we don't send a PHID for the card
// on the other side of the header.
// If the view has headers, we always send the header the card was
// dropped under.
var after_data;
var after_card = after_node;
while (after_card) {
after_data = JX.Stratcom.getData(after_card);
if (after_data.objectPHID) {
break;
}
if (after_data.headerKey) {
break;
}
after_card = after_card.previousSibling;
}
var before_node = item.nextSibling;
if (before_node) {
var before_phid = JX.Stratcom.getData(before_node).objectPHID;
if (before_phid) {
data.beforePHID = before_phid;
if (after_data) {
if (after_data.objectPHID) {
data.afterPHID = after_data.objectPHID;
}
}
var before_data;
var before_card = item.nextSibling;
while (before_card) {
before_data = JX.Stratcom.getData(before_card);
if (before_data.objectPHID) {
break;
}
if (before_data.headerKey) {
break;
}
before_card = before_card.nextSibling;
}
if (before_data) {
if (before_data.objectPHID) {
data.beforePHID = before_data.objectPHID;
}
}
var header_data;
var header_node = after_node;
while (header_node) {
header_data = JX.Stratcom.getData(header_node);
if (header_data.headerKey) {
break;
}
header_node = header_node.previousSibling;
}
if (header_data) {
var header_key = header_data.headerKey;
if (header_key) {
var properties = this.getHeaderTemplate(header_key)
.getEditProperties();
data.header = JX.JSON.stringify(properties);
}
}
@ -202,24 +317,15 @@ JX.install('WorkboardBoard', {
var phid = response.objectPHID;
if (!this._templates[phid]) {
for (var add_phid in response.columnMaps) {
var target_column = this.getColumn(add_phid);
for (var add_phid in response.columnMaps) {
var target_column = this.getColumn(add_phid);
if (!target_column) {
// If the column isn't visible, don't try to add a card to it.
continue;
}
target_column.newCard(phid);
if (!target_column) {
// If the column isn't visible, don't try to add a card to it.
continue;
}
}
this.setCardTemplate(phid, response.cardHTML);
var order_maps = response.orderMaps;
for (var order_phid in order_maps) {
this.setOrderMap(order_phid, order_maps[order_phid]);
target_column.newCard(phid);
}
var column_maps = response.columnMaps;
@ -237,9 +343,37 @@ JX.install('WorkboardBoard', {
natural_column.setNaturalOrder(column_maps[natural_phid]);
}
var property_maps = response.propertyMaps;
for (var property_phid in property_maps) {
this.setObjectProperties(property_phid, property_maps[property_phid]);
for (var card_phid in response.cards) {
var card_data = response.cards[card_phid];
var card_template = this.getCardTemplate(card_phid);
if (card_data.nodeHTMLTemplate) {
card_template.setNodeHTMLTemplate(card_data.nodeHTMLTemplate);
}
var order;
for (order in card_data.vectors) {
card_template.setSortVector(order, card_data.vectors[order]);
}
for (order in card_data.headers) {
card_template.setHeaderKey(order, card_data.headers[order]);
}
for (var key in card_data.properties) {
card_template.setObjectProperty(key, card_data.properties[key]);
}
}
var headers = response.headers;
for (var jj = 0; jj < headers.length; jj++) {
var header = headers[jj];
this.getHeaderTemplate(header.key)
.setOrder(header.order)
.setNodeHTMLTemplate(header.template)
.setVector(header.vector)
.setEditProperties(header.editProperties);
}
for (var column_phid in columns) {

View file

@ -29,7 +29,9 @@ JX.install('WorkboardCard', {
},
getProperties: function() {
return this.getColumn().getBoard().getObjectProperties(this.getPHID());
return this.getColumn().getBoard()
.getCardTemplate(this.getPHID())
.getObjectProperties();
},
getPoints: function() {
@ -43,14 +45,23 @@ JX.install('WorkboardCard', {
getNode: function() {
if (!this._root) {
var phid = this.getPHID();
var template = this.getColumn().getBoard().getCardTemplate(phid);
this._root = JX.$H(template).getFragment().firstChild;
JX.Stratcom.getData(this._root).objectPHID = this.getPHID();
var root = this.getColumn().getBoard()
.getCardTemplate(phid)
.newNode();
JX.Stratcom.getData(root).objectPHID = phid;
this._root = root;
}
return this._root;
},
isWorkboardHeader: function() {
return false;
},
redraw: function() {
var old_node = this._root;
this._root = null;

View file

@ -0,0 +1,64 @@
/**
* @provides javelin-workboard-card-template
* @requires javelin-install
* @javelin
*/
JX.install('WorkboardCardTemplate', {
construct: function(phid) {
this._phid = phid;
this._vectors = {};
this._headerKeys = {};
this.setObjectProperties({});
},
properties: {
objectProperties: null
},
members: {
_phid: null,
_html: null,
_vectors: null,
_headerKeys: null,
getPHID: function() {
return this._phid;
},
setNodeHTMLTemplate: function(html) {
this._html = html;
return this;
},
setSortVector: function(order, vector) {
this._vectors[order] = vector;
return this;
},
getSortVector: function(order) {
return this._vectors[order];
},
setHeaderKey: function(order, key) {
this._headerKeys[order] = key;
return this;
},
getHeaderKey: function(order) {
return this._headerKeys[order];
},
newNode: function() {
return JX.$H(this._html).getFragment().firstChild;
},
setObjectProperty: function(key, value) {
this.getObjectProperties()[key] = value;
return this;
}
}
});

View file

@ -2,6 +2,7 @@
* @provides javelin-workboard-column
* @requires javelin-install
* javelin-workboard-card
* javelin-workboard-header
* @javelin
*/
@ -21,6 +22,8 @@ JX.install('WorkboardColumn', {
'column-points-content');
this._cards = {};
this._headers = {};
this._objects = [];
this._naturalOrder = [];
},
@ -29,11 +32,14 @@ JX.install('WorkboardColumn', {
_root: null,
_board: null,
_cards: null,
_headers: null,
_naturalOrder: null,
_orderVectors: null,
_panel: null,
_pointsNode: null,
_pointsContentNode: null,
_dirty: true,
_objects: null,
getPHID: function() {
return this._phid;
@ -47,6 +53,10 @@ JX.install('WorkboardColumn', {
return this._cards;
},
_getObjects: function() {
return this._objects;
},
getCard: function(phid) {
return this._cards[phid];
},
@ -57,6 +67,7 @@ JX.install('WorkboardColumn', {
setNaturalOrder: function(order) {
this._naturalOrder = order;
this._orderVectors = null;
return this;
},
@ -77,6 +88,7 @@ JX.install('WorkboardColumn', {
this._cards[phid] = card;
this._naturalOrder.push(phid);
this._orderVectors = null;
return card;
},
@ -88,6 +100,7 @@ JX.install('WorkboardColumn', {
for (var ii = 0; ii < this._naturalOrder.length; ii++) {
if (this._naturalOrder[ii] == phid) {
this._naturalOrder.splice(ii, 1);
this._orderVectors = null;
break;
}
}
@ -118,15 +131,18 @@ JX.install('WorkboardColumn', {
this._naturalOrder.splice(index, 0, phid);
}
this._orderVectors = null;
return this;
},
getCardNodes: function() {
var cards = this.getCards();
getDropTargetNodes: function() {
var objects = this._getObjects();
var nodes = [];
for (var k in cards) {
nodes.push(cards[k].getNode());
for (var ii = 0; ii < objects.length; ii++) {
var object = objects[ii];
nodes.push(object.getNode());
}
return nodes;
@ -148,24 +164,108 @@ JX.install('WorkboardColumn', {
return this._dirty;
},
getHeader: function(key) {
if (!this._headers[key]) {
this._headers[key] = new JX.WorkboardHeader(this, key);
}
return this._headers[key];
},
handleDragGhost: function(default_handler, ghost, node) {
// If the column has headers, don't let the user drag a card above
// the topmost header: for example, you can't change a task to have
// a priority higher than the highest possible priority.
if (this._hasColumnHeaders()) {
if (!node) {
return false;
}
}
return default_handler(ghost, node);
},
_hasColumnHeaders: function() {
var board = this.getBoard();
var order = board.getOrder();
return board.getOrderTemplate(order).getHasHeaders();
},
redraw: function() {
var board = this.getBoard();
var order = board.getOrder();
var list;
if (order == 'natural') {
list = this._getCardsSortedNaturally();
} else {
list = this._getCardsSortedByKey(order);
var list = this._getCardsSortedByKey(order);
var ii;
var objects = [];
var has_headers = this._hasColumnHeaders();
var header_keys = [];
var seen_headers = {};
if (has_headers) {
var header_templates = board.getHeaderTemplatesForOrder(order);
for (var k in header_templates) {
header_keys.push(header_templates[k].getHeaderKey());
}
header_keys.reverse();
}
var content = [];
for (var ii = 0; ii < list.length; ii++) {
var header_key;
var next;
for (ii = 0; ii < list.length; ii++) {
var card = list[ii];
var node = card.getNode();
content.push(node);
// If a column has a "High" priority card and a "Low" priority card,
// we need to add the "Normal" header in between them. This allows
// you to change priority to "Normal" even if there are no "Normal"
// cards in a column.
if (has_headers) {
header_key = board.getCardTemplate(card.getPHID())
.getHeaderKey(order);
if (!seen_headers[header_key]) {
while (header_keys.length) {
next = header_keys.pop();
var header = this.getHeader(next);
objects.push(header);
seen_headers[header_key] = true;
if (next === header_key) {
break;
}
}
}
}
objects.push(card);
}
// Add any leftover headers at the bottom of the column which don't have
// any cards in them. In particular, empty columns don't have any cards
// but should still have headers.
while (header_keys.length) {
next = header_keys.pop();
if (seen_headers[next]) {
continue;
}
objects.push(this.getHeader(next));
}
this._objects = objects;
var content = [];
for (ii = 0; ii < this._objects.length; ii++) {
var object = this._objects[ii];
var node = object.getNode();
content.push(node);
}
JX.DOM.setContent(this.getRoot(), content);
@ -175,15 +275,30 @@ JX.install('WorkboardColumn', {
this._dirty = false;
},
_getCardsSortedNaturally: function() {
var list = [];
compareHandler: function(src_list, src_node, dst_list, dst_node) {
var board = this.getBoard();
var order = board.getOrder();
for (var ii = 0; ii < this._naturalOrder.length; ii++) {
var phid = this._naturalOrder[ii];
list.push(this.getCard(phid));
var u_vec = this._getNodeOrderVector(src_node, order);
var v_vec = this._getNodeOrderVector(dst_node, order);
return board.compareVectors(u_vec, v_vec);
},
_getNodeOrderVector: function(node, order) {
var board = this.getBoard();
var data = JX.Stratcom.getData(node);
if (data.objectPHID) {
return this._getOrderVector(data.objectPHID, order);
}
return list;
return board.getHeaderTemplate(data.headerKey).getVector();
},
setIsDropTarget: function(is_target) {
var node = this.getWorkpanelNode();
JX.DOM.alterClass(node, 'workboard-column-drop-target', is_target);
},
_getCardsSortedByKey: function(order) {
@ -200,20 +315,65 @@ JX.install('WorkboardColumn', {
},
_sortCards: function(order, u, v) {
var ud = this.getBoard().getOrderVector(u.getPHID(), order);
var vd = this.getBoard().getOrderVector(v.getPHID(), order);
var board = this.getBoard();
var u_vec = this._getOrderVector(u.getPHID(), order);
var v_vec = this._getOrderVector(v.getPHID(), order);
for (var ii = 0; ii < ud.length; ii++) {
if (ud[ii] > vd[ii]) {
return 1;
}
return board.compareVectors(u_vec, v_vec);
},
if (ud[ii] < vd[ii]) {
return -1;
}
_getOrderVector: function(phid, order) {
var board = this.getBoard();
if (!this._orderVectors) {
this._orderVectors = {};
}
return 0;
if (!this._orderVectors[order]) {
var cards = this.getCards();
var vectors = {};
for (var k in cards) {
var card_phid = cards[k].getPHID();
var vector = board.getCardTemplate(card_phid)
.getSortVector(order);
vectors[card_phid] = [].concat(vector);
// Push a "card" type, so cards always sort after headers; headers
// have a "0" in this position.
vectors[card_phid].push(1);
}
for (var ii = 0; ii < this._naturalOrder.length; ii++) {
var natural_phid = this._naturalOrder[ii];
if (vectors[natural_phid]) {
vectors[natural_phid].push(ii);
}
}
this._orderVectors[order] = vectors;
}
if (!this._orderVectors[order][phid]) {
// In this case, we're comparing a card being dragged in from another
// column to the cards already in this column. We're just going to
// build a temporary vector for it.
var incoming_vector = board.getCardTemplate(phid)
.getSortVector(order);
incoming_vector = [].concat(incoming_vector);
// Add a "card" type to sort this after headers.
incoming_vector.push(1);
// Add a "0" for the natural ordering to put this on top. A new card
// has no natural ordering on a column it isn't part of yet.
incoming_vector.push(0);
return incoming_vector;
}
return this._orderVectors[order][phid];
},
_redrawFrame: function() {
@ -279,9 +439,18 @@ JX.install('WorkboardColumn', {
JX.DOM.setContent(content_node, display_value);
var is_empty = !this.getCardPHIDs().length;
// Only put the "empty" style on the column (which just adds some empty
// space so it's easier to drop cards into an empty column) if it has no
// cards and no headers.
var is_empty =
(!this.getCardPHIDs().length) &&
(!this._hasColumnHeaders());
var panel = JX.DOM.findAbove(this.getRoot(), 'div', 'workpanel');
JX.DOM.alterClass(panel, 'project-panel-empty', is_empty);
JX.DOM.alterClass(panel, 'project-panel-over-limit', over_limit);
var color_map = {

View file

@ -0,0 +1,48 @@
/**
* @provides javelin-workboard-header
* @requires javelin-install
* @javelin
*/
JX.install('WorkboardHeader', {
construct: function(column, header_key) {
this._column = column;
this._headerKey = header_key;
},
members: {
_root: null,
_column: null,
_headerKey: null,
getColumn: function() {
return this._column;
},
getHeaderKey: function() {
return this._headerKey;
},
getNode: function() {
if (!this._root) {
var header_key = this.getHeaderKey();
var root = this.getColumn().getBoard()
.getHeaderTemplate(header_key)
.newNode();
JX.Stratcom.getData(root).headerKey = header_key;
this._root = root;
}
return this._root;
},
isWorkboardHeader: function() {
return true;
}
}
});

View file

@ -0,0 +1,39 @@
/**
* @provides javelin-workboard-header-template
* @requires javelin-install
* @javelin
*/
JX.install('WorkboardHeaderTemplate', {
construct: function(header_key) {
this._headerKey = header_key;
},
properties: {
template: null,
order: null,
vector: null,
editProperties: null
},
members: {
_headerKey: null,
_html: null,
getHeaderKey: function() {
return this._headerKey;
},
setNodeHTMLTemplate: function(html) {
this._html = html;
return this;
},
newNode: function() {
return JX.$H(this._html).getFragment().firstChild;
}
}
});

View file

@ -0,0 +1,27 @@
/**
* @provides javelin-workboard-order-template
* @requires javelin-install
* @javelin
*/
JX.install('WorkboardOrderTemplate', {
construct: function(order) {
this._orderKey = order;
},
properties: {
hasHeaders: false,
canReorder: false
},
members: {
_orderKey: null,
getOrderKey: function() {
return this._orderKey;
}
}
});

View file

@ -83,26 +83,58 @@ JX.behavior('project-boards', function(config, statics) {
var templates = config.templateMap;
for (var k in templates) {
board.setCardTemplate(k, templates[k]);
board.getCardTemplate(k)
.setNodeHTMLTemplate(templates[k]);
}
var ii;
var column_maps = config.columnMaps;
for (var column_phid in column_maps) {
var column = board.getColumn(column_phid);
var column_map = column_maps[column_phid];
for (var ii = 0; ii < column_map.length; ii++) {
for (ii = 0; ii < column_map.length; ii++) {
column.newCard(column_map[ii]);
}
}
var order_maps = config.orderMaps;
for (var object_phid in order_maps) {
board.setOrderMap(object_phid, order_maps[object_phid]);
var order_card = board.getCardTemplate(object_phid);
for (var order_key in order_maps[object_phid]) {
order_card.setSortVector(order_key, order_maps[object_phid][order_key]);
}
}
var property_maps = config.propertyMaps;
for (var property_phid in property_maps) {
board.setObjectProperties(property_phid, property_maps[property_phid]);
board.getCardTemplate(property_phid)
.setObjectProperties(property_maps[property_phid]);
}
var headers = config.headers;
for (ii = 0; ii < headers.length; ii++) {
var header = headers[ii];
board.getHeaderTemplate(header.key)
.setOrder(header.order)
.setNodeHTMLTemplate(header.template)
.setVector(header.vector)
.setEditProperties(header.editProperties);
}
var orders = config.orders;
for (ii = 0; ii < orders.length; ii++) {
var order = orders[ii];
board.getOrderTemplate(order.orderKey)
.setHasHeaders(order.hasHeaders)
.setCanReorder(order.canReorder);
}
var header_keys = config.headerKeys;
for (var header_phid in header_keys) {
board.getCardTemplate(header_phid)
.setHeaderKey(config.order, header_keys[header_phid]);
}
board.start();

View file

@ -39,9 +39,13 @@ JX.install('DraggableList', {
properties : {
findItemsHandler: null,
compareHandler: null,
isDropTargetHandler: null,
canDragX: false,
outerContainer: null,
hasInfiniteHeight: false
hasInfiniteHeight: false,
compareOnMove: false,
compareOnReorder: false
},
members : {
@ -238,6 +242,7 @@ JX.install('DraggableList', {
frame.appendChild(clone);
document.body.appendChild(frame);
JX.DOM.alterClass(document.body, 'jx-dragging', true);
this._dragging = drag;
this._clone = clone;
@ -317,7 +322,7 @@ JX.install('DraggableList', {
}
}
JX.DOM.alterClass(root, 'drag-target-list', is_target);
group[ii]._setIsDropTarget(is_target);
}
} else {
target_list = this;
@ -367,8 +372,41 @@ JX.install('DraggableList', {
return this;
},
_setIsDropTarget: function(is_target) {
var root = this.getRootNode();
JX.DOM.alterClass(root, 'drag-target-list', is_target);
var handler = this.getIsDropTargetHandler();
if (handler) {
handler(is_target);
}
return this;
},
_getOrderedTarget: function(src_list, src_node) {
var targets = this._getTargets();
// NOTE: The targets are ordered from the bottom of the column to the
// top, so we're looking for the first node that we sort below. If we
// don't find one, we'll sort to the head of the column.
for (var ii = 0; ii < targets.length; ii++) {
var target = targets[ii];
if (this._compareTargets(src_list, src_node, target.item) > 0) {
return target.item;
}
}
return null;
},
_compareTargets: function(src_list, src_node, dst_node) {
var dst_list = this;
return this.getCompareHandler()(src_list, src_node, dst_list, dst_node);
},
_getCurrentTarget : function(p) {
var ghost = this.getGhostNode();
var targets = this._getTargets();
var dragging = this._dragging;
@ -461,9 +499,34 @@ JX.install('DraggableList', {
// Compute the size and position of the drop target indicator, because we
// need to update our static position computations to account for it.
var compare_handler = this.getCompareHandler();
var cur_target = false;
if (target_list) {
cur_target = target_list._getCurrentTarget(p);
// Determine if we're going to use the compare handler or not: the
// compare hander locks items into a specific place in the list. For
// example, on Workboards, some operations permit the user to drag
// items between lists, but not to reorder items within a list.
var should_compare = false;
var is_reorder = (target_list === this);
var is_move = (target_list !== this);
if (compare_handler) {
if (is_reorder && this.getCompareOnReorder()) {
should_compare = true;
}
if (is_move && this.getCompareOnMove()) {
should_compare = true;
}
}
if (should_compare) {
cur_target = target_list._getOrderedTarget(this, this._dragging);
} else {
cur_target = target_list._getCurrentTarget(p);
}
}
// If we've selected a new target, update the UI to show where we're
@ -577,6 +640,7 @@ JX.install('DraggableList', {
this._autoscroller = null;
JX.DOM.remove(this._frame);
JX.DOM.alterClass(document.body, 'jx-dragging', false);
this._frame = null;
this._clone = null;
@ -605,7 +669,7 @@ JX.install('DraggableList', {
var group = this._group;
for (var ii = 0; ii < group.length; ii++) {
JX.DOM.alterClass(group[ii].getRootNode(), 'drag-target-list', false);
group[ii]._setIsDropTarget(false);
group[ii]._clearTarget();
}