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:
commit
c767b045b2
58 changed files with 2325 additions and 1242 deletions
|
@ -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',
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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.'),
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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',
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -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')
|
||||
|
|
|
@ -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 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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -11,10 +11,6 @@ final class PhabricatorMailAmazonSESAdapter
|
|||
);
|
||||
}
|
||||
|
||||
public function supportsMessageIDHeader() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function validateOptions(array $options) {
|
||||
PhutilTypeSpec::checkMap(
|
||||
$options,
|
||||
|
|
|
@ -11,10 +11,6 @@ final class PhabricatorMailPostmarkAdapter
|
|||
);
|
||||
}
|
||||
|
||||
public function supportsMessageIDHeader() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function validateOptions(array $options) {
|
||||
PhutilTypeSpec::checkMap(
|
||||
$options,
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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'))
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
$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(
|
||||
$header_xactions = $ordering->getColumnTransactions(
|
||||
$object,
|
||||
$after_phid,
|
||||
$before_phid);
|
||||
foreach ($priority_xactions as $xaction) {
|
||||
$xactions[] = $xaction;
|
||||
}
|
||||
$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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
207
src/applications/project/order/PhabricatorProjectColumnOrder.php
Normal file
207
src/applications/project/order/PhabricatorProjectColumnOrder.php
Normal 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(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -82,19 +82,31 @@ 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(
|
||||
->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('fa-pencil')
|
||||
->setIcon($edit_icon)
|
||||
->addSigil('edit-project-card')
|
||||
->setHref('/maniphest/task/edit/'.$task->getID().'/'))
|
||||
->setBarColor($bar_color);
|
||||
->setHref('/maniphest/task/edit/'.$task->getID().'/'));
|
||||
|
||||
if ($owner) {
|
||||
$card->addHandleIcon($owner, $owner->getName());
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 {
|
||||
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)));
|
||||
|
|
|
@ -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
|
||||
==========
|
||||
|
||||
|
|
|
@ -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,
|
||||
)));
|
||||
));
|
||||
|
||||
$icons = array();
|
||||
if ($this->icons) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
});
|
|
@ -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,7 +317,6 @@ 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);
|
||||
|
||||
|
@ -213,14 +327,6 @@ JX.install('WorkboardBoard', {
|
|||
|
||||
target_column.newCard(phid);
|
||||
}
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
var column_maps = response.columnMaps;
|
||||
var natural_column;
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
|
@ -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);
|
||||
},
|
||||
|
||||
_getOrderVector: function(phid, order) {
|
||||
var board = this.getBoard();
|
||||
|
||||
if (!this._orderVectors) {
|
||||
this._orderVectors = {};
|
||||
}
|
||||
|
||||
if (ud[ii] < vd[ii]) {
|
||||
return -1;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
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 = {
|
||||
|
|
48
webroot/rsrc/js/application/projects/WorkboardHeader.js
Normal file
48
webroot/rsrc/js/application/projects/WorkboardHeader.js
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
|
@ -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();
|
||||
|
|
|
@ -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,10 +499,35 @@ 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) {
|
||||
// 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
|
||||
// going to drop the row.
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue