diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a3aadfe9d7..be152c24b9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,13 +7,13 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'b6b40555', + 'core.pkg.css' => '55d9bb83', 'core.pkg.js' => 'f2139810', 'darkconsole.pkg.js' => 'e7393ebb', - 'differential.pkg.css' => 'b3eea3f5', - 'differential.pkg.js' => '01a010d6', + 'differential.pkg.css' => '3e81ae60', + 'differential.pkg.js' => '634399e9', 'diffusion.pkg.css' => '91c5d3a6', - 'diffusion.pkg.js' => '3a9a8bfa', + 'diffusion.pkg.js' => '84c8f8fd', 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '949a7498', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', @@ -25,7 +25,7 @@ return array( 'rsrc/css/aphront/notification.css' => '3f6c89c9', 'rsrc/css/aphront/panel-view.css' => '8427b78d', 'rsrc/css/aphront/phabricator-nav-view.css' => 'ac79a758', - 'rsrc/css/aphront/table-view.css' => '9258e19f', + 'rsrc/css/aphront/table-view.css' => '8df59783', 'rsrc/css/aphront/tokenizer.css' => '056da01b', 'rsrc/css/aphront/tooltip.css' => '1a07aea8', 'rsrc/css/aphront/typeahead-browse.css' => '8904346a', @@ -57,7 +57,7 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'bc6f2127', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => 'ccfbc869', + 'rsrc/css/application/differential/changeset-view.css' => '37792573', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => '5953c28e', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -105,7 +105,7 @@ return array( 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => 'd0801452', 'rsrc/css/core/remarkup.css' => '523d34bb', - 'rsrc/css/core/syntax.css' => '9fc496d5', + 'rsrc/css/core/syntax.css' => '769d3498', 'rsrc/css/core/z-index.css' => '5b6fcf3f', 'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa', 'rsrc/css/font/font-aleo.css' => '8bdb2835', @@ -139,7 +139,7 @@ return array( 'rsrc/css/phui/phui-header-view.css' => '4c7dd8f5', 'rsrc/css/phui/phui-hovercard.css' => 'de1a2119', 'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad', - 'rsrc/css/phui/phui-icon.css' => '3f33ab57', + 'rsrc/css/phui/phui-icon.css' => 'd0534b71', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-info-view.css' => '28efab79', @@ -163,7 +163,7 @@ return array( 'rsrc/css/phui/workboards/phui-workpanel.css' => '92197373', 'rsrc/css/sprite-login.css' => '60e8560e', 'rsrc/css/sprite-menu.css' => '9dd65b92', - 'rsrc/css/sprite-tokens.css' => '4f399012', + 'rsrc/css/sprite-tokens.css' => '72b952bd', 'rsrc/css/syntax/syntax-default.css' => '9923583c', 'rsrc/externals/d3/d3.min.js' => 'a11a5ff2', 'rsrc/externals/font/aleo/aleo-bold.eot' => 'd3d3bed7', @@ -344,8 +344,8 @@ return array( 'rsrc/image/sprite-login.png' => '03d5af29', 'rsrc/image/sprite-menu-X2.png' => 'cfd8fca5', 'rsrc/image/sprite-menu.png' => 'd7a99faa', - 'rsrc/image/sprite-tokens-X2.png' => '348f1745', - 'rsrc/image/sprite-tokens.png' => 'ce0b62be', + 'rsrc/image/sprite-tokens-X2.png' => 'e991bb40', + 'rsrc/image/sprite-tokens.png' => 'fe69d6ab', 'rsrc/image/texture/card-gradient.png' => '815f26e8', 'rsrc/image/texture/dark-menu-hover.png' => '5fa7ece8', 'rsrc/image/texture/dark-menu.png' => '7e22296e', @@ -394,7 +394,7 @@ return array( 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'b42eddc7', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', 'rsrc/js/application/diffusion/behavior-commit-branches.js' => 'bdaf4d04', - 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '5a0b1a64', + 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '49ae8328', 'rsrc/js/application/diffusion/behavior-diffusion-browse-file.js' => '054a0f0b', 'rsrc/js/application/diffusion/behavior-jump-to.js' => '73d09eef', 'rsrc/js/application/diffusion/behavior-load-blame.js' => '42126667', @@ -495,7 +495,7 @@ return array( 'rsrc/js/core/behavior-lightbox-attachments.js' => 'f8ba29d7', 'rsrc/js/core/behavior-line-linker.js' => '1499a8cb', 'rsrc/js/core/behavior-more.js' => 'a80d0378', - 'rsrc/js/core/behavior-object-selector.js' => '9030ebef', + 'rsrc/js/core/behavior-object-selector.js' => 'e0ec7f2f', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', 'rsrc/js/core/behavior-phabricator-nav.js' => '56a1ca03', 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '116cf19b', @@ -518,9 +518,9 @@ return array( 'rsrc/js/core/phtize.js' => 'd254d646', 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '1aa4c968', 'rsrc/js/phui/behavior-phui-file-upload.js' => 'b003d4fb', - 'rsrc/js/phui/behavior-phui-object-box-tabs.js' => '2bfa2836', 'rsrc/js/phui/behavior-phui-profile-menu.js' => '12884df9', 'rsrc/js/phui/behavior-phui-submenu.js' => 'a6f7a73b', + 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262', 'rsrc/js/phuix/PHUIXAutocomplete.js' => '9196fb06', @@ -536,7 +536,7 @@ return array( 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => 'fd18389d', 'aphront-panel-view-css' => '8427b78d', - 'aphront-table-view-css' => '9258e19f', + 'aphront-table-view-css' => '8df59783', 'aphront-tokenizer-control-css' => '056da01b', 'aphront-tooltip-css' => '1a07aea8', 'aphront-typeahead-control-css' => 'd4f16145', @@ -555,7 +555,7 @@ return array( 'conpherence-update-css' => 'faf6be09', 'conpherence-widget-pane-css' => '775eaaba', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => 'ccfbc869', + 'differential-changeset-view-css' => '37792573', 'differential-core-view-css' => '5b7b8ff4', 'differential-inline-comment-editor' => '64a5550f', 'differential-revision-add-comment-css' => 'c47f8c40', @@ -619,7 +619,7 @@ return array( 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', - 'javelin-behavior-diffusion-commit-graph' => '5a0b1a64', + 'javelin-behavior-diffusion-commit-graph' => '49ae8328', 'javelin-behavior-diffusion-jump-to' => '73d09eef', 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', @@ -658,7 +658,7 @@ return array( 'javelin-behavior-phabricator-line-linker' => '1499a8cb', 'javelin-behavior-phabricator-nav' => '56a1ca03', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', - 'javelin-behavior-phabricator-object-selector' => '9030ebef', + 'javelin-behavior-phabricator-object-selector' => 'e0ec7f2f', 'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-remarkup-assist' => '116cf19b', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', @@ -673,9 +673,9 @@ return array( 'javelin-behavior-phui-dropdown-menu' => '1aa4c968', 'javelin-behavior-phui-file-upload' => 'b003d4fb', 'javelin-behavior-phui-hovercards' => 'bcaccd64', - 'javelin-behavior-phui-object-box-tabs' => '2bfa2836', 'javelin-behavior-phui-profile-menu' => '12884df9', 'javelin-behavior-phui-submenu' => 'a6f7a73b', + 'javelin-behavior-phui-tab-group' => '0a0b10e9', 'javelin-behavior-policy-control' => 'd0c516d5', 'javelin-behavior-policy-rule-editor' => '5e9f347c', 'javelin-behavior-project-boards' => '14a1faae', @@ -845,7 +845,7 @@ return array( 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'de1a2119', 'phui-icon-set-selector-css' => '1ab67aad', - 'phui-icon-view-css' => '3f33ab57', + 'phui-icon-view-css' => 'd0534b71', 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => '28efab79', @@ -888,9 +888,9 @@ return array( 'setup-issue-css' => 'db7e9c40', 'sprite-login-css' => '60e8560e', 'sprite-menu-css' => '9dd65b92', - 'sprite-tokens-css' => '4f399012', + 'sprite-tokens-css' => '72b952bd', 'syntax-default-css' => '9923583c', - 'syntax-highlighting-css' => '9fc496d5', + 'syntax-highlighting-css' => '769d3498', 'tokens-css' => '3d0f239e', 'typeahead-browse-css' => '8904346a', 'unhandled-exception-css' => '4c96257a', @@ -977,6 +977,11 @@ return array( 'javelin-stratcom', 'javelin-vector', ), + '0a0b10e9' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + ), '0a3f3021' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1107,11 +1112,6 @@ return array( 'javelin-install', 'javelin-util', ), - '2bfa2836' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - ), '2c426492' => array( 'javelin-behavior', 'javelin-dom', @@ -1136,6 +1136,9 @@ return array( 'javelin-dom', 'javelin-workflow', ), + 37792573 => array( + 'phui-inline-comment-view-css', + ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1214,6 +1217,11 @@ return array( 'javelin-uri', 'phabricator-notification', ), + '49ae8328' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), '4b700e9e' => array( 'javelin-behavior', 'javelin-dom', @@ -1340,11 +1348,6 @@ return array( 'javelin-vector', 'javelin-dom', ), - '5a0b1a64' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - ), '5a13c79f' => array( 'javelin-install', 'javelin-dom', @@ -1460,6 +1463,9 @@ return array( 'javelin-vector', 'javelin-dom', ), + '769d3498' => array( + 'syntax-default-css', + ), '76b9fc3e' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1602,12 +1608,6 @@ return array( 'javelin-dom', 'javelin-request', ), - '9030ebef' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-request', - 'javelin-util', - ), '9196fb06' => array( 'javelin-install', 'javelin-dom', @@ -1671,9 +1671,6 @@ return array( 'javelin-dom', 'javelin-vector', ), - '9fc496d5' => array( - 'syntax-default-css', - ), 'a0b57eb8' => array( 'javelin-behavior', 'javelin-dom', @@ -1931,9 +1928,6 @@ return array( 'javelin-util', 'phabricator-notification-css', ), - 'ccfbc869' => array( - 'phui-inline-comment-view-css', - ), 'cf86d16a' => array( 'javelin-behavior', 'javelin-dom', @@ -2036,6 +2030,12 @@ return array( 'df5e11d2' => array( 'javelin-install', ), + 'e0ec7f2f' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-request', + 'javelin-util', + ), 'e10f8e18' => array( 'javelin-behavior', 'javelin-dom', diff --git a/resources/sprite/manifest/tokens.json b/resources/sprite/manifest/tokens.json index eee41c9d78..6096050a30 100644 --- a/resources/sprite/manifest/tokens.json +++ b/resources/sprite/manifest/tokens.json @@ -4,88 +4,88 @@ "tokens-coin-1": { "name": "tokens-coin-1", "rule": ".tokens-coin-1", - "hash": "0ec4c7309f8191972340c6789a6b5691" + "hash": "5343d745423994c45c5fc689edc47d05" }, "tokens-coin-2": { "name": "tokens-coin-2", "rule": ".tokens-coin-2", - "hash": "4c85dd4b0c388cfefe0075b7056384fd" + "hash": "9a94b5f925f3e6f8eed673d50fbfe148" }, "tokens-coin-3": { "name": "tokens-coin-3", "rule": ".tokens-coin-3", - "hash": "a2e3770894539957e436a7d5a2be4703" + "hash": "68db03ca248309a76cee97ada64239c6" }, "tokens-coin-4": { "name": "tokens-coin-4", "rule": ".tokens-coin-4", - "hash": "856cb87c5590975c0a25177ca2fd2a8f" + "hash": "75832b7e42df9287b3c35c6afed12a93" }, "tokens-heart-1": { "name": "tokens-heart-1", "rule": ".tokens-heart-1", - "hash": "370228318750a79d93848bdf686444e5" + "hash": "2d4812b2129a8eb05fcdbed1e9654422" }, "tokens-heart-2": { "name": "tokens-heart-2", "rule": ".tokens-heart-2", - "hash": "197144d3987308aaef311e29e3503707" + "hash": "64cbdbfb0dc565f17b6f13b5e41bc000" }, "tokens-like-1": { "name": "tokens-like-1", "rule": ".tokens-like-1", - "hash": "3c5271d6678ad6d217a47779488c9918" + "hash": "1b3966d6e0e5d902b558fe3d76ed8a79" }, "tokens-like-2": { "name": "tokens-like-2", "rule": ".tokens-like-2", - "hash": "b009176baadc3e71786ac24ce8229c5a" + "hash": "b74308407fdaa94e08492cfd9b44f2a2" }, "tokens-medal-1": { "name": "tokens-medal-1", "rule": ".tokens-medal-1", - "hash": "cd897529c6834917da062589ae1a69ae" + "hash": "33d837e703091060c1892c402535eef0" }, "tokens-medal-2": { "name": "tokens-medal-2", "rule": ".tokens-medal-2", - "hash": "d56f106b508c33bca6c0a33e2544d0d6" + "hash": "fa2f3b237d7616a6cb309718ad162d7a" }, "tokens-medal-3": { "name": "tokens-medal-3", "rule": ".tokens-medal-3", - "hash": "d4e7c06cfd39d932a35aa25841d5008c" + "hash": "d7282911ba57373b54b4093986143f3e" }, "tokens-medal-4": { "name": "tokens-medal-4", "rule": ".tokens-medal-4", - "hash": "36f596bd2615e521542ac10a771d6902" + "hash": "a107a334968d57314ec6a71620c45b99" }, "tokens-misc-1": { "name": "tokens-misc-1", "rule": ".tokens-misc-1", - "hash": "8f7575c0176570b30aaffb801bcb2c13" + "hash": "671ce03f62c7b0946482ec92d35b8aa3" }, "tokens-misc-2": { "name": "tokens-misc-2", "rule": ".tokens-misc-2", - "hash": "5c61bc36fd0b5545ebf31b57c6ab5185" + "hash": "872353ba82e41512c3b54e5dc2aa3d43" }, "tokens-misc-3": { "name": "tokens-misc-3", "rule": ".tokens-misc-3", - "hash": "97a383def5eb847077b2b26a1a441c0e" + "hash": "cdf9171ec6397b95ea9abe1edeaab359" }, "tokens-misc-4": { "name": "tokens-misc-4", "rule": ".tokens-misc-4", - "hash": "229c8a28e3b6bb883effbb62689e190f" + "hash": "7371fa5ecde282e718b7a15b02ca51e8" } }, "scales": [ 1, 2 ], - "header": "\/**\n * @provides sprite-tokens-css\n * @generated\n *\/\n\n.sprite-tokens {\n background-image: url(\/rsrc\/image\/sprite-tokens.png);\n background-repeat: no-repeat;\n}\n\n@media\nonly screen and (min-device-pixel-ratio: 1.5),\nonly screen and (-webkit-min-device-pixel-ratio: 1.5),\nonly screen and (min-resolution: 1.5dppx) {\n .sprite-tokens {\n background-image: url(\/rsrc\/image\/sprite-tokens-X2.png);\n background-size: {X}px {Y}px;\n }\n}\n", + "header": "/**\n * @provides sprite-tokens-css\n * @generated\n */\n\n.sprite-tokens {\n background-image: url(/rsrc/image/sprite-tokens.png);\n background-repeat: no-repeat;\n}\n\n@media\nonly screen and (min-device-pixel-ratio: 1.5),\nonly screen and (-webkit-min-device-pixel-ratio: 1.5),\nonly screen and (min-resolution: 1.5dppx) {\n .sprite-tokens {\n background-image: url(/rsrc/image/sprite-tokens-X2.png);\n background-size: {X}px {Y}px;\n }\n}\n", "type": "standard" } diff --git a/resources/sprite/tokens_1x/coin-1.png b/resources/sprite/tokens_1x/coin-1.png index bb981e0e20..ec7b376f31 100644 Binary files a/resources/sprite/tokens_1x/coin-1.png and b/resources/sprite/tokens_1x/coin-1.png differ diff --git a/resources/sprite/tokens_1x/coin-2.png b/resources/sprite/tokens_1x/coin-2.png index be74169b1f..8a6bdb5dc5 100644 Binary files a/resources/sprite/tokens_1x/coin-2.png and b/resources/sprite/tokens_1x/coin-2.png differ diff --git a/resources/sprite/tokens_1x/coin-3.png b/resources/sprite/tokens_1x/coin-3.png index b0e65082dd..c89f3d5a53 100644 Binary files a/resources/sprite/tokens_1x/coin-3.png and b/resources/sprite/tokens_1x/coin-3.png differ diff --git a/resources/sprite/tokens_1x/coin-4.png b/resources/sprite/tokens_1x/coin-4.png index 21b82077a3..27cd25c15d 100644 Binary files a/resources/sprite/tokens_1x/coin-4.png and b/resources/sprite/tokens_1x/coin-4.png differ diff --git a/resources/sprite/tokens_1x/heart-1.png b/resources/sprite/tokens_1x/heart-1.png index 53e1013a58..65c469ea5d 100644 Binary files a/resources/sprite/tokens_1x/heart-1.png and b/resources/sprite/tokens_1x/heart-1.png differ diff --git a/resources/sprite/tokens_1x/heart-2.png b/resources/sprite/tokens_1x/heart-2.png index 88bc63982d..387be5eaf8 100644 Binary files a/resources/sprite/tokens_1x/heart-2.png and b/resources/sprite/tokens_1x/heart-2.png differ diff --git a/resources/sprite/tokens_1x/like-1.png b/resources/sprite/tokens_1x/like-1.png index e0a27d224e..dfb7cc2a90 100644 Binary files a/resources/sprite/tokens_1x/like-1.png and b/resources/sprite/tokens_1x/like-1.png differ diff --git a/resources/sprite/tokens_1x/like-2.png b/resources/sprite/tokens_1x/like-2.png index d4069f13eb..287a04d553 100644 Binary files a/resources/sprite/tokens_1x/like-2.png and b/resources/sprite/tokens_1x/like-2.png differ diff --git a/resources/sprite/tokens_1x/medal-1.png b/resources/sprite/tokens_1x/medal-1.png index 83c06af3ad..adb1dbfaf9 100644 Binary files a/resources/sprite/tokens_1x/medal-1.png and b/resources/sprite/tokens_1x/medal-1.png differ diff --git a/resources/sprite/tokens_1x/medal-2.png b/resources/sprite/tokens_1x/medal-2.png index 1f970d4007..ec0c3c5342 100644 Binary files a/resources/sprite/tokens_1x/medal-2.png and b/resources/sprite/tokens_1x/medal-2.png differ diff --git a/resources/sprite/tokens_1x/medal-3.png b/resources/sprite/tokens_1x/medal-3.png index ab60c071fd..7c9bf7bfe1 100644 Binary files a/resources/sprite/tokens_1x/medal-3.png and b/resources/sprite/tokens_1x/medal-3.png differ diff --git a/resources/sprite/tokens_1x/medal-4.png b/resources/sprite/tokens_1x/medal-4.png index e471b25647..ceb8bd255e 100644 Binary files a/resources/sprite/tokens_1x/medal-4.png and b/resources/sprite/tokens_1x/medal-4.png differ diff --git a/resources/sprite/tokens_1x/misc-1.png b/resources/sprite/tokens_1x/misc-1.png index 1d970631fa..9d7a74199b 100644 Binary files a/resources/sprite/tokens_1x/misc-1.png and b/resources/sprite/tokens_1x/misc-1.png differ diff --git a/resources/sprite/tokens_1x/misc-2.png b/resources/sprite/tokens_1x/misc-2.png index 70264e0682..773b38915a 100644 Binary files a/resources/sprite/tokens_1x/misc-2.png and b/resources/sprite/tokens_1x/misc-2.png differ diff --git a/resources/sprite/tokens_1x/misc-3.png b/resources/sprite/tokens_1x/misc-3.png index 1f1d12edc1..3f4e9c90db 100644 Binary files a/resources/sprite/tokens_1x/misc-3.png and b/resources/sprite/tokens_1x/misc-3.png differ diff --git a/resources/sprite/tokens_1x/misc-4.png b/resources/sprite/tokens_1x/misc-4.png index 2004a78417..956bb1bbaf 100644 Binary files a/resources/sprite/tokens_1x/misc-4.png and b/resources/sprite/tokens_1x/misc-4.png differ diff --git a/resources/sprite/tokens_2x/coin-1.png b/resources/sprite/tokens_2x/coin-1.png index a782f94e76..6f73730ec0 100644 Binary files a/resources/sprite/tokens_2x/coin-1.png and b/resources/sprite/tokens_2x/coin-1.png differ diff --git a/resources/sprite/tokens_2x/coin-2.png b/resources/sprite/tokens_2x/coin-2.png index 646fc4dcfc..5c462eb73b 100644 Binary files a/resources/sprite/tokens_2x/coin-2.png and b/resources/sprite/tokens_2x/coin-2.png differ diff --git a/resources/sprite/tokens_2x/coin-3.png b/resources/sprite/tokens_2x/coin-3.png index 118addbca1..2b79bc3050 100644 Binary files a/resources/sprite/tokens_2x/coin-3.png and b/resources/sprite/tokens_2x/coin-3.png differ diff --git a/resources/sprite/tokens_2x/coin-4.png b/resources/sprite/tokens_2x/coin-4.png index 85642d813a..10436a617c 100644 Binary files a/resources/sprite/tokens_2x/coin-4.png and b/resources/sprite/tokens_2x/coin-4.png differ diff --git a/resources/sprite/tokens_2x/heart-1.png b/resources/sprite/tokens_2x/heart-1.png index 93690575a0..baa1024882 100644 Binary files a/resources/sprite/tokens_2x/heart-1.png and b/resources/sprite/tokens_2x/heart-1.png differ diff --git a/resources/sprite/tokens_2x/heart-2.png b/resources/sprite/tokens_2x/heart-2.png index 49e7f55efd..fabce4406b 100644 Binary files a/resources/sprite/tokens_2x/heart-2.png and b/resources/sprite/tokens_2x/heart-2.png differ diff --git a/resources/sprite/tokens_2x/like-1.png b/resources/sprite/tokens_2x/like-1.png index 58b0514c38..4d4a4d6a9d 100644 Binary files a/resources/sprite/tokens_2x/like-1.png and b/resources/sprite/tokens_2x/like-1.png differ diff --git a/resources/sprite/tokens_2x/like-2.png b/resources/sprite/tokens_2x/like-2.png index b14dc06225..4d41fa4975 100644 Binary files a/resources/sprite/tokens_2x/like-2.png and b/resources/sprite/tokens_2x/like-2.png differ diff --git a/resources/sprite/tokens_2x/medal-1.png b/resources/sprite/tokens_2x/medal-1.png index 26fa8c48e4..9a07e5defe 100644 Binary files a/resources/sprite/tokens_2x/medal-1.png and b/resources/sprite/tokens_2x/medal-1.png differ diff --git a/resources/sprite/tokens_2x/medal-2.png b/resources/sprite/tokens_2x/medal-2.png index da56605069..4587141553 100644 Binary files a/resources/sprite/tokens_2x/medal-2.png and b/resources/sprite/tokens_2x/medal-2.png differ diff --git a/resources/sprite/tokens_2x/medal-3.png b/resources/sprite/tokens_2x/medal-3.png index 862bbe4cd5..2aa37023ce 100644 Binary files a/resources/sprite/tokens_2x/medal-3.png and b/resources/sprite/tokens_2x/medal-3.png differ diff --git a/resources/sprite/tokens_2x/medal-4.png b/resources/sprite/tokens_2x/medal-4.png index e8f1644571..1347eacb11 100644 Binary files a/resources/sprite/tokens_2x/medal-4.png and b/resources/sprite/tokens_2x/medal-4.png differ diff --git a/resources/sprite/tokens_2x/misc-1.png b/resources/sprite/tokens_2x/misc-1.png index a867afec4a..956429d607 100644 Binary files a/resources/sprite/tokens_2x/misc-1.png and b/resources/sprite/tokens_2x/misc-1.png differ diff --git a/resources/sprite/tokens_2x/misc-2.png b/resources/sprite/tokens_2x/misc-2.png index 4d0a565f93..9ebcc2fdbb 100644 Binary files a/resources/sprite/tokens_2x/misc-2.png and b/resources/sprite/tokens_2x/misc-2.png differ diff --git a/resources/sprite/tokens_2x/misc-3.png b/resources/sprite/tokens_2x/misc-3.png index 9a5046cc6a..c40e5a28b3 100644 Binary files a/resources/sprite/tokens_2x/misc-3.png and b/resources/sprite/tokens_2x/misc-3.png differ diff --git a/resources/sprite/tokens_2x/misc-4.png b/resources/sprite/tokens_2x/misc-4.png index e5bad2aef8..56b6220ec4 100644 Binary files a/resources/sprite/tokens_2x/misc-4.png and b/resources/sprite/tokens_2x/misc-4.png differ diff --git a/resources/sql/autopatches/20160201.revision.properties.1.sql b/resources/sql/autopatches/20160201.revision.properties.1.sql new file mode 100644 index 0000000000..2ab60b2ce9 --- /dev/null +++ b/resources/sql/autopatches/20160201.revision.properties.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_differential.differential_revision +ADD properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160201.revision.properties.2.sql b/resources/sql/autopatches/20160201.revision.properties.2.sql new file mode 100644 index 0000000000..41d3234abe --- /dev/null +++ b/resources/sql/autopatches/20160201.revision.properties.2.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_differential.differential_revision +SET properties = '{}' WHERE properties = ''; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 68fab974c6..051ab9c41c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -381,6 +381,7 @@ phutil_register_library_map(array( 'DifferentialChangesetTwoUpRenderer' => 'applications/differential/render/DifferentialChangesetTwoUpRenderer.php', 'DifferentialChangesetTwoUpTestRenderer' => 'applications/differential/render/DifferentialChangesetTwoUpTestRenderer.php', 'DifferentialChangesetViewController' => 'applications/differential/controller/DifferentialChangesetViewController.php', + 'DifferentialChildRevisionsField' => 'applications/differential/customfield/DifferentialChildRevisionsField.php', 'DifferentialCloseConduitAPIMethod' => 'applications/differential/conduit/DifferentialCloseConduitAPIMethod.php', 'DifferentialCommentPreviewController' => 'applications/differential/controller/DifferentialCommentPreviewController.php', 'DifferentialCommentSaveController' => 'applications/differential/controller/DifferentialCommentSaveController.php', @@ -407,8 +408,6 @@ phutil_register_library_map(array( 'DifferentialCustomFieldStringIndex' => 'applications/differential/storage/DifferentialCustomFieldStringIndex.php', 'DifferentialDAO' => 'applications/differential/storage/DifferentialDAO.php', 'DifferentialDefaultViewCapability' => 'applications/differential/capability/DifferentialDefaultViewCapability.php', - 'DifferentialDependenciesField' => 'applications/differential/customfield/DifferentialDependenciesField.php', - 'DifferentialDependsOnField' => 'applications/differential/customfield/DifferentialDependsOnField.php', 'DifferentialDiff' => 'applications/differential/storage/DifferentialDiff.php', 'DifferentialDiffAffectedFilesHeraldField' => 'applications/differential/herald/DifferentialDiffAffectedFilesHeraldField.php', 'DifferentialDiffAuthorHeraldField' => 'applications/differential/herald/DifferentialDiffAuthorHeraldField.php', @@ -476,6 +475,7 @@ phutil_register_library_map(array( 'DifferentialManiphestTasksField' => 'applications/differential/customfield/DifferentialManiphestTasksField.php', 'DifferentialModernHunk' => 'applications/differential/storage/DifferentialModernHunk.php', 'DifferentialNextStepField' => 'applications/differential/customfield/DifferentialNextStepField.php', + 'DifferentialParentRevisionsField' => 'applications/differential/customfield/DifferentialParentRevisionsField.php', 'DifferentialParseCacheGarbageCollector' => 'applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php', 'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php', 'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php', @@ -521,9 +521,14 @@ phutil_register_library_map(array( 'DifferentialRevisionDependsOnRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php', 'DifferentialRevisionEditController' => 'applications/differential/controller/DifferentialRevisionEditController.php', 'DifferentialRevisionFulltextEngine' => 'applications/differential/search/DifferentialRevisionFulltextEngine.php', + 'DifferentialRevisionGraph' => 'infrastructure/graph/DifferentialRevisionGraph.php', + 'DifferentialRevisionHasChildRelationship' => 'applications/differential/relationships/DifferentialRevisionHasChildRelationship.php', 'DifferentialRevisionHasCommitEdgeType' => 'applications/differential/edge/DifferentialRevisionHasCommitEdgeType.php', + 'DifferentialRevisionHasCommitRelationship' => 'applications/differential/relationships/DifferentialRevisionHasCommitRelationship.php', + 'DifferentialRevisionHasParentRelationship' => 'applications/differential/relationships/DifferentialRevisionHasParentRelationship.php', 'DifferentialRevisionHasReviewerEdgeType' => 'applications/differential/edge/DifferentialRevisionHasReviewerEdgeType.php', 'DifferentialRevisionHasTaskEdgeType' => 'applications/differential/edge/DifferentialRevisionHasTaskEdgeType.php', + 'DifferentialRevisionHasTaskRelationship' => 'applications/differential/relationships/DifferentialRevisionHasTaskRelationship.php', 'DifferentialRevisionHeraldField' => 'applications/differential/herald/DifferentialRevisionHeraldField.php', 'DifferentialRevisionHeraldFieldGroup' => 'applications/differential/herald/DifferentialRevisionHeraldFieldGroup.php', 'DifferentialRevisionIDField' => 'applications/differential/customfield/DifferentialRevisionIDField.php', @@ -536,6 +541,8 @@ phutil_register_library_map(array( 'DifferentialRevisionPackageHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageHeraldField.php', 'DifferentialRevisionPackageOwnerHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageOwnerHeraldField.php', 'DifferentialRevisionQuery' => 'applications/differential/query/DifferentialRevisionQuery.php', + 'DifferentialRevisionRelationship' => 'applications/differential/relationships/DifferentialRevisionRelationship.php', + 'DifferentialRevisionRelationshipSource' => 'applications/search/relationship/DifferentialRevisionRelationshipSource.php', 'DifferentialRevisionRepositoryHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryHeraldField.php', 'DifferentialRevisionRepositoryProjectsHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryProjectsHeraldField.php', 'DifferentialRevisionRequiredActionResultBucket' => 'applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php', @@ -600,7 +607,9 @@ phutil_register_library_map(array( 'DiffusionCommitEditController' => 'applications/diffusion/controller/DiffusionCommitEditController.php', 'DiffusionCommitFulltextEngine' => 'applications/repository/search/DiffusionCommitFulltextEngine.php', 'DiffusionCommitHasRevisionEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasRevisionEdgeType.php', + 'DiffusionCommitHasRevisionRelationship' => 'applications/diffusion/relationships/DiffusionCommitHasRevisionRelationship.php', 'DiffusionCommitHasTaskEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasTaskEdgeType.php', + 'DiffusionCommitHasTaskRelationship' => 'applications/diffusion/relationships/DiffusionCommitHasTaskRelationship.php', 'DiffusionCommitHash' => 'applications/diffusion/data/DiffusionCommitHash.php', 'DiffusionCommitHeraldField' => 'applications/diffusion/herald/DiffusionCommitHeraldField.php', 'DiffusionCommitHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionCommitHeraldFieldGroup.php', @@ -614,6 +623,8 @@ phutil_register_library_map(array( 'DiffusionCommitParentsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCommitParentsQueryConduitAPIMethod.php', 'DiffusionCommitQuery' => 'applications/diffusion/query/DiffusionCommitQuery.php', 'DiffusionCommitRef' => 'applications/diffusion/data/DiffusionCommitRef.php', + 'DiffusionCommitRelationship' => 'applications/diffusion/relationships/DiffusionCommitRelationship.php', + 'DiffusionCommitRelationshipSource' => 'applications/search/relationship/DiffusionCommitRelationshipSource.php', 'DiffusionCommitRemarkupRule' => 'applications/diffusion/remarkup/DiffusionCommitRemarkupRule.php', 'DiffusionCommitRemarkupRuleTestCase' => 'applications/diffusion/remarkup/__tests__/DiffusionCommitRemarkupRuleTestCase.php', 'DiffusionCommitRepositoryHeraldField' => 'applications/diffusion/herald/DiffusionCommitRepositoryHeraldField.php', @@ -1410,6 +1421,7 @@ phutil_register_library_map(array( 'ManiphestTaskAssigneeHeraldField' => 'applications/maniphest/herald/ManiphestTaskAssigneeHeraldField.php', 'ManiphestTaskAuthorHeraldField' => 'applications/maniphest/herald/ManiphestTaskAuthorHeraldField.php', 'ManiphestTaskAuthorPolicyRule' => 'applications/maniphest/policyrule/ManiphestTaskAuthorPolicyRule.php', + 'ManiphestTaskCloseAsDuplicateRelationship' => 'applications/maniphest/relationship/ManiphestTaskCloseAsDuplicateRelationship.php', 'ManiphestTaskClosedStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskClosedStatusDatasource.php', 'ManiphestTaskDependedOnByTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependedOnByTaskEdgeType.php', 'ManiphestTaskDependsOnTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependsOnTaskEdgeType.php', @@ -1418,8 +1430,10 @@ phutil_register_library_map(array( 'ManiphestTaskEditBulkJobType' => 'applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php', 'ManiphestTaskEditController' => 'applications/maniphest/controller/ManiphestTaskEditController.php', 'ManiphestTaskFulltextEngine' => 'applications/maniphest/search/ManiphestTaskFulltextEngine.php', + 'ManiphestTaskGraph' => 'infrastructure/graph/ManiphestTaskGraph.php', 'ManiphestTaskHasCommitEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasCommitEdgeType.php', 'ManiphestTaskHasCommitRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasCommitRelationship.php', + 'ManiphestTaskHasDuplicateTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasDuplicateTaskEdgeType.php', 'ManiphestTaskHasMockEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasMockEdgeType.php', 'ManiphestTaskHasMockRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasMockRelationship.php', 'ManiphestTaskHasParentRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasParentRelationship.php', @@ -1428,10 +1442,12 @@ phutil_register_library_map(array( 'ManiphestTaskHasSubtaskRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasSubtaskRelationship.php', 'ManiphestTaskHeraldField' => 'applications/maniphest/herald/ManiphestTaskHeraldField.php', 'ManiphestTaskHeraldFieldGroup' => 'applications/maniphest/herald/ManiphestTaskHeraldFieldGroup.php', + 'ManiphestTaskIsDuplicateOfTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskIsDuplicateOfTaskEdgeType.php', 'ManiphestTaskListController' => 'applications/maniphest/controller/ManiphestTaskListController.php', 'ManiphestTaskListHTTPParameterType' => 'applications/maniphest/httpparametertype/ManiphestTaskListHTTPParameterType.php', 'ManiphestTaskListView' => 'applications/maniphest/view/ManiphestTaskListView.php', 'ManiphestTaskMailReceiver' => 'applications/maniphest/mail/ManiphestTaskMailReceiver.php', + 'ManiphestTaskMergeInRelationship' => 'applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php', 'ManiphestTaskOpenStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskOpenStatusDatasource.php', 'ManiphestTaskPHIDResolver' => 'applications/maniphest/httpparametertype/ManiphestTaskPHIDResolver.php', 'ManiphestTaskPHIDType' => 'applications/maniphest/phid/ManiphestTaskPHIDType.php', @@ -1442,6 +1458,7 @@ phutil_register_library_map(array( 'ManiphestTaskPriorityHeraldField' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldField.php', 'ManiphestTaskQuery' => 'applications/maniphest/query/ManiphestTaskQuery.php', 'ManiphestTaskRelationship' => 'applications/maniphest/relationship/ManiphestTaskRelationship.php', + 'ManiphestTaskRelationshipSource' => 'applications/search/relationship/ManiphestTaskRelationshipSource.php', 'ManiphestTaskResultListView' => 'applications/maniphest/view/ManiphestTaskResultListView.php', 'ManiphestTaskSearchEngine' => 'applications/maniphest/query/ManiphestTaskSearchEngine.php', 'ManiphestTaskStatus' => 'applications/maniphest/constants/ManiphestTaskStatus.php', @@ -1584,6 +1601,8 @@ phutil_register_library_map(array( 'PHUICurtainExtension' => 'view/extension/PHUICurtainExtension.php', 'PHUICurtainPanelView' => 'view/layout/PHUICurtainPanelView.php', 'PHUICurtainView' => 'view/layout/PHUICurtainView.php', + 'PHUIDiffGraphView' => 'infrastructure/diff/view/PHUIDiffGraphView.php', + 'PHUIDiffGraphViewTestCase' => 'infrastructure/diff/view/__tests__/PHUIDiffGraphViewTestCase.php', 'PHUIDiffInlineCommentDetailView' => 'infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php', 'PHUIDiffInlineCommentEditView' => 'infrastructure/diff/view/PHUIDiffInlineCommentEditView.php', 'PHUIDiffInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php', @@ -1648,6 +1667,8 @@ phutil_register_library_map(array( 'PHUISpacesNamespaceContextView' => 'applications/spaces/view/PHUISpacesNamespaceContextView.php', 'PHUIStatusItemView' => 'view/phui/PHUIStatusItemView.php', 'PHUIStatusListView' => 'view/phui/PHUIStatusListView.php', + 'PHUITabGroupView' => 'view/phui/PHUITabGroupView.php', + 'PHUITabView' => 'view/phui/PHUITabView.php', 'PHUITagExample' => 'applications/uiexample/examples/PHUITagExample.php', 'PHUITagView' => 'view/phui/PHUITagView.php', 'PHUITimelineEventView' => 'view/phui/PHUITimelineEventView.php', @@ -2849,6 +2870,7 @@ phutil_register_library_map(array( 'PhabricatorOAuthServerTokenController' => 'applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php', 'PhabricatorOAuthServerTransaction' => 'applications/oauthserver/storage/PhabricatorOAuthServerTransaction.php', 'PhabricatorOAuthServerTransactionQuery' => 'applications/oauthserver/query/PhabricatorOAuthServerTransactionQuery.php', + 'PhabricatorObjectGraph' => 'infrastructure/graph/PhabricatorObjectGraph.php', 'PhabricatorObjectHandle' => 'applications/phid/PhabricatorObjectHandle.php', 'PhabricatorObjectHasAsanaSubtaskEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasAsanaSubtaskEdgeType.php', 'PhabricatorObjectHasAsanaTaskEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasAsanaTaskEdgeType.php', @@ -2867,6 +2889,7 @@ phutil_register_library_map(array( 'PhabricatorObjectQuery' => 'applications/phid/query/PhabricatorObjectQuery.php', 'PhabricatorObjectRelationship' => 'applications/search/relationship/PhabricatorObjectRelationship.php', 'PhabricatorObjectRelationshipList' => 'applications/search/relationship/PhabricatorObjectRelationshipList.php', + 'PhabricatorObjectRelationshipSource' => 'applications/search/relationship/PhabricatorObjectRelationshipSource.php', 'PhabricatorObjectRemarkupRule' => 'infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php', 'PhabricatorObjectSelectorDialog' => 'view/control/PhabricatorObjectSelectorDialog.php', 'PhabricatorOffsetPagedQuery' => 'infrastructure/query/PhabricatorOffsetPagedQuery.php', @@ -3360,7 +3383,6 @@ phutil_register_library_map(array( 'PhabricatorSearchApplication' => 'applications/search/application/PhabricatorSearchApplication.php', 'PhabricatorSearchApplicationSearchEngine' => 'applications/search/query/PhabricatorSearchApplicationSearchEngine.php', 'PhabricatorSearchApplicationStorageEnginePanel' => 'applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php', - 'PhabricatorSearchAttachController' => 'applications/search/controller/PhabricatorSearchAttachController.php', 'PhabricatorSearchBaseController' => 'applications/search/controller/PhabricatorSearchBaseController.php', 'PhabricatorSearchCheckboxesField' => 'applications/search/field/PhabricatorSearchCheckboxesField.php', 'PhabricatorSearchConfigOptions' => 'applications/search/config/PhabricatorSearchConfigOptions.php', @@ -3397,12 +3419,12 @@ phutil_register_library_map(array( 'PhabricatorSearchOrderField' => 'applications/search/field/PhabricatorSearchOrderField.php', 'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.php', 'PhabricatorSearchRelationshipController' => 'applications/search/controller/PhabricatorSearchRelationshipController.php', + 'PhabricatorSearchRelationshipSourceController' => 'applications/search/controller/PhabricatorSearchRelationshipSourceController.php', 'PhabricatorSearchResultBucket' => 'applications/search/buckets/PhabricatorSearchResultBucket.php', 'PhabricatorSearchResultBucketGroup' => 'applications/search/buckets/PhabricatorSearchResultBucketGroup.php', 'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php', 'PhabricatorSearchSchemaSpec' => 'applications/search/storage/PhabricatorSearchSchemaSpec.php', 'PhabricatorSearchScopeSetting' => 'applications/settings/setting/PhabricatorSearchScopeSetting.php', - 'PhabricatorSearchSelectController' => 'applications/search/controller/PhabricatorSearchSelectController.php', 'PhabricatorSearchSelectField' => 'applications/search/field/PhabricatorSearchSelectField.php', 'PhabricatorSearchStringListField' => 'applications/search/field/PhabricatorSearchStringListField.php', 'PhabricatorSearchSubscribersField' => 'applications/search/field/PhabricatorSearchSubscribersField.php', @@ -3779,6 +3801,7 @@ phutil_register_library_map(array( 'PhameBlogArchiveController' => 'applications/phame/controller/blog/PhameBlogArchiveController.php', 'PhameBlogController' => 'applications/phame/controller/blog/PhameBlogController.php', 'PhameBlogCreateCapability' => 'applications/phame/capability/PhameBlogCreateCapability.php', + 'PhameBlogDatasource' => 'applications/phame/typeahead/PhameBlogDatasource.php', 'PhameBlogEditConduitAPIMethod' => 'applications/phame/conduit/PhameBlogEditConduitAPIMethod.php', 'PhameBlogEditController' => 'applications/phame/controller/blog/PhameBlogEditController.php', 'PhameBlogEditEngine' => 'applications/phame/editor/PhameBlogEditEngine.php', @@ -3862,6 +3885,7 @@ phutil_register_library_map(array( 'PholioMockEmbedView' => 'applications/pholio/view/PholioMockEmbedView.php', 'PholioMockFulltextEngine' => 'applications/pholio/search/PholioMockFulltextEngine.php', 'PholioMockHasTaskEdgeType' => 'applications/pholio/edge/PholioMockHasTaskEdgeType.php', + 'PholioMockHasTaskRelationship' => 'applications/pholio/relationships/PholioMockHasTaskRelationship.php', 'PholioMockHeraldField' => 'applications/pholio/herald/PholioMockHeraldField.php', 'PholioMockHeraldFieldGroup' => 'applications/pholio/herald/PholioMockHeraldFieldGroup.php', 'PholioMockImagesView' => 'applications/pholio/view/PholioMockImagesView.php', @@ -3870,6 +3894,8 @@ phutil_register_library_map(array( 'PholioMockNameHeraldField' => 'applications/pholio/herald/PholioMockNameHeraldField.php', 'PholioMockPHIDType' => 'applications/pholio/phid/PholioMockPHIDType.php', 'PholioMockQuery' => 'applications/pholio/query/PholioMockQuery.php', + 'PholioMockRelationship' => 'applications/pholio/relationships/PholioMockRelationship.php', + 'PholioMockRelationshipSource' => 'applications/search/relationship/PholioMockRelationshipSource.php', 'PholioMockSearchEngine' => 'applications/pholio/query/PholioMockSearchEngine.php', 'PholioMockThumbGridView' => 'applications/pholio/view/PholioMockThumbGridView.php', 'PholioMockViewController' => 'applications/pholio/controller/PholioMockViewController.php', @@ -4703,6 +4729,7 @@ phutil_register_library_map(array( 'DifferentialChangesetTwoUpRenderer' => 'DifferentialChangesetHTMLRenderer', 'DifferentialChangesetTwoUpTestRenderer' => 'DifferentialChangesetTestRenderer', 'DifferentialChangesetViewController' => 'DifferentialController', + 'DifferentialChildRevisionsField' => 'DifferentialCustomField', 'DifferentialCloseConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialCommentPreviewController' => 'DifferentialController', 'DifferentialCommentSaveController' => 'DifferentialController', @@ -4729,8 +4756,6 @@ phutil_register_library_map(array( 'DifferentialCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage', 'DifferentialDAO' => 'PhabricatorLiskDAO', 'DifferentialDefaultViewCapability' => 'PhabricatorPolicyCapability', - 'DifferentialDependenciesField' => 'DifferentialCustomField', - 'DifferentialDependsOnField' => 'DifferentialCustomField', 'DifferentialDiff' => array( 'DifferentialDAO', 'PhabricatorPolicyInterface', @@ -4811,6 +4836,7 @@ phutil_register_library_map(array( 'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField', 'DifferentialModernHunk' => 'DifferentialHunk', 'DifferentialNextStepField' => 'DifferentialCustomField', + 'DifferentialParentRevisionsField' => 'DifferentialCustomField', 'DifferentialParseCacheGarbageCollector' => 'PhabricatorGarbageCollector', 'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialParseRenderTestCase' => 'PhabricatorTestCase', @@ -4872,9 +4898,14 @@ phutil_register_library_map(array( 'DifferentialRevisionDependsOnRevisionEdgeType' => 'PhabricatorEdgeType', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionFulltextEngine' => 'PhabricatorFulltextEngine', + 'DifferentialRevisionGraph' => 'PhabricatorObjectGraph', + 'DifferentialRevisionHasChildRelationship' => 'DifferentialRevisionRelationship', 'DifferentialRevisionHasCommitEdgeType' => 'PhabricatorEdgeType', + 'DifferentialRevisionHasCommitRelationship' => 'DifferentialRevisionRelationship', + 'DifferentialRevisionHasParentRelationship' => 'DifferentialRevisionRelationship', 'DifferentialRevisionHasReviewerEdgeType' => 'PhabricatorEdgeType', 'DifferentialRevisionHasTaskEdgeType' => 'PhabricatorEdgeType', + 'DifferentialRevisionHasTaskRelationship' => 'DifferentialRevisionRelationship', 'DifferentialRevisionHeraldField' => 'HeraldField', 'DifferentialRevisionHeraldFieldGroup' => 'HeraldFieldGroup', 'DifferentialRevisionIDField' => 'DifferentialCustomField', @@ -4887,6 +4918,8 @@ phutil_register_library_map(array( 'DifferentialRevisionPackageHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionPackageOwnerHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'DifferentialRevisionRelationship' => 'PhabricatorObjectRelationship', + 'DifferentialRevisionRelationshipSource' => 'PhabricatorObjectRelationshipSource', 'DifferentialRevisionRepositoryHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionRepositoryProjectsHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionRequiredActionResultBucket' => 'DifferentialRevisionResultBucket', @@ -4951,7 +4984,9 @@ phutil_register_library_map(array( 'DiffusionCommitEditController' => 'DiffusionController', 'DiffusionCommitFulltextEngine' => 'PhabricatorFulltextEngine', 'DiffusionCommitHasRevisionEdgeType' => 'PhabricatorEdgeType', + 'DiffusionCommitHasRevisionRelationship' => 'DiffusionCommitRelationship', 'DiffusionCommitHasTaskEdgeType' => 'PhabricatorEdgeType', + 'DiffusionCommitHasTaskRelationship' => 'DiffusionCommitRelationship', 'DiffusionCommitHash' => 'Phobject', 'DiffusionCommitHeraldField' => 'HeraldField', 'DiffusionCommitHeraldFieldGroup' => 'HeraldFieldGroup', @@ -4965,6 +5000,8 @@ phutil_register_library_map(array( 'DiffusionCommitParentsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionCommitQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DiffusionCommitRef' => 'Phobject', + 'DiffusionCommitRelationship' => 'PhabricatorObjectRelationship', + 'DiffusionCommitRelationshipSource' => 'PhabricatorObjectRelationshipSource', 'DiffusionCommitRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'DiffusionCommitRemarkupRuleTestCase' => 'PhabricatorTestCase', 'DiffusionCommitRepositoryHeraldField' => 'DiffusionCommitHeraldField', @@ -5903,6 +5940,7 @@ phutil_register_library_map(array( 'ManiphestTaskAssigneeHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskAuthorHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskAuthorPolicyRule' => 'PhabricatorPolicyRule', + 'ManiphestTaskCloseAsDuplicateRelationship' => 'ManiphestTaskRelationship', 'ManiphestTaskClosedStatusDatasource' => 'PhabricatorTypeaheadDatasource', 'ManiphestTaskDependedOnByTaskEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskDependsOnTaskEdgeType' => 'PhabricatorEdgeType', @@ -5911,8 +5949,10 @@ phutil_register_library_map(array( 'ManiphestTaskEditBulkJobType' => 'PhabricatorWorkerBulkJobType', 'ManiphestTaskEditController' => 'ManiphestController', 'ManiphestTaskFulltextEngine' => 'PhabricatorFulltextEngine', + 'ManiphestTaskGraph' => 'PhabricatorObjectGraph', 'ManiphestTaskHasCommitEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskHasCommitRelationship' => 'ManiphestTaskRelationship', + 'ManiphestTaskHasDuplicateTaskEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskHasMockEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskHasMockRelationship' => 'ManiphestTaskRelationship', 'ManiphestTaskHasParentRelationship' => 'ManiphestTaskRelationship', @@ -5921,10 +5961,12 @@ phutil_register_library_map(array( 'ManiphestTaskHasSubtaskRelationship' => 'ManiphestTaskRelationship', 'ManiphestTaskHeraldField' => 'HeraldField', 'ManiphestTaskHeraldFieldGroup' => 'HeraldFieldGroup', + 'ManiphestTaskIsDuplicateOfTaskEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskListController' => 'ManiphestController', 'ManiphestTaskListHTTPParameterType' => 'AphrontListHTTPParameterType', 'ManiphestTaskListView' => 'ManiphestView', 'ManiphestTaskMailReceiver' => 'PhabricatorObjectMailReceiver', + 'ManiphestTaskMergeInRelationship' => 'ManiphestTaskRelationship', 'ManiphestTaskOpenStatusDatasource' => 'PhabricatorTypeaheadDatasource', 'ManiphestTaskPHIDResolver' => 'PhabricatorPHIDResolver', 'ManiphestTaskPHIDType' => 'PhabricatorPHIDType', @@ -5935,6 +5977,7 @@ phutil_register_library_map(array( 'ManiphestTaskPriorityHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'ManiphestTaskRelationship' => 'PhabricatorObjectRelationship', + 'ManiphestTaskRelationshipSource' => 'PhabricatorObjectRelationshipSource', 'ManiphestTaskResultListView' => 'ManiphestView', 'ManiphestTaskSearchEngine' => 'PhabricatorApplicationSearchEngine', 'ManiphestTaskStatus' => 'ManiphestConstants', @@ -6096,6 +6139,8 @@ phutil_register_library_map(array( 'PHUICurtainExtension' => 'Phobject', 'PHUICurtainPanelView' => 'AphrontTagView', 'PHUICurtainView' => 'AphrontTagView', + 'PHUIDiffGraphView' => 'Phobject', + 'PHUIDiffGraphViewTestCase' => 'PhabricatorTestCase', 'PHUIDiffInlineCommentDetailView' => 'PHUIDiffInlineCommentView', 'PHUIDiffInlineCommentEditView' => 'PHUIDiffInlineCommentView', 'PHUIDiffInlineCommentRowScaffold' => 'AphrontView', @@ -6160,6 +6205,8 @@ phutil_register_library_map(array( 'PHUISpacesNamespaceContextView' => 'AphrontView', 'PHUIStatusItemView' => 'AphrontTagView', 'PHUIStatusListView' => 'AphrontTagView', + 'PHUITabGroupView' => 'AphrontTagView', + 'PHUITabView' => 'AphrontTagView', 'PHUITagExample' => 'PhabricatorUIExample', 'PHUITagView' => 'AphrontTagView', 'PHUITimelineEventView' => 'AphrontView', @@ -7540,6 +7587,7 @@ phutil_register_library_map(array( 'PhabricatorOAuthServerTokenController' => 'PhabricatorOAuthServerController', 'PhabricatorOAuthServerTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorOAuthServerTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorObjectGraph' => 'AbstractDirectedGraph', 'PhabricatorObjectHandle' => array( 'Phobject', 'PhabricatorPolicyInterface', @@ -7561,6 +7609,7 @@ phutil_register_library_map(array( 'PhabricatorObjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorObjectRelationship' => 'Phobject', 'PhabricatorObjectRelationshipList' => 'Phobject', + 'PhabricatorObjectRelationshipSource' => 'Phobject', 'PhabricatorObjectRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorObjectSelectorDialog' => 'Phobject', 'PhabricatorOffsetPagedQuery' => 'PhabricatorQuery', @@ -8182,7 +8231,6 @@ phutil_register_library_map(array( 'PhabricatorSearchApplication' => 'PhabricatorApplication', 'PhabricatorSearchApplicationSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorSearchApplicationStorageEnginePanel' => 'PhabricatorApplicationConfigurationPanel', - 'PhabricatorSearchAttachController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchBaseController' => 'PhabricatorController', 'PhabricatorSearchCheckboxesField' => 'PhabricatorSearchField', 'PhabricatorSearchConfigOptions' => 'PhabricatorApplicationConfigOptions', @@ -8219,12 +8267,12 @@ phutil_register_library_map(array( 'PhabricatorSearchOrderField' => 'PhabricatorSearchField', 'PhabricatorSearchRelationship' => 'Phobject', 'PhabricatorSearchRelationshipController' => 'PhabricatorSearchBaseController', + 'PhabricatorSearchRelationshipSourceController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchResultBucket' => 'Phobject', 'PhabricatorSearchResultBucketGroup' => 'Phobject', 'PhabricatorSearchResultView' => 'AphrontView', 'PhabricatorSearchSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorSearchScopeSetting' => 'PhabricatorInternalSetting', - 'PhabricatorSearchSelectController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchSelectField' => 'PhabricatorSearchField', 'PhabricatorSearchStringListField' => 'PhabricatorSearchField', 'PhabricatorSearchSubscribersField' => 'PhabricatorSearchTokenizerField', @@ -8665,6 +8713,7 @@ phutil_register_library_map(array( 'PhameBlogArchiveController' => 'PhameBlogController', 'PhameBlogController' => 'PhameController', 'PhameBlogCreateCapability' => 'PhabricatorPolicyCapability', + 'PhameBlogDatasource' => 'PhabricatorTypeaheadDatasource', 'PhameBlogEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhameBlogEditController' => 'PhameBlogController', 'PhameBlogEditEngine' => 'PhabricatorEditEngine', @@ -8782,6 +8831,7 @@ phutil_register_library_map(array( 'PholioMockEmbedView' => 'AphrontView', 'PholioMockFulltextEngine' => 'PhabricatorFulltextEngine', 'PholioMockHasTaskEdgeType' => 'PhabricatorEdgeType', + 'PholioMockHasTaskRelationship' => 'PholioMockRelationship', 'PholioMockHeraldField' => 'HeraldField', 'PholioMockHeraldFieldGroup' => 'HeraldFieldGroup', 'PholioMockImagesView' => 'AphrontView', @@ -8790,6 +8840,8 @@ phutil_register_library_map(array( 'PholioMockNameHeraldField' => 'PholioMockHeraldField', 'PholioMockPHIDType' => 'PhabricatorPHIDType', 'PholioMockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PholioMockRelationship' => 'PhabricatorObjectRelationship', + 'PholioMockRelationshipSource' => 'PhabricatorObjectRelationshipSource', 'PholioMockSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PholioMockThumbGridView' => 'AphrontView', 'PholioMockViewController' => 'PholioController', diff --git a/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php b/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php index 6d516916eb..8e063ba565 100644 --- a/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php +++ b/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php @@ -24,7 +24,10 @@ final class PhabricatorAuthRevokeTokenController } } - $panel_uri = '/settings/panel/tokens/'; + $panel_uri = id(new PhabricatorTokensSettingsPanel()) + ->setViewer($viewer) + ->setUser($viewer) + ->getPanelURI(); if (!$tokens) { return $this->newDialog() diff --git a/src/applications/badges/storage/PhabricatorBadgesBadge.php b/src/applications/badges/storage/PhabricatorBadgesBadge.php index 91ed3cf34d..be83c8404f 100644 --- a/src/applications/badges/storage/PhabricatorBadgesBadge.php +++ b/src/applications/badges/storage/PhabricatorBadgesBadge.php @@ -186,7 +186,7 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO ->execute(); foreach ($awards as $award) { - $engine->destroyObjectPermanently($award); + $engine->destroyObject($award); } $this->openTransaction(); diff --git a/src/applications/celerity/CeleritySpriteGenerator.php b/src/applications/celerity/CeleritySpriteGenerator.php index 55346c915c..0dba6c346f 100644 --- a/src/applications/celerity/CeleritySpriteGenerator.php +++ b/src/applications/celerity/CeleritySpriteGenerator.php @@ -61,7 +61,7 @@ final class CeleritySpriteGenerator extends Phobject { '2x' => 2, ); $template = id(new PhutilSprite()) - ->setSourceSize(16, 16); + ->setSourceSize(18, 18); $sprites = array(); $prefix = 'tokens_'; diff --git a/src/applications/conduit/controller/PhabricatorConduitController.php b/src/applications/conduit/controller/PhabricatorConduitController.php index 000d01f888..6187421e16 100644 --- a/src/applications/conduit/controller/PhabricatorConduitController.php +++ b/src/applications/conduit/controller/PhabricatorConduitController.php @@ -25,6 +25,8 @@ abstract class PhabricatorConduitController extends PhabricatorController { } protected function renderExampleBox(ConduitAPIMethod $method, $params) { + $viewer = $this->getViewer(); + $arc_example = id(new PHUIPropertyListView()) ->addRawContent($this->renderExample($method, 'arc', $params)); @@ -34,10 +36,15 @@ abstract class PhabricatorConduitController extends PhabricatorController { $php_example = id(new PHUIPropertyListView()) ->addRawContent($this->renderExample($method, 'php', $params)); + $panel_uri = id(new PhabricatorConduitTokensSettingsPanel()) + ->setViewer($viewer) + ->setUser($viewer) + ->getPanelURI(); + $panel_link = phutil_tag( 'a', array( - 'href' => '/settings/panel/apitokens/', + 'href' => $panel_uri, ), pht('Conduit API Tokens')); @@ -53,13 +60,28 @@ abstract class PhabricatorConduitController extends PhabricatorController { ->setErrors($messages) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE); + $tab_group = id(new PHUITabGroupView()) + ->addTab( + id(new PHUITabView()) + ->setName(pht('arc call-conduit')) + ->setKey('arc') + ->appendChild($arc_example)) + ->addTab( + id(new PHUITabView()) + ->setName(pht('cURL')) + ->setKey('curl') + ->appendChild($curl_example)) + ->addTab( + id(new PHUITabView()) + ->setName(pht('PHP')) + ->setKey('php') + ->appendChild($php_example)); + return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Examples')) ->setInfoView($info_view) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($arc_example, pht('arc call-conduit')) - ->addPropertyList($curl_example, pht('cURL')) - ->addPropertyList($php_example, pht('PHP')); + ->addTabGroup($tab_group); } private function renderExample( diff --git a/src/applications/conduit/controller/PhabricatorConduitTokenEditController.php b/src/applications/conduit/controller/PhabricatorConduitTokenEditController.php index 503456d010..7550f92210 100644 --- a/src/applications/conduit/controller/PhabricatorConduitTokenEditController.php +++ b/src/applications/conduit/controller/PhabricatorConduitTokenEditController.php @@ -49,11 +49,10 @@ final class PhabricatorConduitTokenEditController $submit_button = pht('Generate Token'); } - if ($viewer->getPHID() == $object->getPHID()) { - $panel_uri = '/settings/panel/apitokens/'; - } else { - $panel_uri = '/settings/'.$object->getID().'/panel/apitokens/'; - } + $panel_uri = id(new PhabricatorConduitTokensSettingsPanel()) + ->setViewer($viewer) + ->setUser($object) + ->getPanelURI(); id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( $viewer, diff --git a/src/applications/conduit/controller/PhabricatorConduitTokenTerminateController.php b/src/applications/conduit/controller/PhabricatorConduitTokenTerminateController.php index 466089ebeb..9f1ffd2964 100644 --- a/src/applications/conduit/controller/PhabricatorConduitTokenTerminateController.php +++ b/src/applications/conduit/controller/PhabricatorConduitTokenTerminateController.php @@ -31,7 +31,6 @@ final class PhabricatorConduitTokenTerminateController 'Really terminate this token? Any system using this token '. 'will no longer be able to make API requests.'); $submit_button = pht('Terminate Token'); - $panel_uri = '/settings/panel/apitokens/'; } else { $tokens = id(new PhabricatorConduitTokenQuery()) ->setViewer($viewer) @@ -51,7 +50,6 @@ final class PhabricatorConduitTokenTerminateController $submit_button = pht('Terminate Tokens'); } - $panel_uri = '/settings/panel/apitokens/'; if ($object_phid != $viewer->getPHID()) { $object = id(new PhabricatorObjectQuery()) ->setViewer($viewer) @@ -60,9 +58,15 @@ final class PhabricatorConduitTokenTerminateController if (!$object) { return new Aphront404Response(); } - $panel_uri = '/settings/'.$object->getID().'/panel/apitokens/'; + } else { + $object = $viewer; } + $panel_uri = id(new PhabricatorConduitTokensSettingsPanel()) + ->setViewer($viewer) + ->setUser($object) + ->getPanelURI(); + id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( $viewer, $request, diff --git a/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php index 2bf43d295e..1d4bcc2a8d 100644 --- a/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php @@ -206,6 +206,7 @@ final class DifferentialQueryConduitAPIMethod 'statusName' => ArcanistDifferentialRevisionStatus::getNameForRevisionStatus( $revision->getStatus()), + 'properties' => $revision->getProperties(), 'branch' => $diff->getBranch(), 'summary' => $revision->getSummary(), 'testPlan' => $revision->getTestPlan(), diff --git a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php index fa0bd0c891..1d7b646270 100644 --- a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php +++ b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php @@ -40,8 +40,8 @@ final class PhabricatorDifferentialConfigOptions new DifferentialViewPolicyField(), new DifferentialEditPolicyField(), - new DifferentialDependsOnField(), - new DifferentialDependenciesField(), + new DifferentialParentRevisionsField(), + new DifferentialChildRevisionsField(), new DifferentialManiphestTasksField(), new DifferentialCommitsField(), diff --git a/src/applications/differential/controller/DifferentialController.php b/src/applications/differential/controller/DifferentialController.php index 43252c07f6..1991580116 100644 --- a/src/applications/differential/controller/DifferentialController.php +++ b/src/applications/differential/controller/DifferentialController.php @@ -28,8 +28,8 @@ abstract class DifferentialController extends PhabricatorController { $viewer = $this->getViewer(); $toc_view = id(new PHUIDiffTableOfContentsListView()) - ->setUser($viewer) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); + ->setViewer($viewer) + ->setBare(true); $have_owners = PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorOwnersApplication', diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 32f9978e4d..e7fcdba377 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -292,7 +292,7 @@ final class DifferentialRevisionViewController extends DifferentialController { '/differential/comment/inline/edit/'.$revision->getID().'/'); } - $diff_history = id(new DifferentialRevisionUpdateHistoryView()) + $history = id(new DifferentialRevisionUpdateHistoryView()) ->setUser($viewer) ->setDiffs($diffs) ->setSelectedVersusDiffID($diff_vs) @@ -300,7 +300,7 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setSelectedWhitespace($whitespace) ->setCommitsForLinks($commits_for_links); - $local_view = id(new DifferentialLocalCommitsView()) + $local_table = id(new DifferentialLocalCommitsView()) ->setUser($viewer) ->setLocalCommits(idx($props, 'local:commits')) ->setCommitsForLinks($commits_for_links); @@ -324,6 +324,69 @@ final class DifferentialRevisionViewController extends DifferentialController { $visible_changesets, $target->loadCoverageMap($viewer)); + $tab_group = id(new PHUITabGroupView()) + ->addTab( + id(new PHUITabView()) + ->setName(pht('Files')) + ->setKey('files') + ->appendChild($toc_view)) + ->addTab( + id(new PHUITabView()) + ->setName(pht('History')) + ->setKey('history') + ->appendChild($history)) + ->addTab( + id(new PHUITabView()) + ->setName(pht('Commits')) + ->setKey('commits') + ->appendChild($local_table)); + + $stack_graph = id(new DifferentialRevisionGraph()) + ->setViewer($viewer) + ->setSeedPHID($revision->getPHID()) + ->setLoadEntireGraph(true) + ->loadGraph(); + if (!$stack_graph->isEmpty()) { + $stack_table = $stack_graph->newGraphTable(); + + $parent_type = DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST; + $reachable = $stack_graph->getReachableObjects($parent_type); + + foreach ($reachable as $key => $reachable_revision) { + if ($reachable_revision->isClosed()) { + unset($reachable[$key]); + } + } + + if ($reachable) { + $stack_name = pht('Stack (%s Open)', phutil_count($reachable)); + $stack_color = PHUIListItemView::STATUS_FAIL; + } else { + $stack_name = pht('Stack'); + $stack_color = null; + } + + $tab_group->addTab( + id(new PHUITabView()) + ->setName($stack_name) + ->setKey('stack') + ->setColor($stack_color) + ->appendChild($stack_table)); + } + + if ($other_view) { + $tab_group->addTab( + id(new PHUITabView()) + ->setName(pht('Similar')) + ->setKey('similar') + ->appendChild($other_view)); + } + + $tab_view = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Revision Contents')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addTabGroup($tab_group); + $comment_form = null; if (!$viewer_is_anonymous) { $comment_form = $this->buildCommentForm($revision, $field_list); @@ -348,15 +411,16 @@ final class DifferentialRevisionViewController extends DifferentialController { 'The content of this revision is hidden until the author has '. 'signed all of the required legal agreements.')); } else { - $footer[] = - array( - $diff_history, - $warning, - $local_view, - $toc_view, - $other_view, - $changeset_view, - ); + $anchor = id(new PhabricatorAnchorView()) + ->setAnchorName('toc') + ->setNavigationMarker(true); + + $footer[] = array( + $anchor, + $warning, + $tab_view, + $changeset_view, + ); } if ($comment_form) { @@ -516,28 +580,6 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $this->requireResource('phabricator-object-selector-css'); - $this->requireResource('javelin-behavior-phabricator-object-selector'); - - $curtain->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-link') - ->setName(pht('Edit Dependencies')) - ->setHref("/search/attach/{$revision_phid}/DREV/dependencies/") - ->setWorkflow(true) - ->setDisabled(!$can_edit)); - - $maniphest = 'PhabricatorManiphestApplication'; - if (PhabricatorApplication::isClassInstalled($maniphest)) { - $curtain->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-anchor') - ->setName(pht('Edit Maniphest Tasks')) - ->setHref("/search/attach/{$revision_phid}/TASK/") - ->setWorkflow(true) - ->setDisabled(!$can_edit)); - } - $request_uri = $this->getRequest()->getRequestURI(); $curtain->addAction( id(new PhabricatorActionView()) @@ -545,6 +587,26 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setName(pht('Download Raw Diff')) ->setHref($request_uri->alter('download', 'true'))); + $relationship_list = PhabricatorObjectRelationshipList::newForObject( + $viewer, + $revision); + + $revision_actions = array( + DifferentialRevisionHasParentRelationship::RELATIONSHIPKEY, + DifferentialRevisionHasChildRelationship::RELATIONSHIPKEY, + ); + + $revision_submenu = $relationship_list->newActionSubmenu($revision_actions) + ->setName(pht('Edit Related Revisions...')) + ->setIcon('fa-cog'); + + $curtain->addAction($revision_submenu); + + $relationship_submenu = $relationship_list->newActionMenu(); + if ($relationship_submenu) { + $curtain->addAction($relationship_submenu); + } + return $curtain; } @@ -872,9 +934,9 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setHeader(pht('Recent Similar Revisions')); $view = id(new DifferentialRevisionListView()) - ->setHeader($header) ->setRevisions($revisions) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setNoBox(true) ->setUser($viewer); $phids = $view->getRequiredHandlePHIDs(); @@ -1033,28 +1095,26 @@ final class DifferentialRevisionViewController extends DifferentialController { ); } - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Diff Detail')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setUser($viewer); + $tab_group = id(new PHUITabGroupView()) + ->setHideSingleTab(true); - $last_tab = null; foreach ($property_lists as $key => $property_list) { list($tab_name, $list_view) = $property_list; - $tab = id(new PHUIListItemView()) + $tab = id(new PHUITabView()) ->setKey($key) - ->setName($tab_name); + ->setName($tab_name) + ->appendChild($list_view); - $box->addPropertyList($list_view, $tab); - $last_tab = $tab; + $tab_group->addTab($tab); + $tab_group->selectTab($key); } - if ($last_tab) { - $last_tab->setSelected(true); - } - - return $box; + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Diff Detail')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setUser($viewer) + ->addTabGroup($tab_group); } private function buildDiffPropertyList( @@ -1170,5 +1230,4 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setShowViewAll(true); } - } diff --git a/src/applications/differential/customfield/DifferentialChildRevisionsField.php b/src/applications/differential/customfield/DifferentialChildRevisionsField.php new file mode 100644 index 0000000000..1d9406a448 --- /dev/null +++ b/src/applications/differential/customfield/DifferentialChildRevisionsField.php @@ -0,0 +1,22 @@ +getFieldName(); - } - - public function getRequiredHandlePHIDsForPropertyView() { - return PhabricatorEdgeQuery::loadDestinationPHIDs( - $this->getObject()->getPHID(), - DifferentialRevisionDependedOnByRevisionEdgeType::EDGECONST); - } - - public function renderPropertyViewValue(array $handles) { - return $this->renderHandleList($handles); - } - -} diff --git a/src/applications/differential/customfield/DifferentialDependsOnField.php b/src/applications/differential/customfield/DifferentialParentRevisionsField.php similarity index 61% rename from src/applications/differential/customfield/DifferentialDependsOnField.php rename to src/applications/differential/customfield/DifferentialParentRevisionsField.php index 17156fe240..30ecea4bf2 100644 --- a/src/applications/differential/customfield/DifferentialDependsOnField.php +++ b/src/applications/differential/customfield/DifferentialParentRevisionsField.php @@ -1,6 +1,6 @@ getFieldName(); - } - - public function getRequiredHandlePHIDsForPropertyView() { - return PhabricatorEdgeQuery::loadDestinationPHIDs( - $this->getObject()->getPHID(), - DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST); - } - - public function renderPropertyViewValue(array $handles) { - return $this->renderHandleList($handles); - } - public function getProTips() { return array( pht( diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index a87c12c926..bf2df0acee 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -182,6 +182,7 @@ final class DifferentialTransactionEditor $status_revision = ArcanistDifferentialRevisionStatus::NEEDS_REVISION; $status_plan = ArcanistDifferentialRevisionStatus::CHANGES_PLANNED; $status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED; + $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; switch ($xaction->getTransactionType()) { case DifferentialTransaction::TYPE_INLINE: @@ -233,7 +234,12 @@ final class DifferentialTransactionEditor $object->setStatus($status_review); return; case DifferentialAction::ACTION_CLOSE: + $old_status = $object->getStatus(); $object->setStatus(ArcanistDifferentialRevisionStatus::CLOSED); + $was_accepted = ($old_status == $status_accepted); + $object->setProperty( + DifferentialRevision::PROPERTY_CLOSED_FROM_ACCEPTED, + $was_accepted); return; case DifferentialAction::ACTION_CLAIM: $object->setAuthorPHID($this->getActingAsPHID()); diff --git a/src/applications/differential/relationships/DifferentialRevisionHasChildRelationship.php b/src/applications/differential/relationships/DifferentialRevisionHasChildRelationship.php new file mode 100644 index 0000000000..01c088ab39 --- /dev/null +++ b/src/applications/differential/relationships/DifferentialRevisionHasChildRelationship.php @@ -0,0 +1,44 @@ +getViewer(); + + $has_app = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorDifferentialApplication', + $viewer); + if (!$has_app) { + return false; + } + + return ($object instanceof DifferentialRevision); + } + +} diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index c2915d92c6..97d5a5e581 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -35,6 +35,7 @@ final class DifferentialRevision extends DifferentialDAO protected $repositoryPHID; protected $viewPolicy = PhabricatorPolicies::POLICY_USER; protected $editPolicy = PhabricatorPolicies::POLICY_USER; + protected $properties = array(); private $relationships = self::ATTACHABLE; private $commits = self::ATTACHABLE; @@ -53,6 +54,8 @@ final class DifferentialRevision extends DifferentialDAO const RELATION_REVIEWER = 'revw'; const RELATION_SUBSCRIBED = 'subd'; + const PROPERTY_CLOSED_FROM_ACCEPTED = 'wasAcceptedBeforeClose'; + public static function initializeNewRevision(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) ->setViewer($actor) @@ -76,6 +79,7 @@ final class DifferentialRevision extends DifferentialDAO self::CONFIG_SERIALIZATION => array( 'attached' => self::SERIALIZATION_JSON, 'unsubscribed' => self::SERIALIZATION_JSON, + 'properties' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'title' => 'text255', @@ -114,11 +118,28 @@ final class DifferentialRevision extends DifferentialDAO ) + parent::getConfiguration(); } + public function setProperty($key, $value) { + $this->properties[$key] = $value; + return $this; + } + + public function getProperty($key, $default = null) { + return idx($this->properties, $key, $default); + } + + public function hasRevisionProperty($key) { + return array_key_exists($key, $this->properties); + } + public function getMonogram() { $id = $this->getID(); return "D{$id}"; } + public function getURI() { + return '/'.$this->getMonogram(); + } + public function setTitle($title) { $this->title = $title; if (!$this->getID()) { @@ -409,6 +430,31 @@ final class DifferentialRevision extends DifferentialDAO return DifferentialRevisionStatus::isClosedStatus($this->getStatus()); } + public function getStatusIcon() { + $map = array( + ArcanistDifferentialRevisionStatus::NEEDS_REVIEW + => 'fa-code grey', + ArcanistDifferentialRevisionStatus::NEEDS_REVISION + => 'fa-refresh red', + ArcanistDifferentialRevisionStatus::CHANGES_PLANNED + => 'fa-headphones red', + ArcanistDifferentialRevisionStatus::ACCEPTED + => 'fa-check green', + ArcanistDifferentialRevisionStatus::CLOSED + => 'fa-check-square-o black', + ArcanistDifferentialRevisionStatus::ABANDONED + => 'fa-plane black', + ); + + return idx($map, $this->getStatus()); + } + + public function getStatusDisplayName() { + $status = $this->getStatus(); + return ArcanistDifferentialRevisionStatus::getNameForRevisionStatus( + $status); + } + public function getFlag(PhabricatorUser $viewer) { return $this->assertAttachedKey($this->flags, $viewer->getPHID()); } diff --git a/src/applications/differential/view/DifferentialLocalCommitsView.php b/src/applications/differential/view/DifferentialLocalCommitsView.php index 639b62fc4b..4ac2bc14b3 100644 --- a/src/applications/differential/view/DifferentialLocalCommitsView.php +++ b/src/applications/differential/view/DifferentialLocalCommitsView.php @@ -125,10 +125,7 @@ final class DifferentialLocalCommitsView extends AphrontView { $headers[] = pht('Date'); $table->setHeaders($headers); - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Local Commits')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); + return $table; } private static function formatCommit($commit) { diff --git a/src/applications/differential/view/DifferentialRevisionListView.php b/src/applications/differential/view/DifferentialRevisionListView.php index 6451bd9d2a..74e3f7633f 100644 --- a/src/applications/differential/view/DifferentialRevisionListView.php +++ b/src/applications/differential/view/DifferentialRevisionListView.php @@ -104,10 +104,6 @@ final class DifferentialRevisionListView extends AphrontView { $modified = $revision->getDateModified(); - $status = $revision->getStatus(); - $status_name = - ArcanistDifferentialRevisionStatus::getNameForRevisionStatus($status); - if (isset($icons['flag'])) { $item->addHeadIcon($icons['flag']); } @@ -155,29 +151,14 @@ final class DifferentialRevisionListView extends AphrontView { $item->addAttribute(pht('Reviewers: %s', $reviewers)); $item->setEpoch($revision->getDateModified()); - switch ($status) { - case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: - $item->setStatusIcon('fa-code grey', pht('Needs Review')); - break; - case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: - $item->setStatusIcon('fa-refresh red', pht('Needs Revision')); - break; - case ArcanistDifferentialRevisionStatus::CHANGES_PLANNED: - $item->setStatusIcon('fa-headphones red', pht('Changes Planned')); - break; - case ArcanistDifferentialRevisionStatus::ACCEPTED: - $item->setStatusIcon('fa-check green', pht('Accepted')); - break; - case ArcanistDifferentialRevisionStatus::CLOSED: - $item->setDisabled(true); - $item->setStatusIcon('fa-check-square-o black', pht('Closed')); - break; - case ArcanistDifferentialRevisionStatus::ABANDONED: - $item->setDisabled(true); - $item->setStatusIcon('fa-plane black', pht('Abandoned')); - break; + if ($revision->isClosed()) { + $item->setDisabled(true); } + $item->setStatusIcon( + $revision->getStatusIcon(), + $revision->getStatusDisplayName()); + $list->addItem($item); } diff --git a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php index 5ce766afb9..2d311fd0bd 100644 --- a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php +++ b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php @@ -303,10 +303,7 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView { $show_diff, )); - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Revision Update History')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($content); + return $content; } const STAR_NONE = 'none'; diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index e9ab15287c..1ee2ccedad 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -968,26 +968,21 @@ final class DiffusionCommitController extends DiffusionController { ->setWorkflow(!$can_edit); $curtain->addAction($action); - require_celerity_resource('phabricator-object-selector-css'); - require_celerity_resource('javelin-behavior-phabricator-object-selector'); - - $maniphest = 'PhabricatorManiphestApplication'; - if (PhabricatorApplication::isClassInstalled($maniphest)) { - $action = id(new PhabricatorActionView()) - ->setName(pht('Edit Maniphest Tasks')) - ->setIcon('fa-anchor') - ->setHref('/search/attach/'.$commit->getPHID().'/TASK/edge/') - ->setWorkflow(true) - ->setDisabled(!$can_edit); - $curtain->addAction($action); - } - $action = id(new PhabricatorActionView()) ->setName(pht('Download Raw Diff')) ->setHref($request->getRequestURI()->alter('diff', true)) ->setIcon('fa-download'); $curtain->addAction($action); + $relationship_list = PhabricatorObjectRelationshipList::newForObject( + $viewer, + $commit); + + $relationship_submenu = $relationship_list->newActionMenu(); + if ($relationship_submenu) { + $curtain->addAction($relationship_submenu); + } + return $curtain; } diff --git a/src/applications/diffusion/herald/DiffusionCommitRevisionAcceptedHeraldField.php b/src/applications/diffusion/herald/DiffusionCommitRevisionAcceptedHeraldField.php index 10d9c29c92..810b58f2a1 100644 --- a/src/applications/diffusion/herald/DiffusionCommitRevisionAcceptedHeraldField.php +++ b/src/applications/diffusion/herald/DiffusionCommitRevisionAcceptedHeraldField.php @@ -19,10 +19,31 @@ final class DiffusionCommitRevisionAcceptedHeraldField return null; } + $status = $revision->getStatus(); + + switch ($status) { + case ArcanistDifferentialRevisionStatus::ACCEPTED: + return $revision->getPHID(); + case ArcanistDifferentialRevisionStatus::CLOSED: + if ($revision->hasRevisionProperty( + DifferentialRevision::PROPERTY_CLOSED_FROM_ACCEPTED)) { + + if ($revision->getProperty( + DifferentialRevision::PROPERTY_CLOSED_FROM_ACCEPTED)) { + return $revision->getPHID(); + } else { + return null; + } + } else { + // continue on to old-style precommitRevisionStatus + break; + } + default: + return null; + } + $data = $object->getCommitData(); - $status = $data->getCommitDetail( - 'precommitRevisionStatus', - $revision->getStatus()); + $status = $data->getCommitDetail('precommitRevisionStatus'); switch ($status) { case ArcanistDifferentialRevisionStatus::ACCEPTED: diff --git a/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptedHeraldField.php b/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptedHeraldField.php index 864cc043af..e19878e627 100644 --- a/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptedHeraldField.php +++ b/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptedHeraldField.php @@ -20,12 +20,19 @@ final class DiffusionPreCommitContentRevisionAcceptedHeraldField return null; } - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; - if ($revision->getStatus() != $status_accepted) { - return null; + switch ($revision->getStatus()) { + case ArcanistDifferentialRevisionStatus::ACCEPTED: + return $revision->getPHID(); + case ArcanistDifferentialRevisionStatus::CLOSED: + if ($revision->getProperty( + DifferentialRevision::PROPERTY_CLOSED_FROM_ACCEPTED)) { + + return $revision->getPHID(); + } + break; } - return $revision->getPHID(); + return null; } protected function getHeraldFieldStandardType() { diff --git a/src/applications/diffusion/relationships/DiffusionCommitHasRevisionRelationship.php b/src/applications/diffusion/relationships/DiffusionCommitHasRevisionRelationship.php new file mode 100644 index 0000000000..226684c8f2 --- /dev/null +++ b/src/applications/diffusion/relationships/DiffusionCommitHasRevisionRelationship.php @@ -0,0 +1,40 @@ +getViewer(); + + $has_app = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorDiffusionApplication', + $viewer); + if (!$has_app) { + return false; + } + + return ($object instanceof PhabricatorRepositoryCommit); + } + +} diff --git a/src/applications/diffusion/view/DiffusionHistoryTableView.php b/src/applications/diffusion/view/DiffusionHistoryTableView.php index a9fb34e683..80e8aacf40 100644 --- a/src/applications/diffusion/view/DiffusionHistoryTableView.php +++ b/src/applications/diffusion/view/DiffusionHistoryTableView.php @@ -82,7 +82,10 @@ final class DiffusionHistoryTableView extends DiffusionView { $graph = null; if ($this->parents) { - $graph = $this->renderGraph(); + $graph = id(new PHUIDiffGraphView()) + ->setIsHead($this->isHead) + ->setIsTail($this->isTail) + ->renderGraph($this->parents); } $show_builds = PhabricatorApplication::isClassInstalledForViewer( @@ -219,166 +222,4 @@ final class DiffusionHistoryTableView extends DiffusionView { return $view->render(); } - /** - * Draw a merge/branch graph from the parent revision data. We're basically - * building up a bunch of strings like this: - * - * ^ - * |^ - * o| - * |o - * o - * - * ...which form an ASCII representation of the graph we eventually want to - * draw. - * - * NOTE: The actual implementation is black magic. - */ - private function renderGraph() { - // This keeps our accumulated information about each line of the - // merge/branch graph. - $graph = array(); - - // This holds the next commit we're looking for in each column of the - // graph. - $threads = array(); - - // This is the largest number of columns any row has, i.e. the width of - // the graph. - $count = 0; - - foreach ($this->history as $key => $history) { - $joins = array(); - $splits = array(); - - $parent_list = $this->parents[$history->getCommitIdentifier()]; - - // Look for some thread which has this commit as the next commit. If - // we find one, this commit goes on that thread. Otherwise, this commit - // goes on a new thread. - - $line = ''; - $found = false; - $pos = count($threads); - for ($n = 0; $n < $count; $n++) { - if (empty($threads[$n])) { - $line .= ' '; - continue; - } - - if ($threads[$n] == $history->getCommitIdentifier()) { - if ($found) { - $line .= ' '; - $joins[] = $n; - unset($threads[$n]); - } else { - $line .= 'o'; - $found = true; - $pos = $n; - } - } else { - - // We render a "|" for any threads which have a commit that we haven't - // seen yet, this is later drawn as a vertical line. - $line .= '|'; - } - } - - // If we didn't find the thread this commit goes on, start a new thread. - // We use "o" to mark the commit for the rendering engine, or "^" to - // indicate that there's nothing after it so the line from the commit - // upward should not be drawn. - - if (!$found) { - if ($this->isHead) { - $line .= '^'; - } else { - $line .= 'o'; - foreach ($graph as $k => $meta) { - // Go back across all the lines we've already drawn and add a - // "|" to the end, since this is connected to some future commit - // we don't know about. - for ($jj = strlen($meta['line']); $jj <= $count; $jj++) { - $graph[$k]['line'] .= '|'; - } - } - } - } - - // Update the next commit on this thread to the commit's first parent. - // This might have the effect of making a new thread. - $threads[$pos] = head($parent_list); - - // If we made a new thread, increase the thread count. - $count = max($pos + 1, $count); - - // Now, deal with splits (merges). I picked this terms opposite to the - // underlying repository term to confuse you. - foreach (array_slice($parent_list, 1) as $parent) { - $found = false; - - // Try to find the other parent(s) in our existing threads. If we find - // them, split to that thread. - - foreach ($threads as $idx => $thread_commit) { - if ($thread_commit == $parent) { - $found = true; - $splits[] = $idx; - } - } - - // If we didn't find the parent, we don't know about it yet. Find the - // first free thread and add it as the "next" commit in that thread. - // This might create a new thread. - - if (!$found) { - for ($n = 0; $n < $count; $n++) { - if (empty($threads[$n])) { - break; - } - } - $threads[$n] = $parent; - $splits[] = $n; - $count = max($n + 1, $count); - } - } - - $graph[] = array( - 'line' => $line, - 'split' => $splits, - 'join' => $joins, - ); - } - - // If this is the last page in history, replace the "o" with an "x" so we - // do not draw a connecting line downward, and replace "^" with an "X" for - // repositories with exactly one commit. - if ($this->isTail && $graph) { - $last = array_pop($graph); - $last['line'] = str_replace('o', 'x', $last['line']); - $last['line'] = str_replace('^', 'X', $last['line']); - $graph[] = $last; - } - - // Render into tags for the behavior. - - foreach ($graph as $k => $meta) { - $graph[$k] = javelin_tag( - 'div', - array( - 'sigil' => 'commit-graph', - 'meta' => $meta, - ), - ''); - } - - Javelin::initBehavior( - 'diffusion-commit-graph', - array( - 'count' => $count, - )); - - return $graph; - } - } diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index 7166b0bef6..1583d28df2 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -45,12 +45,27 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $locks = $this->buildLocksTab($lease->getPHID()); $commands = $this->buildCommandsTab($lease->getPHID()); + $tab_group = id(new PHUITabGroupView()) + ->addTab( + id(new PHUITabView()) + ->setName(pht('Properties')) + ->setKey('properties') + ->appendChild($properties)) + ->addTab( + id(new PHUITabView()) + ->setName(pht('Slot Locks')) + ->setKey('locks') + ->appendChild($locks)) + ->addTab( + id(new PHUITabView()) + ->setName(pht('Commands')) + ->setKey('commands') + ->appendChild($commands)); + $object_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Properties')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($properties, pht('Properties')) - ->addPropertyList($locks, pht('Slot Locks')) - ->addPropertyList($commands, pht('Commands')); + ->addTabGroup($tab_group); $view = id(new PHUITwoColumnView()) ->setHeader($header) diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index c2ab4337f5..c6771007ba 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -49,14 +49,30 @@ final class DrydockResourceViewController extends DrydockResourceController { $locks = $this->buildLocksTab($resource->getPHID()); $commands = $this->buildCommandsTab($resource->getPHID()); - $lease_box = $this->buildLeaseBox($resource); + + $tab_group = id(new PHUITabGroupView()) + ->addTab( + id(new PHUITabView()) + ->setName(pht('Properties')) + ->setKey('properties') + ->appendChild($properties)) + ->addTab( + id(new PHUITabView()) + ->setName(pht('Slot Locks')) + ->setKey('locks') + ->appendChild($locks)) + ->addTab( + id(new PHUITabView()) + ->setName(pht('Commands')) + ->setKey('commands') + ->appendChild($commands)); $object_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Properties')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($properties, pht('Properties')) - ->addPropertyList($locks, pht('Slot Locks')) - ->addPropertyList($commands, pht('Commands')); + ->addTabGroup($tab_group); + + $lease_box = $this->buildLeaseBox($resource); $view = id(new PHUITwoColumnView()) ->setHeader($header) diff --git a/src/applications/files/conduit/FileUploadChunkConduitAPIMethod.php b/src/applications/files/conduit/FileUploadChunkConduitAPIMethod.php index e3444d05e2..949bdde28a 100644 --- a/src/applications/files/conduit/FileUploadChunkConduitAPIMethod.php +++ b/src/applications/files/conduit/FileUploadChunkConduitAPIMethod.php @@ -51,6 +51,16 @@ final class FileUploadChunkConduitAPIMethod $start, $start + $length); + // If this is the initial chunk, leave the MIME type unset so we detect + // it and can update the parent file. If this is any other chunk, it has + // no meaningful MIME type. Provide a default type so we can avoid writing + // it to disk to perform MIME type detection. + if (!$start) { + $mime_type = null; + } else { + $mime_type = 'application/octet-stream'; + } + // NOTE: These files have a view policy which prevents normal access. They // are only accessed through the storage engine. $chunk_data = PhabricatorFile::newFromFileData( @@ -58,13 +68,26 @@ final class FileUploadChunkConduitAPIMethod array( 'name' => $file->getMonogram().'.chunk-'.$chunk->getID(), 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + 'mime-type' => $mime_type, )); $chunk->setDataFilePHID($chunk_data->getPHID())->save(); + $needs_update = false; + $missing = $this->loadAnyMissingChunk($viewer, $file); if (!$missing) { - $file->setIsPartial(0)->save(); + $file->setIsPartial(0); + $needs_update = true; + } + + if (!$start) { + $file->setMimeType($chunk_data->getMimeType()); + $needs_update = true; + } + + if ($needs_update) { + $file->save(); } return null; diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php index 9633a3f956..3c822b401c 100644 --- a/src/applications/files/controller/PhabricatorFileInfoController.php +++ b/src/applications/files/controller/PhabricatorFileInfoController.php @@ -182,8 +182,16 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { $request = $this->getRequest(); $viewer = $request->getUser(); + $tab_group = id(new PHUITabGroupView()); + $box->addTabGroup($tab_group); + $properties = id(new PHUIPropertyListView()); - $box->addPropertyList($properties, pht('Details')); + + $tab_group->addTab( + id(new PHUITabView()) + ->setName(pht('Details')) + ->setKey('details') + ->appendChild($properties)); if ($file->getAuthorPHID()) { $properties->addProperty( @@ -195,9 +203,13 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { pht('Created'), phabricator_datetime($file->getDateCreated(), $viewer)); - $finfo = id(new PHUIPropertyListView()); - $box->addPropertyList($finfo, pht('File Info')); + + $tab_group->addTab( + id(new PHUITabView()) + ->setName(pht('File Info')) + ->setKey('info') + ->appendChild($finfo)); $finfo->addProperty( pht('Size'), @@ -262,7 +274,12 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { } $storage_properties = new PHUIPropertyListView(); - $box->addPropertyList($storage_properties, pht('Storage')); + + $tab_group->addTab( + id(new PHUITabView()) + ->setName(pht('Storage')) + ->setKey('storage') + ->appendChild($storage_properties)); $storage_properties->addProperty( pht('Engine'), @@ -285,7 +302,12 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { $phids = $file->getObjectPHIDs(); if ($phids) { $attached = new PHUIPropertyListView(); - $box->addPropertyList($attached, pht('Attached')); + + $tab_group->addTab( + id(new PHUITabView()) + ->setName(pht('Attached')) + ->setKey('attached') + ->appendChild($attached)); $attached->addProperty( pht('Attached To'), @@ -357,7 +379,12 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { if ($engine) { if ($engine->isChunkEngine()) { $chunkinfo = new PHUIPropertyListView(); - $box->addPropertyList($chunkinfo, pht('Chunks')); + + $tab_group->addTab( + id(new PHUITabView()) + ->setName(pht('Chunks')) + ->setKey('chunks') + ->appendChild($chunkinfo)); $chunks = id(new PhabricatorFileChunkQuery()) ->setViewer($viewer) diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 4e43e3dd18..7e8f4b18fc 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -270,10 +270,8 @@ final class PhabricatorFile extends PhabricatorFileDAO $file->setByteSize($length); - // TODO: We might be able to test the first chunk in order to figure - // this out more reliably, since MIME detection usually examines headers. - // However, enormous files are probably always either actually raw data - // or reasonable to treat like raw data. + // NOTE: Once we receive the first chunk, we'll detect its MIME type and + // update the parent file. This matters for large media files like video. $file->setMimeType('application/octet-stream'); $chunked_hash = idx($params, 'chunkedHash'); diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php index 6b0e7c1e0d..f78db68149 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php @@ -95,6 +95,9 @@ final class HarbormasterBuildViewController ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setHeader($header); + $tab_group = new PHUITabGroupView(); + $target_box->addTabGroup($tab_group); + $property_list = new PHUIPropertyListView(); $target_artifacts = idx($artifacts, $build_target->getPHID(), array()); @@ -178,7 +181,11 @@ final class HarbormasterBuildViewController $property_list->addProperty(pht('Status'), $status_view); - $target_box->addPropertyList($property_list, pht('Overview')); + $tab_group->addTab( + id(new PHUITabView()) + ->setName(pht('Overview')) + ->setKey('overview') + ->appendChild($property_list)); $step = $build_target->getBuildStep(); @@ -204,22 +211,34 @@ final class HarbormasterBuildViewController foreach ($details as $key => $value) { $property_list->addProperty($key, $value); } - $target_box->addPropertyList($property_list, pht('Configuration')); + $tab_group->addTab( + id(new PHUITabView()) + ->setName(pht('Configuration')) + ->setKey('configuration') + ->appendChild($property_list)); $variables = $build_target->getVariables(); - $property_list = new PHUIPropertyListView(); - $property_list->addRawContent($this->buildProperties($variables)); - $target_box->addPropertyList($property_list, pht('Variables')); + $variables_tab = $this->buildProperties($variables); + $tab_group->addTab( + id(new PHUITabView()) + ->setName(pht('Variables')) + ->setKey('variables') + ->appendChild($variables_tab)); $artifacts_tab = $this->buildArtifacts($build_target, $target_artifacts); - $property_list = new PHUIPropertyListView(); - $property_list->addRawContent($artifacts_tab); - $target_box->addPropertyList($property_list, pht('Artifacts')); + $tab_group->addTab( + id(new PHUITabView()) + ->setName(pht('Artifacts')) + ->setKey('artifacts') + ->appendChild($artifacts_tab)); $build_messages = idx($messages, $build_target->getPHID(), array()); - $property_list = new PHUIPropertyListView(); - $property_list->addRawContent($this->buildMessages($build_messages)); - $target_box->addPropertyList($property_list, pht('Messages')); + $messages_tab = $this->buildMessages($build_messages); + $tab_group->addTab( + id(new PHUITabView()) + ->setName(pht('Messages')) + ->setKey('messages') + ->appendChild($messages_tab)); $property_list = new PHUIPropertyListView(); $property_list->addProperty( @@ -228,7 +247,12 @@ final class HarbormasterBuildViewController $property_list->addProperty( pht('Build Target PHID'), $build_target->getPHID()); - $target_box->addPropertyList($property_list, pht('Metadata')); + + $tab_group->addTab( + id(new PHUITabView()) + ->setName(pht('Metadata')) + ->setKey('metadata') + ->appendChild($property_list)); $targets[] = $target_box; diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index bc862a1122..f544fa6b2c 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -31,8 +31,6 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setTargetObject($task); $e_commit = ManiphestTaskHasCommitEdgeType::EDGECONST; - $e_dep_on = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; - $e_dep_by = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST; $e_rev = ManiphestTaskHasRevisionEdgeType::EDGECONST; $e_mock = ManiphestTaskHasMockEdgeType::EDGECONST; @@ -43,8 +41,6 @@ final class ManiphestTaskDetailController extends ManiphestController { ->withEdgeTypes( array( $e_commit, - $e_dep_on, - $e_dep_by, $e_rev, $e_mock, )); @@ -91,6 +87,15 @@ final class ManiphestTaskDetailController extends ManiphestController { ->addPropertySection(pht('Description'), $description) ->addPropertySection(pht('Details'), $details); + $task_graph = id(new ManiphestTaskGraph()) + ->setViewer($viewer) + ->setSeedPHID($task->getPHID()) + ->loadGraph(); + if (!$task_graph->isEmpty()) { + $graph_table = $task_graph->newGraphTable(); + $view->addPropertySection(pht('Task Graph'), $graph_table); + } + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) @@ -186,9 +191,7 @@ final class ManiphestTaskDetailController extends ManiphestController { $edit_uri = $this->getApplicationURI($edit_uri); } - $task_submenu = array(); - - $task_submenu[] = id(new PhabricatorActionView()) + $subtask_item = id(new PhabricatorActionView()) ->setName(pht('Create Subtask')) ->setHref($edit_uri) ->setIcon('fa-level-down') @@ -199,27 +202,19 @@ final class ManiphestTaskDetailController extends ManiphestController { $viewer, $task); - $parent_key = ManiphestTaskHasParentRelationship::RELATIONSHIPKEY; - $subtask_key = ManiphestTaskHasSubtaskRelationship::RELATIONSHIPKEY; + $submenu_actions = array( + $subtask_item, + ManiphestTaskHasParentRelationship::RELATIONSHIPKEY, + ManiphestTaskHasSubtaskRelationship::RELATIONSHIPKEY, + ManiphestTaskMergeInRelationship::RELATIONSHIPKEY, + ManiphestTaskCloseAsDuplicateRelationship::RELATIONSHIPKEY, + ); - $task_submenu[] = $relationship_list->getRelationship($parent_key) - ->newAction($task); + $task_submenu = $relationship_list->newActionSubmenu($submenu_actions) + ->setName(pht('Edit Related Tasks...')) + ->setIcon('fa-anchor'); - $task_submenu[] = $relationship_list->getRelationship($subtask_key) - ->newAction($task); - - $task_submenu[] = id(new PhabricatorActionView()) - ->setName(pht('Merge Duplicates In')) - ->setHref("/search/attach/{$phid}/TASK/merge/") - ->setIcon('fa-compress') - ->setDisabled(!$can_edit) - ->setWorkflow(true); - - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Related Tasks...')) - ->setIcon('fa-anchor') - ->setSubmenu($task_submenu)); + $curtain->addAction($task_submenu); $relationship_submenu = $relationship_list->newActionMenu(); if ($relationship_submenu) { @@ -289,10 +284,6 @@ final class ManiphestTaskDetailController extends ManiphestController { } $edge_types = array( - ManiphestTaskDependedOnByTaskEdgeType::EDGECONST - => pht('Parent Tasks'), - ManiphestTaskDependsOnTaskEdgeType::EDGECONST - => pht('Subtasks'), ManiphestTaskHasRevisionEdgeType::EDGECONST => pht('Differential Revisions'), ManiphestTaskHasMockEdgeType::EDGECONST diff --git a/src/applications/maniphest/edge/ManiphestTaskHasDuplicateTaskEdgeType.php b/src/applications/maniphest/edge/ManiphestTaskHasDuplicateTaskEdgeType.php new file mode 100644 index 0000000000..b5f5b44ae3 --- /dev/null +++ b/src/applications/maniphest/edge/ManiphestTaskHasDuplicateTaskEdgeType.php @@ -0,0 +1,16 @@ +setSelectedFilter('open'); + } + + public function getRequiredRelationshipCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function canUndoRelationship() { + return false; + } + + public function getMaximumSelectionSize() { + return 1; + } + + public function willUpdateRelationships($object, array $add, array $rem) { + $task = head($add); + return $this->newMergeIntoTransactions($task); + } + + public function didUpdateRelationships($object, array $add, array $rem) { + $viewer = $this->getViewer(); + $content_source = $this->getContentSource(); + + $task = head($add); + $xactions = $this->newMergeFromTransactions(array($object)); + + $task->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSource($content_source) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true) + ->applyTransactions($task, $xactions); + } + +} diff --git a/src/applications/maniphest/relationship/ManiphestTaskHasCommitRelationship.php b/src/applications/maniphest/relationship/ManiphestTaskHasCommitRelationship.php index 4fb35e46c9..e21f15a11c 100644 --- a/src/applications/maniphest/relationship/ManiphestTaskHasCommitRelationship.php +++ b/src/applications/maniphest/relationship/ManiphestTaskHasCommitRelationship.php @@ -17,13 +17,6 @@ final class ManiphestTaskHasCommitRelationship return 'fa-code'; } - public function shouldAppearInActionMenu() { - // TODO: For now, the default search for commits is not very good, so - // it is hard to find objects to link to. Until that works better, just - // hide this item. - return false; - } - public function canRelateObjects($src, $dst) { return ($dst instanceof PhabricatorRepositoryCommit); } @@ -40,4 +33,8 @@ final class ManiphestTaskHasCommitRelationship return pht('Save Related Commits'); } + protected function newRelationshipSource() { + return new DiffusionCommitRelationshipSource(); + } + } diff --git a/src/applications/maniphest/relationship/ManiphestTaskHasMockRelationship.php b/src/applications/maniphest/relationship/ManiphestTaskHasMockRelationship.php index 4425d1c3e9..830da4ba01 100644 --- a/src/applications/maniphest/relationship/ManiphestTaskHasMockRelationship.php +++ b/src/applications/maniphest/relationship/ManiphestTaskHasMockRelationship.php @@ -33,4 +33,8 @@ final class ManiphestTaskHasMockRelationship return pht('Save Related Mocks'); } + protected function newRelationshipSource() { + return new PholioMockRelationshipSource(); + } + } diff --git a/src/applications/maniphest/relationship/ManiphestTaskHasParentRelationship.php b/src/applications/maniphest/relationship/ManiphestTaskHasParentRelationship.php index 8d7d3a539f..6cfd11ff09 100644 --- a/src/applications/maniphest/relationship/ManiphestTaskHasParentRelationship.php +++ b/src/applications/maniphest/relationship/ManiphestTaskHasParentRelationship.php @@ -37,4 +37,9 @@ final class ManiphestTaskHasParentRelationship return pht('Save Parent Tasks'); } + protected function newRelationshipSource() { + return id(new ManiphestTaskRelationshipSource()) + ->setSelectedFilter('open'); + } + } diff --git a/src/applications/maniphest/relationship/ManiphestTaskHasRevisionRelationship.php b/src/applications/maniphest/relationship/ManiphestTaskHasRevisionRelationship.php index 4530940191..f1c8796f50 100644 --- a/src/applications/maniphest/relationship/ManiphestTaskHasRevisionRelationship.php +++ b/src/applications/maniphest/relationship/ManiphestTaskHasRevisionRelationship.php @@ -33,4 +33,8 @@ final class ManiphestTaskHasRevisionRelationship return pht('Save Related Revisions'); } + protected function newRelationshipSource() { + return new DifferentialRevisionRelationshipSource(); + } + } diff --git a/src/applications/maniphest/relationship/ManiphestTaskHasSubtaskRelationship.php b/src/applications/maniphest/relationship/ManiphestTaskHasSubtaskRelationship.php index 9a1f696f6b..2fb69e90fb 100644 --- a/src/applications/maniphest/relationship/ManiphestTaskHasSubtaskRelationship.php +++ b/src/applications/maniphest/relationship/ManiphestTaskHasSubtaskRelationship.php @@ -37,4 +37,9 @@ final class ManiphestTaskHasSubtaskRelationship return pht('Save Subtasks'); } + protected function newRelationshipSource() { + return id(new ManiphestTaskRelationshipSource()) + ->setSelectedFilter('open'); + } + } diff --git a/src/applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php b/src/applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php new file mode 100644 index 0000000000..c8442c428f --- /dev/null +++ b/src/applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php @@ -0,0 +1,76 @@ +setSelectedFilter('open'); + } + + public function getRequiredRelationshipCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function canUndoRelationship() { + return false; + } + + public function willUpdateRelationships($object, array $add, array $rem) { + return $this->newMergeFromTransactions($add); + } + + public function didUpdateRelationships($object, array $add, array $rem) { + $viewer = $this->getViewer(); + $content_source = $this->getContentSource(); + + foreach ($add as $task) { + $xactions = $this->newMergeIntoTransactions($object); + + $task->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSource($content_source) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true) + ->applyTransactions($task, $xactions); + } + } + +} diff --git a/src/applications/maniphest/relationship/ManiphestTaskRelationship.php b/src/applications/maniphest/relationship/ManiphestTaskRelationship.php index a0383d77f4..928d49e87d 100644 --- a/src/applications/maniphest/relationship/ManiphestTaskRelationship.php +++ b/src/applications/maniphest/relationship/ManiphestTaskRelationship.php @@ -16,4 +16,52 @@ abstract class ManiphestTaskRelationship return ($object instanceof ManiphestTask); } + protected function newMergeIntoTransactions(ManiphestTask $task) { + return array( + id(new ManiphestTransaction()) + ->setTransactionType(ManiphestTransaction::TYPE_MERGED_INTO) + ->setNewValue($task->getPHID()), + ); + } + + protected function newMergeFromTransactions(array $tasks) { + $xactions = array(); + + $subscriber_phids = $this->loadMergeSubscriberPHIDs($tasks); + + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) + ->setNewValue(array('+' => $subscriber_phids)); + + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(ManiphestTransaction::TYPE_MERGED_FROM) + ->setNewValue(mpull($tasks, 'getPHID')); + + return $xactions; + } + + private function loadMergeSubscriberPHIDs(array $tasks) { + $phids = array(); + + foreach ($tasks as $task) { + $phids[] = $task->getAuthorPHID(); + $phids[] = $task->getOwnerPHID(); + } + + $subscribers = id(new PhabricatorSubscribersQuery()) + ->withObjectPHIDs(mpull($tasks, 'getPHID')) + ->execute(); + + foreach ($subscribers as $phid => $subscriber_list) { + foreach ($subscriber_list as $subscriber) { + $phids[] = $subscriber; + } + } + + $phids = array_unique($phids); + $phids = array_filter($phids); + + return $phids; + } + } diff --git a/src/applications/maniphest/storage/ManiphestTask.php b/src/applications/maniphest/storage/ManiphestTask.php index 9ac7190782..4fb9fc2861 100644 --- a/src/applications/maniphest/storage/ManiphestTask.php +++ b/src/applications/maniphest/storage/ManiphestTask.php @@ -179,6 +179,10 @@ final class ManiphestTask extends ManiphestDAO return 'T'.$this->getID(); } + public function getURI() { + return '/'.$this->getMonogram(); + } + public function attachGroupByProjectPHID($phid) { $this->groupByProjectPHID = $phid; return $this; diff --git a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php index fda471b79f..80da535a9e 100644 --- a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php +++ b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php @@ -36,13 +36,32 @@ final class PhabricatorMetaMTAMailViewController ->addTextCrumb(pht('Mail %d', $mail->getID())) ->setBorder(true); + $tab_group = id(new PHUITabGroupView()) + ->addTab( + id(new PHUITabView()) + ->setName(pht('Message')) + ->setKey('message') + ->appendChild($this->buildMessageProperties($mail))) + ->addTab( + id(new PHUITabView()) + ->setName(pht('Headers')) + ->setKey('headers') + ->appendChild($this->buildHeaderProperties($mail))) + ->addTab( + id(new PHUITabView()) + ->setName(pht('Delivery')) + ->setKey('delivery') + ->appendChild($this->buildDeliveryProperties($mail))) + ->addTab( + id(new PHUITabView()) + ->setName(pht('Metadata')) + ->setKey('metadata') + ->appendChild($this->buildMetadataProperties($mail))); + $object_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Mail')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($this->buildMessageProperties($mail), pht('Message')) - ->addPropertyList($this->buildHeaderProperties($mail), pht('Headers')) - ->addPropertyList($this->buildDeliveryProperties($mail), pht('Delivery')) - ->addPropertyList($this->buildMetadataProperties($mail), pht('Metadata')); + ->addTabGroup($tab_group); $view = id(new PHUITwoColumnView()) ->setHeader($header) diff --git a/src/applications/phame/controller/PhameLiveController.php b/src/applications/phame/controller/PhameLiveController.php index ba2b65d9f8..1287a33f35 100644 --- a/src/applications/phame/controller/PhameLiveController.php +++ b/src/applications/phame/controller/PhameLiveController.php @@ -87,7 +87,7 @@ abstract class PhameLiveController extends PhameController { $this->isExternal = $is_external; $this->isLive = $is_live; - if ($post_id) { + if (strlen($post_id)) { $post_query = id(new PhamePostQuery()) ->setViewer($viewer) ->withIDs(array($post_id)); @@ -104,6 +104,8 @@ abstract class PhameLiveController extends PhameController { $post = $post_query->executeOne(); if (!$post) { + // Not a valid Post + $this->blog = $blog; return new Aphront404Response(); } diff --git a/src/applications/phame/controller/blog/PhameBlogHeaderPictureController.php b/src/applications/phame/controller/blog/PhameBlogHeaderPictureController.php index fbda0ab1ef..ab026fecbf 100644 --- a/src/applications/phame/controller/blog/PhameBlogHeaderPictureController.php +++ b/src/applications/phame/controller/blog/PhameBlogHeaderPictureController.php @@ -53,12 +53,25 @@ final class PhameBlogHeaderPictureController if (!$errors) { if ($delete_header) { - $blog->setHeaderImagePHID(null); + $new_value = null; } else { - $blog->setHeaderImagePHID($file->getPHID()); $file->attachToObject($blog->getPHID()); + $new_value = $file->getPHID(); } - $blog->save(); + + $xactions = array(); + $xactions[] = id(new PhameBlogTransaction()) + ->setTransactionType(PhameBlogTransaction::TYPE_HEADERIMAGE) + ->setNewValue($new_value); + + $editor = id(new PhameBlogEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($blog, $xactions); + return id(new AphrontRedirectResponse())->setURI($blog_uri); } } diff --git a/src/applications/phame/controller/blog/PhameBlogManageController.php b/src/applications/phame/controller/blog/PhameBlogManageController.php index 4085d3ef6b..2bdcbf7635 100644 --- a/src/applications/phame/controller/blog/PhameBlogManageController.php +++ b/src/applications/phame/controller/blog/PhameBlogManageController.php @@ -36,7 +36,8 @@ final class PhameBlogManageController extends PhameBlogController { ->setTag('a') ->setText(pht('View Live')) ->setIcon('fa-external-link') - ->setHref($blog->getLiveURI()); + ->setHref($blog->getLiveURI()) + ->setDisabled($blog->isArchived()); $header = id(new PHUIHeaderView()) ->setHeader($blog->getName()) @@ -46,6 +47,16 @@ final class PhameBlogManageController extends PhameBlogController { ->setStatus($header_icon, $header_color, $header_name) ->addActionLink($view); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $blog, + PhabricatorPolicyCapability::CAN_EDIT); + + if ($can_edit) { + $header->setImageEditURL( + $this->getApplicationURI('blog/picture/'.$blog->getID().'/')); + } + $curtain = $this->buildCurtain($blog); $properties = $this->buildPropertyView($blog); $file = $this->buildFileView($blog); diff --git a/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php b/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php index 96d59c8ad4..3368046414 100644 --- a/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php +++ b/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php @@ -68,12 +68,25 @@ final class PhameBlogProfilePictureController if (!$errors) { if ($is_default) { - $blog->setProfileImagePHID(null); + $new_value = null; } else { - $blog->setProfileImagePHID($xformed->getPHID()); $xformed->attachToObject($blog->getPHID()); + $new_value = $xformed->getPHID(); } - $blog->save(); + + $xactions = array(); + $xactions[] = id(new PhameBlogTransaction()) + ->setTransactionType(PhameBlogTransaction::TYPE_PROFILEIMAGE) + ->setNewValue($new_value); + + $editor = id(new PhameBlogEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($blog, $xactions); + return id(new AphrontRedirectResponse())->setURI($blog_uri); } } diff --git a/src/applications/phame/controller/blog/PhameBlogViewController.php b/src/applications/phame/controller/blog/PhameBlogViewController.php index 1e751c905b..08b23e5a95 100644 --- a/src/applications/phame/controller/blog/PhameBlogViewController.php +++ b/src/applications/phame/controller/blog/PhameBlogViewController.php @@ -139,6 +139,14 @@ final class PhameBlogViewController extends PhameLiveController { ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); + $actions->addAction( + id(new PhabricatorActionView()) + ->setUser($viewer) + ->setIcon('fa-search') + ->setHref( + $this->getApplicationURI('post/?blog='.$blog->getPHID())) + ->setName(pht('Search Posts'))); + $actions->addAction( id(new PhabricatorActionView()) ->setUser($viewer) diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php index d28f1b5150..f7dddebe53 100644 --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -118,7 +118,7 @@ final class PhamePostViewController array( $user_icon, ' ', - $blogger_profile->getTitle(), + $blogger_profile->getDisplayTitle(), )) ->setImage($blogger->getProfileImageURI()) ->setImageHref($author_uri); @@ -142,12 +142,16 @@ final class PhamePostViewController ->setUser($viewer) ->setObject($post); + $is_live = $this->getIsLive(); + $is_external = $this->getIsExternal(); $next_view = new PhameNextPostView(); if ($next) { - $next_view->setNext($next->getTitle(), $next->getLiveURI()); + $next_view->setNext($next->getTitle(), + $next->getBestURI($is_live, $is_external)); } if ($prev) { - $next_view->setPrevious($prev->getTitle(), $prev->getLiveURI()); + $next_view->setPrevious($prev->getTitle(), + $prev->getBestURI($is_live, $is_external)); } $document->setFoot($next_view); diff --git a/src/applications/phame/editor/PhameBlogEditEngine.php b/src/applications/phame/editor/PhameBlogEditEngine.php index 5f96309e8b..7d6511d55c 100644 --- a/src/applications/phame/editor/PhameBlogEditEngine.php +++ b/src/applications/phame/editor/PhameBlogEditEngine.php @@ -96,6 +96,10 @@ final class PhameBlogEditEngine id(new PhabricatorTextEditField()) ->setKey('domainFullURI') ->setLabel(pht('Full Domain URI')) + ->setControlInstructions(pht('Set Full Domain URI if you plan to '. + 'serve this blog on another hosted domain. Parent Site Name and '. + 'Parent Site URI are optional but helpful since they provide '. + 'a link from the blog back to your parent site.')) ->setDescription(pht('Blog full domain URI.')) ->setConduitDescription(pht('Change the blog full domain URI.')) ->setConduitTypeDescription(pht('New blog full domain URI.')) @@ -103,7 +107,7 @@ final class PhameBlogEditEngine ->setTransactionType(PhameBlogTransaction::TYPE_FULLDOMAIN), id(new PhabricatorTextEditField()) ->setKey('parentSite') - ->setLabel(pht('Parent Site')) + ->setLabel(pht('Parent Site Name')) ->setDescription(pht('Blog parent site name.')) ->setConduitDescription(pht('Change the blog parent site name.')) ->setConduitTypeDescription(pht('New blog parent site name.')) @@ -111,7 +115,7 @@ final class PhameBlogEditEngine ->setTransactionType(PhameBlogTransaction::TYPE_PARENTSITE), id(new PhabricatorTextEditField()) ->setKey('parentDomain') - ->setLabel(pht('Parent Domain')) + ->setLabel(pht('Parent Site URI')) ->setDescription(pht('Blog parent domain name.')) ->setConduitDescription(pht('Change the blog parent domain.')) ->setConduitTypeDescription(pht('New blog parent domain.')) diff --git a/src/applications/phame/editor/PhameBlogEditor.php b/src/applications/phame/editor/PhameBlogEditor.php index 197387985b..d075a750ea 100644 --- a/src/applications/phame/editor/PhameBlogEditor.php +++ b/src/applications/phame/editor/PhameBlogEditor.php @@ -21,6 +21,9 @@ final class PhameBlogEditor $types[] = PhameBlogTransaction::TYPE_PARENTSITE; $types[] = PhameBlogTransaction::TYPE_PARENTDOMAIN; $types[] = PhameBlogTransaction::TYPE_STATUS; + $types[] = PhameBlogTransaction::TYPE_HEADERIMAGE; + $types[] = PhameBlogTransaction::TYPE_PROFILEIMAGE; + $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -44,6 +47,10 @@ final class PhameBlogEditor return $object->getParentSite(); case PhameBlogTransaction::TYPE_PARENTDOMAIN: return $object->getParentDomain(); + case PhameBlogTransaction::TYPE_PROFILEIMAGE: + return $object->getProfileImagePHID(); + case PhameBlogTransaction::TYPE_HEADERIMAGE: + return $object->getHeaderImagePHID(); case PhameBlogTransaction::TYPE_STATUS: return $object->getStatus(); } @@ -59,7 +66,8 @@ final class PhameBlogEditor case PhameBlogTransaction::TYPE_DESCRIPTION: case PhameBlogTransaction::TYPE_STATUS: case PhameBlogTransaction::TYPE_PARENTSITE: - case PhameBlogTransaction::TYPE_PARENTDOMAIN: + case PhameBlogTransaction::TYPE_PROFILEIMAGE: + case PhameBlogTransaction::TYPE_HEADERIMAGE: return $xaction->getNewValue(); case PhameBlogTransaction::TYPE_FULLDOMAIN: $domain = $xaction->getNewValue(); @@ -92,6 +100,10 @@ final class PhameBlogEditor } $object->setDomainFullURI($new_value); return; + case PhameBlogTransaction::TYPE_PROFILEIMAGE: + return $object->setProfileImagePHID($xaction->getNewValue()); + case PhameBlogTransaction::TYPE_HEADERIMAGE: + return $object->setHeaderImagePHID($xaction->getNewValue()); case PhameBlogTransaction::TYPE_STATUS: return $object->setStatus($xaction->getNewValue()); case PhameBlogTransaction::TYPE_PARENTSITE: @@ -114,6 +126,8 @@ final class PhameBlogEditor case PhameBlogTransaction::TYPE_FULLDOMAIN: case PhameBlogTransaction::TYPE_PARENTSITE: case PhameBlogTransaction::TYPE_PARENTDOMAIN: + case PhameBlogTransaction::TYPE_HEADERIMAGE: + case PhameBlogTransaction::TYPE_PROFILEIMAGE: case PhameBlogTransaction::TYPE_STATUS: return; } diff --git a/src/applications/phame/query/PhamePostSearchEngine.php b/src/applications/phame/query/PhamePostSearchEngine.php index dbc248d8b2..8f81c9362e 100644 --- a/src/applications/phame/query/PhamePostSearchEngine.php +++ b/src/applications/phame/query/PhamePostSearchEngine.php @@ -18,25 +18,36 @@ final class PhamePostSearchEngine protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); - if (strlen($map['visibility'])) { - $query->withVisibility(array($map['visibility'])); + if ($map['visibility']) { + $query->withVisibility($map['visibility']); } + if ($map['blogPHIDs']) { + $query->withBlogPHIDs($map['blogPHIDs']); + } + return $query; } protected function buildCustomSearchFields() { return array( - id(new PhabricatorSearchSelectField()) + id(new PhabricatorSearchCheckboxesField()) ->setKey('visibility') ->setLabel(pht('Visibility')) ->setOptions( array( - '' => pht('All'), PhameConstants::VISIBILITY_PUBLISHED => pht('Published'), PhameConstants::VISIBILITY_DRAFT => pht('Draft'), PhameConstants::VISIBILITY_ARCHIVED => pht('Archived'), )), + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Blogs')) + ->setKey('blogPHIDs') + ->setAliases(array('blog', 'blogs', 'blogPHIDs')) + ->setDescription( + pht('Search for posts within certain blogs.')) + ->setDatasource(new PhameBlogDatasource()), + ); } @@ -63,13 +74,13 @@ final class PhamePostSearchEngine return $query; case 'live': return $query->setParameter( - 'visibility', PhameConstants::VISIBILITY_PUBLISHED); + 'visibility', array(PhameConstants::VISIBILITY_PUBLISHED)); case 'draft': return $query->setParameter( - 'visibility', PhameConstants::VISIBILITY_DRAFT); + 'visibility', array(PhameConstants::VISIBILITY_DRAFT)); case 'archived': return $query->setParameter( - 'visibility', PhameConstants::VISIBILITY_ARCHIVED); + 'visibility', array(PhameConstants::VISIBILITY_ARCHIVED)); } return parent::buildSavedQueryFromBuiltin($query_key); diff --git a/src/applications/phame/search/PhamePostFulltextEngine.php b/src/applications/phame/search/PhamePostFulltextEngine.php index 27a97ae4ba..4b41d5de7c 100644 --- a/src/applications/phame/search/PhamePostFulltextEngine.php +++ b/src/applications/phame/search/PhamePostFulltextEngine.php @@ -28,7 +28,6 @@ final class PhamePostFulltextEngine $post->getPHID(), PhabricatorPhamePostPHIDType::TYPECONST, PhabricatorTime::getNow()); - } } diff --git a/src/applications/phame/storage/PhameBlog.php b/src/applications/phame/storage/PhameBlog.php index 6d9c51071a..23ab671552 100644 --- a/src/applications/phame/storage/PhameBlog.php +++ b/src/applications/phame/storage/PhameBlog.php @@ -322,10 +322,12 @@ final class PhameBlog extends PhameDAO $this->openTransaction(); - $posts = id(new PhamePost()) - ->loadAllWhere('blogPHID = %s', $this->getPHID()); + $posts = id(new PhamePostQuery()) + ->setViewer($engine->getViewer()) + ->withBlogPHIDs(array($this->getPHID())) + ->execute(); foreach ($posts as $post) { - $post->delete(); + $engine->destroyObject($post); } $this->delete(); diff --git a/src/applications/phame/storage/PhameBlogTransaction.php b/src/applications/phame/storage/PhameBlogTransaction.php index 2d74ca5cc6..f08c7be688 100644 --- a/src/applications/phame/storage/PhameBlogTransaction.php +++ b/src/applications/phame/storage/PhameBlogTransaction.php @@ -3,13 +3,15 @@ final class PhameBlogTransaction extends PhabricatorApplicationTransaction { - const TYPE_NAME = 'phame.blog.name'; - const TYPE_SUBTITLE = 'phame.blog.subtitle'; - const TYPE_DESCRIPTION = 'phame.blog.description'; - const TYPE_FULLDOMAIN = 'phame.blog.full.domain'; - const TYPE_STATUS = 'phame.blog.status'; - const TYPE_PARENTSITE = 'phame.blog.parent.site'; - const TYPE_PARENTDOMAIN = 'phame.blog.parent.domain'; + const TYPE_NAME = 'phame.blog.name'; + const TYPE_SUBTITLE = 'phame.blog.subtitle'; + const TYPE_DESCRIPTION = 'phame.blog.description'; + const TYPE_FULLDOMAIN = 'phame.blog.full.domain'; + const TYPE_STATUS = 'phame.blog.status'; + const TYPE_PARENTSITE = 'phame.blog.parent.site'; + const TYPE_PARENTDOMAIN = 'phame.blog.parent.domain'; + const TYPE_PROFILEIMAGE = 'phame.blog.header.image'; + const TYPE_HEADERIMAGE = 'phame.blog.profile.image'; const MAILTAG_DETAILS = 'phame-blog-details'; const MAILTAG_SUBSCRIBERS = 'phame-blog-subscribers'; @@ -34,6 +36,22 @@ final class PhameBlogTransaction return parent::shouldHide(); } + public function getRequiredHandlePHIDs() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $req_phids = array(); + switch ($this->getTransactionType()) { + case self::TYPE_PROFILEIMAGE: + case self::TYPE_HEADERIMAGE: + $req_phids[] = $old; + $req_phids[] = $new; + break; + } + + return array_merge($req_phids, parent::getRequiredHandlePHIDs()); + } + public function getIcon() { $old = $this->getOldValue(); $new = $this->getNewValue(); @@ -48,6 +66,10 @@ final class PhameBlogTransaction case self::TYPE_DESCRIPTION: case self::TYPE_FULLDOMAIN: return 'fa-pencil'; + case self::TYPE_HEADERIMAGE: + return 'fa-image'; + case self::TYPE_PROFILEIMAGE: + return 'fa-star'; case self::TYPE_STATUS: if ($new == PhameBlog::STATUS_ARCHIVED) { return 'fa-ban'; @@ -88,6 +110,8 @@ final class PhameBlogTransaction case self::TYPE_FULLDOMAIN: case self::TYPE_PARENTSITE: case self::TYPE_PARENTDOMAIN: + case self::TYPE_PROFILEIMAGE: + case self::TYPE_HEADERIMAGE: $tags[] = self::MAILTAG_DETAILS; break; default: @@ -172,6 +196,42 @@ final class PhameBlogTransaction $new); } break; + case self::TYPE_HEADERIMAGE: + if (!$old) { + return pht( + "%s set this blog's header image to %s.", + $this->renderHandleLink($author_phid), + $this->renderHandleLink($new)); + } else if (!$new) { + return pht( + "%s removed this blog's header image.", + $this->renderHandleLink($author_phid)); + } else { + return pht( + "%s updated this blog's header image from %s to %s.", + $this->renderHandleLink($author_phid), + $this->renderHandleLink($old), + $this->renderHandleLink($new)); + } + break; + case self::TYPE_PROFILEIMAGE: + if (!$old) { + return pht( + "%s set this blog's profile image to %s.", + $this->renderHandleLink($author_phid), + $this->renderHandleLink($new)); + } else if (!$new) { + return pht( + "%s removed this blog's profile image.", + $this->renderHandleLink($author_phid)); + } else { + return pht( + "%s updated this blog's profile image from %s to %s.", + $this->renderHandleLink($author_phid), + $this->renderHandleLink($old), + $this->renderHandleLink($new)); + } + break; case self::TYPE_STATUS: switch ($new) { case PhameBlog::STATUS_ACTIVE: @@ -248,6 +308,18 @@ final class PhameBlogTransaction $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; + case self::TYPE_HEADERIMAGE: + return pht( + '%s updated the header image for %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + break; + case self::TYPE_PROFILEIMAGE: + return pht( + '%s updated the profile image for %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + break; case self::TYPE_STATUS: switch ($new) { case PhameBlog::STATUS_ACTIVE: diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index 2d8fc887c5..4c4acf4dc1 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -86,6 +86,18 @@ final class PhamePost extends PhameDAO return "/phame/post/view/{$id}/{$slug}/"; } + public function getBestURI($is_live, $is_external) { + if ($is_live) { + if ($is_external) { + return $this->getExternalLiveURI(); + } else { + return $this->getInternalLiveURI(); + } + } else { + return $this->getViewURI(); + } + } + public function getEditURI() { return '/phame/post/edit/'.$this->getID().'/'; } @@ -258,15 +270,15 @@ final class PhamePost extends PhameDAO return $timeline; } + /* -( PhabricatorDestructibleInterface )----------------------------------- */ + public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); - $this->delete(); - $this->saveTransaction(); } diff --git a/src/applications/phame/typeahead/PhameBlogDatasource.php b/src/applications/phame/typeahead/PhameBlogDatasource.php new file mode 100644 index 0000000000..0658dec3b9 --- /dev/null +++ b/src/applications/phame/typeahead/PhameBlogDatasource.php @@ -0,0 +1,53 @@ +getViewer(); + + $blogs = id(new PhameBlogQuery()) + ->setViewer($viewer) + ->needProfileImage(true) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->execute(); + + $results = array(); + foreach ($blogs as $blog) { + $closed = null; + + $status = $blog->getStatus(); + if ($status === PhabricatorBadgesBadge::STATUS_ARCHIVED) { + $closed = pht('Archived'); + } + + $results[] = id(new PhabricatorTypeaheadResult()) + ->setName($blog->getName()) + ->setClosed($closed) + ->addAttribute(pht('Phame Blog')) + ->setImageURI($blog->getProfileImageURI()) + ->setPHID($blog->getPHID()); + } + + $results = $this->filterResultsAgainstTokens($results); + + return $results; + } + +} diff --git a/src/applications/pholio/controller/PholioMockViewController.php b/src/applications/pholio/controller/PholioMockViewController.php index 906454c1f3..a1fb40c86b 100644 --- a/src/applications/pholio/controller/PholioMockViewController.php +++ b/src/applications/pholio/controller/PholioMockViewController.php @@ -150,13 +150,14 @@ final class PholioMockViewController extends PholioController { ->setWorkflow(true)); } - $curtain->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-anchor') - ->setName(pht('Edit Maniphest Tasks')) - ->setHref("/search/attach/{$mock->getPHID()}/TASK/edge/") - ->setDisabled(!$viewer->isLoggedIn()) - ->setWorkflow(true)); + $relationship_list = PhabricatorObjectRelationshipList::newForObject( + $viewer, + $mock); + + $relationship_submenu = $relationship_list->newActionMenu(); + if ($relationship_submenu) { + $curtain->addAction($relationship_submenu); + } if ($this->getManiphestTaskPHIDs()) { $curtain->newPanel() diff --git a/src/applications/pholio/relationships/PholioMockHasTaskRelationship.php b/src/applications/pholio/relationships/PholioMockHasTaskRelationship.php new file mode 100644 index 0000000000..69ba1616bc --- /dev/null +++ b/src/applications/pholio/relationships/PholioMockHasTaskRelationship.php @@ -0,0 +1,40 @@ +getViewer(); + + $has_app = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorPholioApplication', + $viewer); + if (!$has_app) { + return false; + } + + return ($object instanceof PholioMock); + } + +} diff --git a/src/applications/pholio/search/PholioMockFulltextEngine.php b/src/applications/pholio/search/PholioMockFulltextEngine.php index 9a162168db..6326cdfb77 100644 --- a/src/applications/pholio/search/PholioMockFulltextEngine.php +++ b/src/applications/pholio/search/PholioMockFulltextEngine.php @@ -20,6 +20,14 @@ final class PholioMockFulltextEngine $mock->getAuthorPHID(), PhabricatorPeopleUserPHIDType::TYPECONST, $mock->getDateCreated()); + + $document->addRelationship( + $mock->isClosed() + ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED + : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, + $mock->getPHID(), + PholioMockPHIDType::TYPECONST, + PhabricatorTime::getNow()); } } diff --git a/src/applications/repository/search/DiffusionCommitFulltextEngine.php b/src/applications/repository/search/DiffusionCommitFulltextEngine.php index dd87a8bda5..640cf03f43 100644 --- a/src/applications/repository/search/DiffusionCommitFulltextEngine.php +++ b/src/applications/repository/search/DiffusionCommitFulltextEngine.php @@ -47,5 +47,13 @@ final class DiffusionCommitFulltextEngine $repository->getPHID(), PhabricatorRepositoryRepositoryPHIDType::TYPECONST, $date_created); + + $document->addRelationship( + $commit->isUnreachable() + ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED + : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, + $commit->getPHID(), + PhabricatorRepositoryCommitPHIDType::TYPECONST, + PhabricatorTime::getNow()); } } diff --git a/src/applications/search/application/PhabricatorSearchApplication.php b/src/applications/search/application/PhabricatorSearchApplication.php index 36ad59764c..a2a28c7665 100644 --- a/src/applications/search/application/PhabricatorSearchApplication.php +++ b/src/applications/search/application/PhabricatorSearchApplication.php @@ -30,10 +30,6 @@ final class PhabricatorSearchApplication extends PhabricatorApplication { return array( '/search/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorSearchController', - 'attach/(?P[^/]+)/(?P\w+)/(?:(?P\w+)/)?' - => 'PhabricatorSearchAttachController', - 'select/(?P\w+)/(?:(?P\w+)/)?' - => 'PhabricatorSearchSelectController', 'index/(?P[^/]+)/' => 'PhabricatorSearchIndexController', 'hovercard/' => 'PhabricatorSearchHovercardController', @@ -43,6 +39,8 @@ final class PhabricatorSearchApplication extends PhabricatorApplication { 'order/(?P[^/]+)/' => 'PhabricatorSearchOrderController', 'rel/(?P[^/]+)/(?P[^/]+)/' => 'PhabricatorSearchRelationshipController', + 'source/(?P[^/]+)/(?P[^/]+)/' + => 'PhabricatorSearchRelationshipSourceController', ), ); } diff --git a/src/applications/search/controller/PhabricatorSearchAttachController.php b/src/applications/search/controller/PhabricatorSearchAttachController.php deleted file mode 100644 index a3238cb50d..0000000000 --- a/src/applications/search/controller/PhabricatorSearchAttachController.php +++ /dev/null @@ -1,330 +0,0 @@ -getUser(); - $phid = $request->getURIData('phid'); - $attach_type = $request->getURIData('type'); - $action = $request->getURIData('action', self::ACTION_ATTACH); - - $handle = id(new PhabricatorHandleQuery()) - ->setViewer($user) - ->withPHIDs(array($phid)) - ->executeOne(); - - $object_type = $handle->getType(); - - $object = id(new PhabricatorObjectQuery()) - ->setViewer($user) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withPHIDs(array($phid)) - ->executeOne(); - - if (!$object) { - return new Aphront404Response(); - } - - $edge_type = null; - switch ($action) { - case self::ACTION_EDGE: - case self::ACTION_DEPENDENCIES: - case self::ACTION_BLOCKS: - case self::ACTION_ATTACH: - $edge_type = $this->getEdgeType($object_type, $attach_type); - break; - } - - if ($request->isFormPost()) { - $phids = explode(';', $request->getStr('phids')); - $phids = array_filter($phids); - $phids = array_values($phids); - - if ($edge_type) { - if (!$object instanceof PhabricatorApplicationTransactionInterface) { - throw new Exception( - pht( - 'Expected object ("%s") to implement interface "%s".', - get_class($object), - 'PhabricatorApplicationTransactionInterface')); - } - - $old_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( - $phid, - $edge_type); - $add_phids = $phids; - $rem_phids = array_diff($old_phids, $add_phids); - - $txn_editor = $object->getApplicationTransactionEditor() - ->setActor($user) - ->setContentSourceFromRequest($request) - ->setContinueOnMissingFields(true) - ->setContinueOnNoEffect(true); - - $txn_template = $object->getApplicationTransactionTemplate() - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $edge_type) - ->setNewValue(array( - '+' => array_fuse($add_phids), - '-' => array_fuse($rem_phids), - )); - - try { - $txn_editor->applyTransactions( - $object->getApplicationTransactionObject(), - array($txn_template)); - } catch (PhabricatorEdgeCycleException $ex) { - $this->raiseGraphCycleException($ex); - } - - return id(new AphrontReloadResponse())->setURI($handle->getURI()); - } else { - return $this->performMerge($object, $handle, $phids); - } - } else { - if ($edge_type) { - $phids = PhabricatorEdgeQuery::loadDestinationPHIDs( - $phid, - $edge_type); - } else { - // This is a merge. - $phids = array(); - } - } - - $strings = $this->getStrings($attach_type, $action); - - $handles = $this->loadViewerHandles($phids); - - $obj_dialog = new PhabricatorObjectSelectorDialog(); - $obj_dialog - ->setUser($user) - ->setHandles($handles) - ->setFilters($this->getFilters($strings, $attach_type)) - ->setSelectedFilter($strings['selected']) - ->setExcluded($phid) - ->setCancelURI($handle->getURI()) - ->setSearchURI('/search/select/'.$attach_type.'/'.$action.'/') - ->setTitle($strings['title']) - ->setHeader($strings['header']) - ->setButtonText($strings['button']) - ->setInstructions($strings['instructions']); - - $dialog = $obj_dialog->buildDialog(); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - - private function performMerge( - ManiphestTask $task, - PhabricatorObjectHandle $handle, - array $phids) { - - $user = $this->getRequest()->getUser(); - $response = id(new AphrontReloadResponse())->setURI($handle->getURI()); - - $phids = array_fill_keys($phids, true); - unset($phids[$task->getPHID()]); // Prevent merging a task into itself. - - if (!$phids) { - return $response; - } - - $targets = id(new ManiphestTaskQuery()) - ->setViewer($user) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withPHIDs(array_keys($phids)) - ->needSubscriberPHIDs(true) - ->needProjectPHIDs(true) - ->execute(); - - if (empty($targets)) { - return $response; - } - - $editor = id(new ManiphestTransactionEditor()) - ->setActor($user) - ->setContentSourceFromRequest($this->getRequest()) - ->setContinueOnNoEffect(true) - ->setContinueOnMissingFields(true); - - $cc_vector = array(); - // since we loaded this via a generic object query, go ahead and get the - // attach the subscriber and project phids now - $task->attachSubscriberPHIDs( - PhabricatorSubscribersQuery::loadSubscribersForPHID($task->getPHID())); - $task->attachProjectPHIDs( - PhabricatorEdgeQuery::loadDestinationPHIDs($task->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST)); - - $cc_vector[] = $task->getSubscriberPHIDs(); - foreach ($targets as $target) { - $cc_vector[] = $target->getSubscriberPHIDs(); - $cc_vector[] = array( - $target->getAuthorPHID(), - $target->getOwnerPHID(), - ); - - $merged_into_txn = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_MERGED_INTO) - ->setNewValue($task->getPHID()); - - $editor->applyTransactions( - $target, - array($merged_into_txn)); - - } - $all_ccs = array_mergev($cc_vector); - $all_ccs = array_filter($all_ccs); - $all_ccs = array_unique($all_ccs); - - $add_ccs = id(new ManiphestTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) - ->setNewValue(array('=' => $all_ccs)); - - $merged_from_txn = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_MERGED_FROM) - ->setNewValue(mpull($targets, 'getPHID')); - - $editor->applyTransactions( - $task, - array($add_ccs, $merged_from_txn)); - - return $response; - } - - private function getStrings($attach_type, $action) { - switch ($attach_type) { - case DifferentialRevisionPHIDType::TYPECONST: - $noun = pht('Revisions'); - $selected = pht('created'); - break; - case ManiphestTaskPHIDType::TYPECONST: - $noun = pht('Tasks'); - $selected = pht('assigned'); - break; - case PhabricatorRepositoryCommitPHIDType::TYPECONST: - $noun = pht('Commits'); - $selected = pht('created'); - break; - case PholioMockPHIDType::TYPECONST: - $noun = pht('Mocks'); - $selected = pht('created'); - break; - } - - switch ($action) { - case self::ACTION_EDGE: - case self::ACTION_ATTACH: - $dialog_title = pht('Manage Attached %s', $noun); - $header_text = pht('Currently Attached %s', $noun); - $button_text = pht('Save %s', $noun); - $instructions = null; - break; - case self::ACTION_MERGE: - $dialog_title = pht('Merge Duplicate Tasks'); - $header_text = pht('Tasks To Merge'); - $button_text = pht('Merge %s', $noun); - $instructions = pht( - 'These tasks will be merged into the current task and then closed. '. - 'The current task will grow stronger.'); - break; - case self::ACTION_DEPENDENCIES: - $dialog_title = pht('Edit Dependencies'); - $header_text = pht('Current Dependencies'); - $button_text = pht('Save Dependencies'); - $instructions = null; - break; - case self::ACTION_BLOCKS: - $dialog_title = pht('Edit Blocking Tasks'); - $header_text = pht('Current Blocking Tasks'); - $button_text = pht('Save Blocking Tasks'); - $instructions = null; - break; - } - - return array( - 'target_plural_noun' => $noun, - 'selected' => $selected, - 'title' => $dialog_title, - 'header' => $header_text, - 'button' => $button_text, - 'instructions' => $instructions, - ); - } - - private function getFilters(array $strings, $attach_type) { - if ($attach_type == PholioMockPHIDType::TYPECONST) { - $filters = array( - 'created' => pht('Created By Me'), - 'all' => pht('All %s', $strings['target_plural_noun']), - ); - } else { - $filters = array( - 'assigned' => pht('Assigned to Me'), - 'created' => pht('Created By Me'), - 'open' => pht('All Open %s', $strings['target_plural_noun']), - 'all' => pht('All %s', $strings['target_plural_noun']), - ); - } - - return $filters; - } - - private function getEdgeType($src_type, $dst_type) { - $t_cmit = PhabricatorRepositoryCommitPHIDType::TYPECONST; - $t_task = ManiphestTaskPHIDType::TYPECONST; - $t_drev = DifferentialRevisionPHIDType::TYPECONST; - $t_mock = PholioMockPHIDType::TYPECONST; - - $map = array( - $t_cmit => array( - $t_task => DiffusionCommitHasTaskEdgeType::EDGECONST, - ), - $t_task => array( - $t_cmit => ManiphestTaskHasCommitEdgeType::EDGECONST, - $t_task => ManiphestTaskDependsOnTaskEdgeType::EDGECONST, - $t_drev => ManiphestTaskHasRevisionEdgeType::EDGECONST, - $t_mock => ManiphestTaskHasMockEdgeType::EDGECONST, - ), - $t_drev => array( - $t_drev => DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST, - $t_task => DifferentialRevisionHasTaskEdgeType::EDGECONST, - ), - $t_mock => array( - $t_task => PholioMockHasTaskEdgeType::EDGECONST, - ), - ); - - if (empty($map[$src_type][$dst_type])) { - return null; - } - - return $map[$src_type][$dst_type]; - } - - private function raiseGraphCycleException(PhabricatorEdgeCycleException $ex) { - $cycle = $ex->getCycle(); - - $handles = $this->loadViewerHandles($cycle); - $names = array(); - foreach ($cycle as $cycle_phid) { - $names[] = $handles[$cycle_phid]->getFullName(); - } - throw new Exception( - pht( - 'You can not create that dependency, because it would create a '. - 'circular dependency: %s.', - implode(" \xE2\x86\x92 ", $names))); - } - -} diff --git a/src/applications/search/controller/PhabricatorSearchBaseController.php b/src/applications/search/controller/PhabricatorSearchBaseController.php index 85ac1eb285..d3f31c1e6a 100644 --- a/src/applications/search/controller/PhabricatorSearchBaseController.php +++ b/src/applications/search/controller/PhabricatorSearchBaseController.php @@ -2,10 +2,34 @@ abstract class PhabricatorSearchBaseController extends PhabricatorController { - const ACTION_ATTACH = 'attach'; - const ACTION_MERGE = 'merge'; - const ACTION_DEPENDENCIES = 'dependencies'; - const ACTION_BLOCKS = 'blocks'; - const ACTION_EDGE = 'edge'; + protected function loadRelationshipObject() { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $phid = $request->getURIData('sourcePHID'); + + return id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + } + + protected function loadRelationship($object) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $relationship_key = $request->getURIData('relationshipKey'); + + $list = PhabricatorObjectRelationshipList::newForObject( + $viewer, + $object); + + return $list->getRelationship($relationship_key); + } } diff --git a/src/applications/search/controller/PhabricatorSearchRelationshipController.php b/src/applications/search/controller/PhabricatorSearchRelationshipController.php index 6af121d1ba..d254969423 100644 --- a/src/applications/search/controller/PhabricatorSearchRelationshipController.php +++ b/src/applications/search/controller/PhabricatorSearchRelationshipController.php @@ -6,26 +6,12 @@ final class PhabricatorSearchRelationshipController public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); - $phid = $request->getURIData('sourcePHID'); - $object = id(new PhabricatorObjectQuery()) - ->setViewer($viewer) - ->withPHIDs(array($phid)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); + $object = $this->loadRelationshipObject(); if (!$object) { return new Aphront404Response(); } - $list = PhabricatorObjectRelationshipList::newForObject( - $viewer, - $object); - - $relationship_key = $request->getURIData('relationshipKey'); - $relationship = $list->getRelationship($relationship_key); + $relationship = $this->loadRelationship($object); if (!$relationship) { return new Aphront404Response(); } @@ -33,9 +19,16 @@ final class PhabricatorSearchRelationshipController $src_phid = $object->getPHID(); $edge_type = $relationship->getEdgeConstant(); - $dst_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( - $src_phid, - $edge_type); + // If this is a normal relationship, users can remove related objects. If + // it's a special relationship like a merge, we can't undo it, so we won't + // prefill the current related objects. + if ($relationship->canUndoRelationship()) { + $dst_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $src_phid, + $edge_type); + } else { + $dst_phids = array(); + } $all_phids = $dst_phids; $all_phids[] = $src_phid; @@ -46,11 +39,23 @@ final class PhabricatorSearchRelationshipController $done_uri = $src_handle->getURI(); $initial_phids = $dst_phids; + $maximum = $relationship->getMaximumSelectionSize(); + if ($request->isFormPost()) { $phids = explode(';', $request->getStr('phids')); $phids = array_filter($phids); $phids = array_values($phids); + // The UI normally enforces this with Javascript, so this is just a + // sanity check and does not need to be particularly user-friendly. + if ($maximum && (count($phids) > $maximum)) { + throw new Exception( + pht( + 'Too many relationships (%s, of type "%s").', + phutil_count($phids), + $relationship->getRelationshipConstant())); + } + $initial_phids = $request->getStrList('initialPHIDs'); // Apply the changes as adds and removes relative to the original state @@ -58,12 +63,16 @@ final class PhabricatorSearchRelationshipController // relationships at the same time don't race and overwrite one another. $add_phids = array_diff($phids, $initial_phids); $rem_phids = array_diff($initial_phids, $phids); + $all_phids = array_merge($add_phids, $rem_phids); - if ($add_phids) { + $capabilities = $relationship->getRequiredRelationshipCapabilities(); + + if ($all_phids) { $dst_objects = id(new PhabricatorObjectQuery()) ->setViewer($viewer) - ->withPHIDs($phids) + ->withPHIDs($all_phids) ->setRaisePolicyExceptions(true) + ->requireCapabilities($capabilities) ->execute(); $dst_objects = mpull($dst_objects, null, 'getPHID'); } else { @@ -81,6 +90,14 @@ final class PhabricatorSearchRelationshipController $add_phid)); } + if ($add_phid == $src_phid) { + throw new Exception( + pht( + 'You can not create a relationship to object "%s" because '. + 'objects can not be related to themselves.', + $add_phid)); + } + if (!$relationship->canRelateObjects($object, $dst_object)) { throw new Exception( pht( @@ -95,9 +112,12 @@ final class PhabricatorSearchRelationshipController return $this->newUnrelatableObjectResponse($ex, $done_uri); } + $content_source = PhabricatorContentSource::newFromRequest($request); + $relationship->setContentSource($content_source); + $editor = $object->getApplicationTransactionEditor() ->setActor($viewer) - ->setContentSourceFromRequest($request) + ->setContentSource($content_source) ->setContinueOnMissingFields(true) ->setContinueOnNoEffect(true); @@ -110,9 +130,29 @@ final class PhabricatorSearchRelationshipController '-' => array_fuse($rem_phids), )); + $add_objects = array_select_keys($dst_objects, $add_phids); + $rem_objects = array_select_keys($dst_objects, $rem_phids); + + if ($add_objects || $rem_objects) { + $more_xactions = $relationship->willUpdateRelationships( + $object, + $add_objects, + $rem_objects); + foreach ($more_xactions as $xaction) { + $xactions[] = $xaction; + } + } + try { $editor->applyTransactions($object, $xactions); + if ($add_objects || $rem_objects) { + $relationship->didUpdateRelationships( + $object, + $add_objects, + $rem_objects); + } + return id(new AphrontRedirectResponse())->setURI($done_uri); } catch (PhabricatorEdgeCycleException $ex) { return $this->newGraphCycleResponse($ex, $done_uri); @@ -122,48 +162,32 @@ final class PhabricatorSearchRelationshipController $handles = iterator_to_array($handles); $handles = array_select_keys($handles, $dst_phids); - // TODO: These are hard-coded for now. - $filters = array( - 'assigned' => pht('Assigned to Me'), - 'created' => pht('Created By Me'), - 'open' => pht('All Open Objects'), - 'all' => pht('All Objects'), - ); - $dialog_title = $relationship->getDialogTitleText(); $dialog_header = $relationship->getDialogHeaderText(); $dialog_button = $relationship->getDialogButtonText(); $dialog_instructions = $relationship->getDialogInstructionsText(); - // TODO: Remove this, this is just legacy support. - $legacy_kinds = array( - ManiphestTaskHasCommitEdgeType::EDGECONST => 'CMIT', - ManiphestTaskHasMockEdgeType::EDGECONST => 'MOCK', - ManiphestTaskHasRevisionEdgeType::EDGECONST => 'DREV', - ManiphestTaskDependsOnTaskEdgeType::EDGECONST => 'TASK', - ManiphestTaskDependedOnByTaskEdgeType::EDGECONST => 'TASK', - ); + $source_uri = $relationship->getSourceURI($object); - $edge_type = $relationship->getEdgeConstant(); - $legacy_kind = idx($legacy_kinds, $edge_type); - if (!$legacy_kind) { - throw new Exception( - pht('Only specific legacy relationships are supported!')); - } + $source = $relationship->newSource(); + + $filters = $source->getFilters(); + $selected_filter = $source->getSelectedFilter(); return id(new PhabricatorObjectSelectorDialog()) ->setUser($viewer) ->setInitialPHIDs($initial_phids) ->setHandles($handles) ->setFilters($filters) - ->setSelectedFilter('created') - ->setExcluded($phid) + ->setSelectedFilter($selected_filter) + ->setExcluded($src_phid) ->setCancelURI($done_uri) - ->setSearchURI("/search/select/{$legacy_kind}/edge/") + ->setSearchURI($source_uri) ->setTitle($dialog_title) ->setHeader($dialog_header) ->setButtonText($dialog_button) ->setInstructions($dialog_instructions) + ->setMaximumSelectionSize($maximum) ->buildDialog(); } diff --git a/src/applications/search/controller/PhabricatorSearchSelectController.php b/src/applications/search/controller/PhabricatorSearchRelationshipSourceController.php similarity index 56% rename from src/applications/search/controller/PhabricatorSearchSelectController.php rename to src/applications/search/controller/PhabricatorSearchRelationshipSourceController.php index f663cd03d7..4a96727a29 100644 --- a/src/applications/search/controller/PhabricatorSearchSelectController.php +++ b/src/applications/search/controller/PhabricatorSearchRelationshipSourceController.php @@ -1,34 +1,44 @@ getUser(); - $type = $request->getURIData('type'); - $action = $request->getURIData('action'); + $viewer = $request->getViewer(); + $object = $this->loadRelationshipObject(); + if (!$object) { + return new Aphront404Response(); + } + + $relationship = $this->loadRelationship($object); + if (!$relationship) { + return new Aphront404Response(); + } + + $source = $relationship->newSource(); $query = new PhabricatorSavedQuery(); + + $action = $request->getURIData('action'); $query_str = $request->getStr('query'); + $filter = $request->getStr('filter'); $query->setEngineClassName('PhabricatorSearchApplicationSearchEngine'); $query->setParameter('query', $query_str); - $query->setParameter('types', array($type)); + + $types = $source->getResultPHIDTypes(); + $query->setParameter('types', $types); $status_open = PhabricatorSearchRelationship::RELATIONSHIP_OPEN; - switch ($request->getStr('filter')) { + switch ($filter) { case 'assigned': - $query->setParameter('ownerPHIDs', array($user->getPHID())); + $query->setParameter('ownerPHIDs', array($viewer->getPHID())); $query->setParameter('statuses', array($status_open)); break; case 'created'; - $query->setParameter('authorPHIDs', array($user->getPHID())); - // TODO - if / when we allow pholio mocks to be archived, etc - // update this - if ($type != PholioMockPHIDType::TYPECONST) { - $query->setParameter('statuses', array($status_open)); - } + $query->setParameter('authorPHIDs', array($viewer->getPHID())); + $query->setParameter('statuses', array($status_open)); break; case 'open': $query->setParameter('statuses', array($status_open)); @@ -37,17 +47,10 @@ final class PhabricatorSearchSelectController $query->setParameter('excludePHIDs', array($request->getStr('exclude'))); - $capabilities = array(PhabricatorPolicyCapability::CAN_VIEW); - switch ($action) { - case self::ACTION_MERGE: - $capabilities[] = PhabricatorPolicyCapability::CAN_EDIT; - break; - default: - break; - } + $capabilities = $relationship->getRequiredRelationshipCapabilities(); $results = id(new PhabricatorSearchDocumentQuery()) - ->setViewer($user) + ->setViewer($viewer) ->requireObjectCapabilities($capabilities) ->withSavedQuery($query) ->setOffset(0) @@ -55,10 +58,10 @@ final class PhabricatorSearchSelectController ->execute(); $phids = array_fill_keys(mpull($results, 'getPHID'), true); - $phids += $this->queryObjectNames($query_str, $capabilities); + $phids = $this->queryObjectNames($query, $capabilities) + $phids; $phids = array_keys($phids); - $handles = $this->loadViewerHandles($phids); + $handles = $viewer->loadHandles($phids); $data = array(); foreach ($handles as $handle) { @@ -69,15 +72,21 @@ final class PhabricatorSearchSelectController return id(new AphrontAjaxResponse())->setContent($data); } - private function queryObjectNames($query, $capabilities) { + private function queryObjectNames( + PhabricatorSavedQuery $query, + array $capabilities) { + $request = $this->getRequest(); $viewer = $request->getUser(); + $types = $query->getParameter('types'); + $match = $query->getParameter('query'); + $objects = id(new PhabricatorObjectQuery()) ->setViewer($viewer) ->requireCapabilities($capabilities) - ->withTypes(array($request->getURIData('type'))) - ->withNames(array($query)) + ->withTypes($query->getParameter('types')) + ->withNames(array($match)) ->execute(); return mpull($objects, 'getPHID'); diff --git a/src/applications/search/relationship/DifferentialRevisionRelationshipSource.php b/src/applications/search/relationship/DifferentialRevisionRelationshipSource.php new file mode 100644 index 0000000000..3a5b274880 --- /dev/null +++ b/src/applications/search/relationship/DifferentialRevisionRelationshipSource.php @@ -0,0 +1,20 @@ +getViewer(); + + return PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorDifferentialApplication', + $viewer); + } + + public function getResultPHIDTypes() { + return array( + DifferentialRevisionPHIDType::TYPECONST, + ); + } + +} diff --git a/src/applications/search/relationship/DiffusionCommitRelationshipSource.php b/src/applications/search/relationship/DiffusionCommitRelationshipSource.php new file mode 100644 index 0000000000..25c799caf4 --- /dev/null +++ b/src/applications/search/relationship/DiffusionCommitRelationshipSource.php @@ -0,0 +1,26 @@ +getViewer(); + + return PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorDiffusionApplication', + $viewer); + } + + public function getResultPHIDTypes() { + return array( + PhabricatorRepositoryCommitPHIDType::TYPECONST, + ); + } + + public function getFilters() { + $filters = parent::getFilters(); + unset($filters['assigned']); + return $filters; + } + +} diff --git a/src/applications/search/relationship/ManiphestTaskRelationshipSource.php b/src/applications/search/relationship/ManiphestTaskRelationshipSource.php new file mode 100644 index 0000000000..e510f73a97 --- /dev/null +++ b/src/applications/search/relationship/ManiphestTaskRelationshipSource.php @@ -0,0 +1,20 @@ +getViewer(); + + return PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorManiphestApplication', + $viewer); + } + + public function getResultPHIDTypes() { + return array( + ManiphestTaskPHIDType::TYPECONST, + ); + } + +} diff --git a/src/applications/search/relationship/PhabricatorObjectRelationship.php b/src/applications/search/relationship/PhabricatorObjectRelationship.php index 9742a35adf..0827c1728c 100644 --- a/src/applications/search/relationship/PhabricatorObjectRelationship.php +++ b/src/applications/search/relationship/PhabricatorObjectRelationship.php @@ -3,6 +3,7 @@ abstract class PhabricatorObjectRelationship extends Phobject { private $viewer; + private $contentSource; public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -13,6 +14,15 @@ abstract class PhabricatorObjectRelationship extends Phobject { return $this->viewer; } + public function setContentSource(PhabricatorContentSource $content_source) { + $this->contentSource = $content_source; + return $this; + } + + public function getContentSource() { + return $this->contentSource; + } + final public function getRelationshipConstant() { return $this->getPhobjectClassConstant('RELATIONSHIPKEY'); } @@ -47,6 +57,28 @@ abstract class PhabricatorObjectRelationship extends Phobject { PhabricatorPolicyCapability::CAN_EDIT); } + public function getRequiredRelationshipCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + final public function newSource() { + $viewer = $this->getViewer(); + + return $this->newRelationshipSource() + ->setViewer($viewer); + } + + abstract protected function newRelationshipSource(); + + final public function getSourceURI($object) { + $relationship_key = $this->getRelationshipConstant(); + $object_phid = $object->getPHID(); + + return "/search/source/{$relationship_key}/{$object_phid}/"; + } + final public function newAction($object) { $is_enabled = $this->isActionEnabled($object); $action_uri = $this->getActionURI($object); @@ -72,4 +104,20 @@ abstract class PhabricatorObjectRelationship extends Phobject { return "/search/rel/{$type}/{$phid}/"; } + public function getMaximumSelectionSize() { + return null; + } + + public function canUndoRelationship() { + return true; + } + + public function willUpdateRelationships($object, array $add, array $rem) { + return array(); + } + + public function didUpdateRelationships($object, array $add, array $rem) { + return; + } + } diff --git a/src/applications/search/relationship/PhabricatorObjectRelationshipList.php b/src/applications/search/relationship/PhabricatorObjectRelationshipList.php index a43bbaa571..02917bd57d 100644 --- a/src/applications/search/relationship/PhabricatorObjectRelationshipList.php +++ b/src/applications/search/relationship/PhabricatorObjectRelationshipList.php @@ -46,6 +46,32 @@ final class PhabricatorObjectRelationshipList extends Phobject { return $this->relationships; } + public function newActionSubmenu(array $keys) { + $object = $this->getObject(); + + $actions = array(); + + foreach ($keys as $key) { + // If we're passed a menu item, just include it verbatim. + if ($key instanceof PhabricatorActionView) { + $actions[] = $key; + continue; + } + + $relationship = $this->getRelationship($key); + if (!$relationship) { + throw new Exception( + pht( + 'No object relationship of type "%s" exists.', + $key)); + } + + $actions[$key] = $relationship->newAction($object); + } + + return $this->newMenuWithActions($actions); + } + public function newActionMenu() { $relationships = $this->getRelationships(); $object = $this->getObject(); @@ -65,9 +91,22 @@ final class PhabricatorObjectRelationshipList extends Phobject { $actions = msort($actions, 'getName'); - return id(new PhabricatorActionView()) + return $this->newMenuWithActions($actions) ->setName(pht('Edit Related Objects...')) - ->setIcon('fa-link') + ->setIcon('fa-link'); + } + + private function newMenuWithActions(array $actions) { + $any_enabled = false; + foreach ($actions as $action) { + if (!$action->getDisabled()) { + $any_enabled = true; + break; + } + } + + return id(new PhabricatorActionView()) + ->setDisabled(!$any_enabled) ->setSubmenu($actions); } @@ -87,6 +126,11 @@ final class PhabricatorObjectRelationshipList extends Phobject { continue; } + $source = $relationship->newSource(); + if (!$source->isEnabledForObject($object)) { + continue; + } + $results[$key] = $relationship; } diff --git a/src/applications/search/relationship/PhabricatorObjectRelationshipSource.php b/src/applications/search/relationship/PhabricatorObjectRelationshipSource.php new file mode 100644 index 0000000000..9990740b4f --- /dev/null +++ b/src/applications/search/relationship/PhabricatorObjectRelationshipSource.php @@ -0,0 +1,48 @@ +viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + abstract public function isEnabledForObject($object); + abstract public function getResultPHIDTypes(); + + protected function getDefaultFilter() { + return 'created'; + } + + final public function setSelectedFilter($selected_filter) { + $this->selectedFilter = $selected_filter; + return $this; + } + + final public function getSelectedFilter() { + if ($this->selectedFilter === null) { + return $this->getDefaultFilter(); + } + + return $this->selectedFilter; + } + + public function getFilters() { + // TODO: These are hard-coded for now, and all of this will probably be + // rewritten when we move to ApplicationSearch. + return array( + 'assigned' => pht('Assigned to Me'), + 'created' => pht('Created By Me'), + 'open' => pht('All Open Objects'), + 'all' => pht('All Objects'), + ); + } + +} diff --git a/src/applications/search/relationship/PholioMockRelationshipSource.php b/src/applications/search/relationship/PholioMockRelationshipSource.php new file mode 100644 index 0000000000..b21fd6624b --- /dev/null +++ b/src/applications/search/relationship/PholioMockRelationshipSource.php @@ -0,0 +1,26 @@ +getViewer(); + + return PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorPholioApplication', + $viewer); + } + + public function getResultPHIDTypes() { + return array( + PholioMockPHIDType::TYPECONST, + ); + } + + public function getFilters() { + $filters = parent::getFilters(); + unset($filters['assigned']); + return $filters; + } + +} diff --git a/src/applications/search/storage/document/PhabricatorSearchDocument.php b/src/applications/search/storage/document/PhabricatorSearchDocument.php index 161e791316..3e177c9813 100644 --- a/src/applications/search/storage/document/PhabricatorSearchDocument.php +++ b/src/applications/search/storage/document/PhabricatorSearchDocument.php @@ -26,6 +26,9 @@ final class PhabricatorSearchDocument extends PhabricatorSearchDAO { 'documentCreated' => array( 'columns' => array('documentCreated'), ), + 'key_type' => array( + 'columns' => array('documentType', 'documentCreated'), + ), ), ) + parent::getConfiguration(); } diff --git a/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php b/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php index ac02fae00e..b5d8ea8617 100644 --- a/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php @@ -227,8 +227,7 @@ final class PhabricatorEmailAddressesSettingsPanel $object->sendVerificationEmail($user); - $dialog = id(new AphrontDialogView()) - ->setUser($user) + $dialog = $this->newDialog() ->addHiddenInput('new', 'verify') ->setTitle(pht('Verification Email Sent')) ->appendChild(phutil_tag('p', array(), pht( @@ -259,8 +258,7 @@ final class PhabricatorEmailAddressesSettingsPanel ->setCaption(PhabricatorUserEmail::describeAllowedAddresses()) ->setError($e_email)); - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + $dialog = $this->newDialog() ->addHiddenInput('new', 'true') ->setTitle(pht('New Address')) ->appendChild($errors) diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index 53259984b4..e130522bb2 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -609,6 +609,8 @@ abstract class PhabricatorApplicationTransaction $edge_type = $this->getMetadataValue('edge:type'); switch ($edge_type) { case PhabricatorObjectMentionsObjectEdgeType::EDGECONST: + case ManiphestTaskHasDuplicateTaskEdgeType::EDGECONST: + case ManiphestTaskIsDuplicateOfTaskEdgeType::EDGECONST: return true; break; case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST: diff --git a/src/applications/uiexample/examples/PHUIPropertyListExample.php b/src/applications/uiexample/examples/PHUIPropertyListExample.php index 76a39774b6..68bc13158b 100644 --- a/src/applications/uiexample/examples/PHUIPropertyListExample.php +++ b/src/applications/uiexample/examples/PHUIPropertyListExample.php @@ -16,24 +16,6 @@ final class PHUIPropertyListExample extends PhabricatorUIExample { $request = $this->getRequest(); $user = $request->getUser(); - $details1 = id(new PHUIListItemView()) - ->setName(pht('Details')) - ->setSelected(true); - - $details2 = id(new PHUIListItemView()) - ->setName(pht('Rainbow Info')) - ->setStatusColor(PHUIListItemView::STATUS_WARN); - - $details3 = id(new PHUIListItemView()) - ->setName(pht('Pasta Haiku')) - ->setStatusColor(PHUIListItemView::STATUS_FAIL); - - $statustabs = id(new PHUIListView()) - ->setType(PHUIListView::NAVBAR_LIST) - ->addMenuItem($details1) - ->addMenuItem($details2) - ->addMenuItem($details3); - $view = new PHUIPropertyListView(); $view->addProperty( @@ -54,7 +36,6 @@ final class PHUIPropertyListExample extends PhabricatorUIExample { 'viverra. Nunc tempus tempor quam id iaculis. Maecenas lectus '. 'velit, aliquam et consequat quis, tincidunt id dolor.'); - $view2 = new PHUIPropertyListView(); $view2->addSectionHeader(pht('Colors of the Rainbow')); @@ -66,7 +47,6 @@ final class PHUIPropertyListExample extends PhabricatorUIExample { $view2->addProperty('I', pht('Indigo')); $view2->addProperty('V', pht('Violet')); - $view3 = new PHUIPropertyListView(); $view3->addSectionHeader(pht('Haiku About Pasta')); @@ -77,11 +57,29 @@ final class PHUIPropertyListExample extends PhabricatorUIExample { pht('haiku. it is very bad.'), pht('what did you expect?'))); + $details1 = id(new PHUITabView()) + ->setName(pht('Details')) + ->setKey('details') + ->appendChild($view); + + $details2 = id(new PHUITabView()) + ->setName(pht('Rainbow Info')) + ->setKey('rainbow') + ->appendChild($view2); + + $details3 = id(new PHUITabView()) + ->setName(pht('Pasta Haiku')) + ->setKey('haiku') + ->appendChild($view3); + + $tab_group = id(new PHUITabGroupView()) + ->addTab($details1) + ->addTab($details2) + ->addTab($details3); + $object_box1 = id(new PHUIObjectBoxView()) ->setHeaderText(pht('%s Stackered', 'PHUIPropertyListView')) - ->addPropertyList($view, $details1) - ->addPropertyList($view2, $details2) - ->addPropertyList($view3, $details3); + ->addTabGroup($tab_group); $edge_cases_view = new PHUIPropertyListView(); diff --git a/src/docs/user/configuration/configuring_encryption.diviner b/src/docs/user/configuration/configuring_encryption.diviner index 1e62071509..dbd0e76314 100644 --- a/src/docs/user/configuration/configuring_encryption.diviner +++ b/src/docs/user/configuration/configuring_encryption.diviner @@ -72,7 +72,7 @@ Each key should have these properties: - `default`: //Optional bool.// Optionally, mark exactly one key as the default key to enable encryption of newly uploaded file data. -The key material is sensitive an an attacker who learns it can decrypt data +The key material is sensitive and an attacker who learns it can decrypt data from the storage engine. diff --git a/src/docs/user/userguide/remarkup.diviner b/src/docs/user/userguide/remarkup.diviner index 8c6c7e1bec..33d8a136df 100644 --- a/src/docs/user/userguide/remarkup.diviner +++ b/src/docs/user/userguide/remarkup.diviner @@ -307,6 +307,28 @@ the rendered result. For example, this callout uses `(NOTE)`: (NOTE) Dr. Egon Spengler is the best resource for additional proton pack questions. + +Dividers +======== + +You can divide sections by putting three or more dashes on a line by +themselves. This creates a divider or horizontal rule similar to an `
` +tag, like this one: + +--- + +The dashes need to appear on their own line and be separated from other +content. For example, like this: + +``` +This section will be visually separated. + +--- + +On an entirely different topic, ... +``` + + = Linking URIs = URIs are automatically linked: http://phabricator.org/ diff --git a/src/infrastructure/diff/view/PHUIDiffGraphView.php b/src/infrastructure/diff/view/PHUIDiffGraphView.php new file mode 100644 index 0000000000..ed4b0acf57 --- /dev/null +++ b/src/infrastructure/diff/view/PHUIDiffGraphView.php @@ -0,0 +1,213 @@ +isHead = $is_head; + return $this; + } + + public function getIsHead() { + return $this->isHead; + } + + public function setIsTail($is_tail) { + $this->isTail = $is_tail; + return $this; + } + + public function getIsTail() { + return $this->isTail; + } + + public function renderRawGraph(array $parents) { + // This keeps our accumulated information about each line of the + // merge/branch graph. + $graph = array(); + + // This holds the next commit we're looking for in each column of the + // graph. + $threads = array(); + + // This is the largest number of columns any row has, i.e. the width of + // the graph. + $count = 0; + + foreach ($parents as $cursor => $parent_list) { + $joins = array(); + $splits = array(); + + // Look for some thread which has this commit as the next commit. If + // we find one, this commit goes on that thread. Otherwise, this commit + // goes on a new thread. + + $line = ''; + $found = false; + $pos = count($threads); + + $thread_count = $pos; + for ($n = 0; $n < $thread_count; $n++) { + + if (empty($threads[$n])) { + $line .= ' '; + continue; + } + + if ($threads[$n] == $cursor) { + if ($found) { + $line .= ' '; + $joins[] = $n; + unset($threads[$n]); + } else { + $line .= 'o'; + $found = true; + $pos = $n; + } + } else { + + // We render a "|" for any threads which have a commit that we haven't + // seen yet, this is later drawn as a vertical line. + $line .= '|'; + } + } + + // If we didn't find the thread this commit goes on, start a new thread. + // We use "o" to mark the commit for the rendering engine, or "^" to + // indicate that there's nothing after it so the line from the commit + // upward should not be drawn. + + if (!$found) { + if ($this->getIsHead()) { + $line .= '^'; + } else { + $line .= 'o'; + foreach ($graph as $k => $meta) { + // Go back across all the lines we've already drawn and add a + // "|" to the end, since this is connected to some future commit + // we don't know about. + for ($jj = strlen($meta['line']); $jj <= $count; $jj++) { + $graph[$k]['line'] .= '|'; + } + } + } + } + + // Update the next commit on this thread to the commit's first parent. + // This might have the effect of making a new thread. + $threads[$pos] = head($parent_list); + + // If we made a new thread, increase the thread count. + $count = max($pos + 1, $count); + + // Now, deal with splits (merges). I picked this terms opposite to the + // underlying repository term to confuse you. + foreach (array_slice($parent_list, 1) as $parent) { + $found = false; + + // Try to find the other parent(s) in our existing threads. If we find + // them, split to that thread. + + foreach ($threads as $idx => $thread_commit) { + if ($thread_commit == $parent) { + $found = true; + $splits[] = $idx; + } + } + + // If we didn't find the parent, we don't know about it yet. Find the + // first free thread and add it as the "next" commit in that thread. + // This might create a new thread. + + if (!$found) { + for ($n = 0; $n < $count; $n++) { + if (empty($threads[$n])) { + break; + } + } + $threads[$n] = $parent; + $splits[] = $n; + $count = max($n + 1, $count); + } + } + + $graph[] = array( + 'line' => $line, + 'split' => $splits, + 'join' => $joins, + ); + } + + // If this is the last page in history, replace any "o" characters at the + // bottom of columns with "x" characters so we do not draw a connecting + // line downward, and replace "^" with an "X" for repositories with + // exactly one commit. + if ($this->getIsTail() && $graph) { + $terminated = array(); + foreach (array_reverse(array_keys($graph)) as $key) { + $line = $graph[$key]['line']; + $len = strlen($line); + for ($ii = 0; $ii < $len; $ii++) { + $c = $line[$ii]; + if ($c == 'o') { + // If we've already terminated this thread, we don't need to add + // a terminator. + if (isset($terminated[$ii])) { + continue; + } + + $terminated[$ii] = true; + + // If this thread is joinining some other node here, we don't want + // to terminate it. + if (isset($graph[$key + 1])) { + $joins = $graph[$key + 1]['join']; + if (in_array($ii, $joins)) { + continue; + } + } + + $graph[$key]['line'][$ii] = 'x'; + } else if ($c != ' ') { + $terminated[$ii] = true; + } else { + unset($terminated[$ii]); + } + } + } + + $last = array_pop($graph); + $last['line'] = str_replace('^', 'X', $last['line']); + $graph[] = $last; + } + + return array($graph, $count); + } + + public function renderGraph(array $parents) { + list($graph, $count) = $this->renderRawGraph($parents); + + // Render into tags for the behavior. + + foreach ($graph as $k => $meta) { + $graph[$k] = javelin_tag( + 'div', + array( + 'sigil' => 'commit-graph', + 'meta' => $meta, + ), + ''); + } + + Javelin::initBehavior( + 'diffusion-commit-graph', + array( + 'count' => $count, + )); + + return $graph; + } + +} diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php index 3330620148..e6ca6cc53d 100644 --- a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php @@ -7,6 +7,7 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { private $header; private $infoView; private $background; + private $bare; public function addItem(PHUIDiffTableOfContentsItemView $item) { $this->items[] = $item; @@ -38,6 +39,15 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { return $this; } + public function setBare($bare) { + $this->bare = $bare; + return $this; + } + + public function getBare() { + return $this->bare; + } + public function render() { $this->requireResource('differential-core-view-css'); $this->requireResource('differential-table-of-contents-css'); @@ -160,6 +170,10 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { ->setAnchorName('toc') ->setNavigationMarker(true); + if ($this->bare) { + return $table; + } + $header = id(new PHUIHeaderView()) ->setHeader(pht('Table of Contents')); diff --git a/src/infrastructure/diff/view/__tests__/PHUIDiffGraphViewTestCase.php b/src/infrastructure/diff/view/__tests__/PHUIDiffGraphViewTestCase.php new file mode 100644 index 0000000000..9bcf9645a3 --- /dev/null +++ b/src/infrastructure/diff/view/__tests__/PHUIDiffGraphViewTestCase.php @@ -0,0 +1,94 @@ + array('B'), + 'B' => array('C', 'D', 'E'), + 'E' => array(), + 'D' => array(), + 'C' => array('F', 'G'), + 'G' => array(), + 'F' => array(), + ); + + $graph = $this->newGraph($nodes); + + $picture = array( + '^', + 'o', + '||x', + '|x ', + 'o ', + '|x ', + 'x ', + ); + + $this->assertGraph($picture, $graph, pht('Terminating Tree')); + } + + public function testReverseTree() { + $nodes = array( + 'A' => array('B'), + 'C' => array('B'), + 'B' => array('D'), + 'E' => array('D'), + 'F' => array('D'), + 'D' => array('G'), + 'G' => array(), + ); + + $graph = $this->newGraph($nodes); + + $picture = array( + '^', + '|^', + 'o ', + '|^', + '||^', + 'o ', + 'x', + ); + + $this->assertGraph($picture, $graph, pht('Reverse Tree')); + } + + public function testJoinTerminateTree() { + $nodes = array( + 'A' => array('D'), + 'B' => array('C'), + 'C' => array('D'), + 'D' => array(), + ); + + $graph = $this->newGraph($nodes); + + $picture = array( + '^', + '|^', + '|o', + 'x ', + ); + + $this->assertGraph($picture, $graph, pht('Reverse Tree')); + } + + private function newGraph(array $nodes) { + return id(new PHUIDiffGraphView()) + ->setIsHead(true) + ->setIsTail(true) + ->renderRawGraph($nodes); + } + + private function assertGraph($picture, $graph, $label) { + list($data, $count) = $graph; + $lines = ipull($data, 'line'); + + $picture = implode("\n", $picture); + $lines = implode("\n", $lines); + + $this->assertEqual($picture, $lines, $label); + } + +} diff --git a/src/infrastructure/graph/DifferentialRevisionGraph.php b/src/infrastructure/graph/DifferentialRevisionGraph.php new file mode 100644 index 0000000000..3b5d638ec7 --- /dev/null +++ b/src/infrastructure/graph/DifferentialRevisionGraph.php @@ -0,0 +1,83 @@ +isClosed(); + } + + protected function newTableRow($phid, $object, $trace) { + $viewer = $this->getViewer(); + + if ($object) { + $status_icon = $object->getStatusIcon(); + $status_name = $object->getStatusDisplayName(); + + $status = array( + id(new PHUIIconView())->setIcon($status_icon), + ' ', + $status_name, + ); + + $author = $viewer->renderHandle($object->getAuthorPHID()); + $link = phutil_tag( + 'a', + array( + 'href' => $object->getURI(), + ), + $object->getTitle()); + + $link = array( + $object->getMonogram(), + ' ', + $link, + ); + } else { + $status = null; + $author = null; + $link = $viewer->renderHandle($phid); + } + + return array( + $trace, + $status, + $author, + $link, + ); + } + + protected function newTable(AphrontTableView $table) { + return $table + ->setHeaders( + array( + null, + pht('Status'), + pht('Author'), + pht('Revision'), + )) + ->setColumnClasses( + array( + 'threads', + 'graph-status', + null, + 'wide pri object-link', + )); + } + +} diff --git a/src/infrastructure/graph/ManiphestTaskGraph.php b/src/infrastructure/graph/ManiphestTaskGraph.php new file mode 100644 index 0000000000..b793493cd3 --- /dev/null +++ b/src/infrastructure/graph/ManiphestTaskGraph.php @@ -0,0 +1,96 @@ +isClosed(); + } + + protected function newTableRow($phid, $object, $trace) { + $viewer = $this->getViewer(); + + if ($object) { + $status = $object->getStatus(); + $priority = $object->getPriority(); + $status_icon = ManiphestTaskStatus::getStatusIcon($status); + $status_name = ManiphestTaskStatus::getTaskStatusName($status); + + $priority_color = ManiphestTaskPriority::getTaskPriorityColor($priority); + if ($object->isClosed()) { + $priority_color = 'grey'; + } + + $status = array( + id(new PHUIIconView())->setIcon($status_icon, $priority_color), + ' ', + $status_name, + ); + + $owner_phid = $object->getOwnerPHID(); + if ($owner_phid) { + $assigned = $viewer->renderHandle($owner_phid); + } else { + $assigned = phutil_tag('em', array(), pht('None')); + } + + $link = phutil_tag( + 'a', + array( + 'href' => $object->getURI(), + ), + $object->getTitle()); + + $link = array( + $object->getMonogram(), + ' ', + $link, + ); + } else { + $status = null; + $assigned = null; + $link = $viewer->renderHandle($phid); + } + + return array( + $trace, + $status, + $assigned, + $link, + ); + } + + protected function newTable(AphrontTableView $table) { + return $table + ->setHeaders( + array( + null, + pht('Status'), + pht('Assigned'), + pht('Task'), + )) + ->setColumnClasses( + array( + 'threads', + 'graph-status', + null, + 'wide pri object-link', + )); + } + +} diff --git a/src/infrastructure/graph/PhabricatorObjectGraph.php b/src/infrastructure/graph/PhabricatorObjectGraph.php new file mode 100644 index 0000000000..7a3790dea4 --- /dev/null +++ b/src/infrastructure/graph/PhabricatorObjectGraph.php @@ -0,0 +1,205 @@ +viewer = $viewer; + return $this; + } + + public function getViewer() { + if (!$this->viewer) { + throw new PhutilInvalidStateException('setViewer'); + } + + return $this->viewer; + } + + abstract protected function getEdgeTypes(); + abstract protected function getParentEdgeType(); + abstract protected function newQuery(); + abstract protected function newTableRow($phid, $object, $trace); + abstract protected function newTable(AphrontTableView $table); + abstract protected function isClosed($object); + + final public function setSeedPHID($phid) { + $this->seedPHID = $phid; + $this->edgeReach[$phid] = array_fill_keys($this->getEdgeTypes(), true); + + return $this->addNodes( + array( + '' => array($phid), + )); + } + + final public function isEmpty() { + return (count($this->getNodes()) <= 2); + } + + final public function getEdges($type) { + $edges = idx($this->edges, $type, array()); + + // Remove any nodes which we never reached. We can get these when loading + // only part of the graph: for example, they point at other subtasks of + // parents or other parents of subtasks. + $nodes = $this->getNodes(); + foreach ($edges as $src => $dsts) { + foreach ($dsts as $key => $dst) { + if (!isset($nodes[$dst])) { + unset($edges[$src][$key]); + } + } + } + + return $edges; + } + + final public function setLoadEntireGraph($load_entire_graph) { + $this->loadEntireGraph = $load_entire_graph; + return $this; + } + + final public function getLoadEntireGraph() { + return $this->loadEntireGraph; + } + + final protected function loadEdges(array $nodes) { + $edge_types = $this->getEdgeTypes(); + + $query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs($nodes) + ->withEdgeTypes($edge_types); + + $query->execute(); + + $whole_graph = $this->getLoadEntireGraph(); + + $map = array(); + foreach ($nodes as $node) { + $map[$node] = array(); + + foreach ($edge_types as $edge_type) { + $dst_phids = $query->getDestinationPHIDs( + array($node), + array($edge_type)); + + $this->edges[$edge_type][$node] = $dst_phids; + foreach ($dst_phids as $dst_phid) { + if ($whole_graph || isset($this->edgeReach[$node][$edge_type])) { + $map[$node][] = $dst_phid; + } + $this->edgeReach[$dst_phid][$edge_type] = true; + } + } + + $map[$node] = array_values(array_fuse($map[$node])); + } + + return $map; + } + + final public function newGraphTable() { + $viewer = $this->getViewer(); + + $ancestry = $this->getEdges($this->getParentEdgeType()); + + $objects = $this->newQuery() + ->setViewer($viewer) + ->withPHIDs(array_keys($ancestry)) + ->execute(); + $objects = mpull($objects, null, 'getPHID'); + + $order = id(new PhutilDirectedScalarGraph()) + ->addNodes($ancestry) + ->getTopographicallySortedNodes(); + + $ancestry = array_select_keys($ancestry, $order); + + $traces = id(new PHUIDiffGraphView()) + ->renderGraph($ancestry); + + $ii = 0; + $rows = array(); + $rowc = array(); + foreach ($ancestry as $phid => $ignored) { + $object = idx($objects, $phid); + $rows[] = $this->newTableRow($phid, $object, $traces[$ii++]); + + $classes = array(); + if ($phid == $this->seedPHID) { + $classes[] = 'highlighted'; + } + + if ($object) { + if ($this->isClosed($object)) { + $classes[] = 'closed'; + } + } + + if ($classes) { + $classes = implode(' ', $classes); + } else { + $classes = null; + } + + $rowc[] = $classes; + } + + $table = id(new AphrontTableView($rows)) + ->setClassName('object-graph-table') + ->setRowClasses($rowc); + + $this->objects = $objects; + + return $this->newTable($table); + } + + final public function getReachableObjects($edge_type) { + if ($this->objects === null) { + throw new PhutilInvalidStateException('newGraphTable'); + } + + $graph = $this->getEdges($edge_type); + + $seen = array(); + $look = array($this->seedPHID); + while ($look) { + $phid = array_pop($look); + + $parents = idx($graph, $phid, array()); + foreach ($parents as $parent) { + if (isset($seen[$parent])) { + continue; + } + + $seen[$parent] = $parent; + $look[] = $parent; + } + } + + $reachable = array(); + foreach ($seen as $phid) { + if ($phid == $this->seedPHID) { + continue; + } + + $object = idx($this->objects, $phid); + if (!$object) { + continue; + } + + $reachable[] = $object; + } + + return $reachable; + } + +} diff --git a/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php b/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php index 1a51467089..99f1bcb864 100644 --- a/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php +++ b/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php @@ -99,6 +99,7 @@ abstract class PhabricatorLiskDAO extends LiskDAO { 'port' => $conf->getPort(), 'database' => $database, 'retries' => 3, + 'timeout' => 10, ), )); } diff --git a/src/view/control/PhabricatorObjectSelectorDialog.php b/src/view/control/PhabricatorObjectSelectorDialog.php index 09d9582e8b..3455cbc8bd 100644 --- a/src/view/control/PhabricatorObjectSelectorDialog.php +++ b/src/view/control/PhabricatorObjectSelectorDialog.php @@ -11,6 +11,7 @@ final class PhabricatorObjectSelectorDialog extends Phobject { private $selectedFilter; private $excluded; private $initialPHIDs; + private $maximumSelectionSize; private $title; private $header; @@ -87,6 +88,15 @@ final class PhabricatorObjectSelectorDialog extends Phobject { return $this->initialPHIDs; } + public function setMaximumSelectionSize($maximum_selection_size) { + $this->maximumSelectionSize = $maximum_selection_size; + return $this; + } + + public function getMaximumSelectionSize() { + return $this->maximumSelectionSize; + } + public function buildDialog() { $user = $this->user; @@ -190,6 +200,8 @@ final class PhabricatorObjectSelectorDialog extends Phobject { $dialog->addHiddenInput('initialPHIDs', $initial_phids); } + $maximum = $this->getMaximumSelectionSize(); + Javelin::initBehavior( 'phabricator-object-selector', array( @@ -202,6 +214,7 @@ final class PhabricatorObjectSelectorDialog extends Phobject { 'exclude' => $this->excluded, 'uri' => $this->searchURI, 'handles' => $handle_views, + 'maximum' => $maximum, )); $dialog->setResizeX(true); diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php index 7a7cc64c3b..493f8c3dbd 100644 --- a/src/view/layout/PhabricatorActionView.php +++ b/src/view/layout/PhabricatorActionView.php @@ -84,6 +84,10 @@ final class PhabricatorActionView extends AphrontView { return $this; } + public function getDisabled() { + return $this->disabled; + } + public function setWorkflow($workflow) { $this->workflow = $workflow; return $this; diff --git a/src/view/phui/PHUIObjectBoxView.php b/src/view/phui/PHUIObjectBoxView.php index 9fa280a843..5c6cef49a3 100644 --- a/src/view/phui/PHUIObjectBoxView.php +++ b/src/view/phui/PHUIObjectBoxView.php @@ -5,6 +5,7 @@ final class PHUIObjectBoxView extends AphrontTagView { private $headerText; private $color; private $background; + private $tabGroups = array(); private $formErrors = null; private $formSaved = false; private $infoView; @@ -24,11 +25,7 @@ final class PHUIObjectBoxView extends AphrontTagView { private $showHideContent; private $showHideOpen; - private $tabs = array(); - private $tabMap = null; - private $tabLists = array(); private $propertyLists = array(); - private $propertyList = null; const COLOR_RED = 'red'; const COLOR_BLUE = 'blue'; @@ -39,48 +36,8 @@ final class PHUIObjectBoxView extends AphrontTagView { const BLUE_PROPERTY = 'phui-box-blue-property'; const GREY = 'phui-box-grey'; - public function addPropertyList( - PHUIPropertyListView $property_list, - $tab = null) { - - if (!($tab instanceof PHUIListItemView) && - ($tab !== null)) { - assert_stringlike($tab); - $tab = id(new PHUIListItemView())->setName($tab); - } - - if ($tab) { - if ($tab->getKey()) { - $key = $tab->getKey(); - } else { - $key = 'tab.default.'.spl_object_hash($tab); - $tab->setKey($key); - } - } else { - $key = 'tab.default'; - } - - if ($tab) { - if (empty($this->tabs[$key])) { - $tab->addSigil('phui-object-box-tab'); - $tab->setMetadata( - array( - 'tabKey' => $key, - )); - - if (!$tab->getHref()) { - $tab->setHref('#'); - } - - if (!$tab->getType()) { - $tab->setType(PHUIListItemView::TYPE_LINK); - } - - $this->tabs[$key] = $tab; - } - } - - $this->propertyLists[$key][] = $property_list; + public function addPropertyList(PHUIPropertyListView $property_list) { + $this->propertyLists[] = $property_list; $action_list = $property_list->getActionList(); if ($action_list) { @@ -128,6 +85,11 @@ final class PHUIObjectBoxView extends AphrontTagView { return $this; } + public function addTabGroup(PHUITabGroupView $view) { + $this->tabGroups[] = $view; + return $this; + } + public function setInfoView(PHUIInfoView $view) { $this->infoView = $view; return $this; @@ -184,68 +146,6 @@ final class PHUIObjectBoxView extends AphrontTagView { return $this; } - public function willRender() { - $tab_lists = array(); - $property_lists = array(); - $tab_map = array(); - - $default_key = 'tab.default'; - - // Find the selected tab, or select the first tab if none are selected. - if ($this->tabs) { - $selected_tab = null; - foreach ($this->tabs as $key => $tab) { - if ($tab->getSelected()) { - $selected_tab = $key; - break; - } - } - if ($selected_tab === null) { - head($this->tabs)->setSelected(true); - $selected_tab = head_key($this->tabs); - } - } - - foreach ($this->propertyLists as $key => $list) { - $group = new PHUIPropertyGroupView(); - $i = 0; - foreach ($list as $item) { - $group->addPropertyList($item); - if ($i > 0) { - $item->addClass('phui-property-list-section-noninitial'); - } - $i++; - } - - if ($this->tabs && $key != $default_key) { - $tab_id = celerity_generate_unique_node_id(); - $tab_map[$key] = $tab_id; - - if ($key === $selected_tab) { - $style = null; - } else { - $style = 'display: none'; - } - - $tab_lists[] = phutil_tag( - 'div', - array( - 'style' => $style, - 'id' => $tab_id, - ), - $group); - } else { - if ($this->tabs) { - $group->addClass('phui-property-group-noninitial'); - } - $property_lists[] = $group; - } - $this->propertyList = $property_lists; - $this->tabMap = $tab_map; - $this->tabLists = $tab_lists; - } - } - protected function getTagAttributes() { $classes = array(); $classes[] = 'phui-box'; @@ -269,19 +169,8 @@ final class PHUIObjectBoxView extends AphrontTagView { $classes[] = $this->background; } - $sigil = null; - $metadata = null; - if ($this->tabs) { - $sigil = 'phui-object-box'; - $metadata = array( - 'tabMap' => $this->tabMap, - ); - } - return array( 'class' => implode(' ', $classes), - 'sigil' => $sigil, - 'meta' => $metadata, ); } @@ -387,16 +276,23 @@ final class PHUIObjectBoxView extends AphrontTagView { } } - $tabs = null; - if ($this->tabs) { - $tabs = id(new PHUIListView()) - ->setType(PHUIListView::NAVBAR_LIST); - foreach ($this->tabs as $tab) { - $tabs->addMenuItem($tab); + if ($this->propertyLists) { + $lists = new PHUIPropertyGroupView(); + + $ii = 0; + foreach ($this->propertyLists as $list) { + if ($ii > 0 || $this->tabGroups) { + $list->addClass('phui-property-list-section-noninitial'); + } + + $lists->addPropertyList($list); + $ii++; } - Javelin::initBehavior('phui-object-box-tabs'); + } else { + $lists = null; } + $content = array( ($this->showHideOpen == false ? $this->anchor : null), $header, @@ -405,11 +301,10 @@ final class PHUIObjectBoxView extends AphrontTagView { $this->formSaved, $exception_errors, $this->form, - $tabs, - $this->tabLists, + $this->tabGroups, $showhide, ($this->showHideOpen == true ? $this->anchor : null), - $this->propertyList, + $lists, $this->table, $this->renderChildren(), ); diff --git a/src/view/phui/PHUITabGroupView.php b/src/view/phui/PHUITabGroupView.php new file mode 100644 index 0000000000..4a1963e050 --- /dev/null +++ b/src/view/phui/PHUITabGroupView.php @@ -0,0 +1,116 @@ +hideSingleTab = $hide_single_tab; + return $this; + } + + public function getHideSingleTab() { + return $this->hideSingleTab; + } + + public function addTab(PHUITabView $tab) { + $key = $tab->getKey(); + $tab->lockKey(); + + if (isset($this->tabs[$key])) { + throw new Exception( + pht( + 'Each tab in a tab group must have a unique key; attempting to add '. + 'a second tab with a duplicate key ("%s").', + $key)); + } + + $this->tabs[$key] = $tab; + + return $this; + } + + public function selectTab($key) { + if (empty($this->tabs[$key])) { + throw new Exception( + pht( + 'Unable to select tab ("%s") which does not exist.', + $key)); + } + + $this->selectedTab = $key; + + return $this; + } + + public function getSelectedTabKey() { + if (!$this->tabs) { + return null; + } + + if ($this->selectedTab !== null) { + return $this->selectedTab; + } + + return head($this->tabs)->getKey(); + } + + protected function getTagAttributes() { + $tab_map = mpull($this->tabs, 'getContentID', 'getKey'); + + return array( + 'sigil' => 'phui-tab-group-view', + 'meta' => array( + 'tabMap' => $tab_map, + ), + ); + } + + protected function getTagContent() { + Javelin::initBehavior('phui-tab-group'); + + $tabs = id(new PHUIListView()) + ->setType(PHUIListView::NAVBAR_LIST); + $content = array(); + + $selected_tab = $this->getSelectedTabKey(); + foreach ($this->tabs as $tab) { + $item = $tab->newMenuItem(); + $tab_key = $tab->getKey(); + + if ($tab_key == $selected_tab) { + $item->setSelected(true); + $style = null; + } else { + $style = 'display: none;'; + } + + $tabs->addMenuItem($item); + + $content[] = javelin_tag( + 'div', + array( + 'style' => $style, + 'id' => $tab->getContentID(), + ), + $tab); + } + + if ($this->hideSingleTab && (count($this->tabs) == 1)) { + $tabs = null; + } + + return array( + $tabs, + $content, + ); + } + +} diff --git a/src/view/phui/PHUITabView.php b/src/view/phui/PHUITabView.php new file mode 100644 index 0000000000..bcc80bf774 --- /dev/null +++ b/src/view/phui/PHUITabView.php @@ -0,0 +1,91 @@ +keyLocked) { + throw new Exception( + pht( + 'Attempting to change the key of a tab with a locked key ("%s").', + $this->key)); + } + + $this->key = $key; + return $this; + } + + public function hasKey() { + return ($this->key !== null); + } + + public function getKey() { + if (!$this->hasKey()) { + throw new PhutilInvalidStateException('setKey'); + } + + return $this->key; + } + + public function lockKey() { + if (!$this->hasKey()) { + throw new PhutilInvalidStateException('setKey'); + } + + $this->keyLocked = true; + + return $this; + } + + public function setName($name) { + $this->name = $name; + return $this; + } + + public function getName() { + return $this->name; + } + + public function getContentID() { + if ($this->contentID === null) { + $this->contentID = celerity_generate_unique_node_id(); + } + + return $this->contentID; + } + + public function setColor($color) { + $this->color = $color; + return $this; + } + + public function getColor() { + return $this->color; + } + + public function newMenuItem() { + $item = id(new PHUIListItemView()) + ->setName($this->getName()) + ->setKey($this->getKey()) + ->setType(PHUIListItemView::TYPE_LINK) + ->setHref('#') + ->addSigil('phui-tab-view') + ->setMetadata( + array( + 'tabKey' => $this->getKey(), + )); + + $color = $this->getColor(); + if ($color !== null) { + $item->setStatusColor($color); + } + + return $item; + } + +} diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index af304c930a..dddd78f103 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -228,6 +228,23 @@ span.single-display-line-content { position: static; } +.aphront-table-view tr.closed td.object-link, +.aphront-table-view tr.alt-closed td.object-link { + text-decoration: line-through; + color: rgba({$alphablack}, 0.5); +} + +.aphront-table-view tr.closed td.object-link a, +.aphront-table-view tr.alt-closed td.object-link a { + color: rgba({$alphablack}, 0.5); +} + +.aphront-table-view tr.closed td.graph-status, +.aphront-table-view tr.alt-closed td.graph-status, +.object-graph-table em { + color: {$lightgreytext}; +} + .aphront-table-view tr.highlighted { background: #fdf9e4; } diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 2a72357d4d..2cedb39db5 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -67,7 +67,7 @@ padding: 1px 4px; } -.device .differential-diff .inline td { +.device .differential-diff .inline > td { padding: 4px; } @@ -308,7 +308,7 @@ td.cov-I { box-sizing: border-box; } -.differential-diff .inline td { +.differential-diff .inline > td { padding: 8px 12px; } diff --git a/webroot/rsrc/css/core/syntax.css b/webroot/rsrc/css/core/syntax.css index 27006f2e4c..5de3b5d4a4 100644 --- a/webroot/rsrc/css/core/syntax.css +++ b/webroot/rsrc/css/core/syntax.css @@ -12,7 +12,8 @@ } .remarkup-code td span { - display: inline-block; + display: inline; + word-break: break-all; } .remarkup-code .rbw_r { color: red; } diff --git a/webroot/rsrc/css/phui/phui-icon.css b/webroot/rsrc/css/phui/phui-icon.css index ad789768c8..aa362034ed 100644 --- a/webroot/rsrc/css/phui/phui-icon.css +++ b/webroot/rsrc/css/phui/phui-icon.css @@ -8,8 +8,8 @@ } .phui-icon-view.sprite-tokens { - height: 16px; - width: 16px; + height: 18px; + width: 18px; display: inline-block; vertical-align: top; } diff --git a/webroot/rsrc/css/sprite-tokens.css b/webroot/rsrc/css/sprite-tokens.css index 9a911d40cc..2e51031199 100644 --- a/webroot/rsrc/css/sprite-tokens.css +++ b/webroot/rsrc/css/sprite-tokens.css @@ -14,7 +14,7 @@ only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx) { .sprite-tokens { background-image: url(/rsrc/image/sprite-tokens-X2.png); - background-size: 68px 68px; + background-size: 76px 76px; } } @@ -24,61 +24,61 @@ only screen and (min-resolution: 1.5dppx) { } .tokens-coin-2 { - background-position: -17px 0px; + background-position: -19px 0px; } .tokens-coin-3 { - background-position: -34px 0px; + background-position: -38px 0px; } .tokens-coin-4 { - background-position: -51px 0px; + background-position: -57px 0px; } .tokens-heart-1 { - background-position: 0px -17px; + background-position: 0px -19px; } .tokens-heart-2 { - background-position: -17px -17px; + background-position: -19px -19px; } .tokens-like-1 { - background-position: -34px -17px; + background-position: -38px -19px; } .tokens-like-2 { - background-position: -51px -17px; + background-position: -57px -19px; } .tokens-medal-1 { - background-position: 0px -34px; + background-position: 0px -38px; } .tokens-medal-2 { - background-position: -17px -34px; + background-position: -19px -38px; } .tokens-medal-3 { - background-position: -34px -34px; + background-position: -38px -38px; } .tokens-medal-4 { - background-position: -51px -34px; + background-position: -57px -38px; } .tokens-misc-1 { - background-position: 0px -51px; + background-position: 0px -57px; } .tokens-misc-2 { - background-position: -17px -51px; + background-position: -19px -57px; } .tokens-misc-3 { - background-position: -34px -51px; + background-position: -38px -57px; } .tokens-misc-4 { - background-position: -51px -51px; + background-position: -57px -57px; } diff --git a/webroot/rsrc/image/sprite-tokens-X2.png b/webroot/rsrc/image/sprite-tokens-X2.png index 84a3407cae..3596bf5374 100644 Binary files a/webroot/rsrc/image/sprite-tokens-X2.png and b/webroot/rsrc/image/sprite-tokens-X2.png differ diff --git a/webroot/rsrc/image/sprite-tokens.png b/webroot/rsrc/image/sprite-tokens.png index 4ecda8e2b5..fc02173e40 100644 Binary files a/webroot/rsrc/image/sprite-tokens.png and b/webroot/rsrc/image/sprite-tokens.png differ diff --git a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js index a3a7c7bdf8..317a132240 100644 --- a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js +++ b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js @@ -79,6 +79,7 @@ JX.behavior('diffusion-commit-graph', function(config) { c = data.line.charAt(jj); switch (c) { case 'o': + case 'x': case '^': origin = xpos(jj); break; @@ -91,6 +92,7 @@ JX.behavior('diffusion-commit-graph', function(config) { for (jj = 0; jj < data.join.length; jj++) { var join = data.join[jj]; x = xpos(join); + cxt.beginPath(); cxt.moveTo(x, 0); cxt.bezierCurveTo(x, h/4, origin, h/4, origin, h/2); diff --git a/webroot/rsrc/js/core/behavior-object-selector.js b/webroot/rsrc/js/core/behavior-object-selector.js index d4a542c840..686f0f8820 100644 --- a/webroot/rsrc/js/core/behavior-object-selector.js +++ b/webroot/rsrc/js/core/behavior-object-selector.js @@ -10,11 +10,13 @@ JX.behavior('phabricator-object-selector', function(config) { var n = 0; var phids = {}; + var display = []; + var handles = config.handles; for (var k in handles) { phids[k] = true; } - var button_list = {}; + var query_timer = null; var query_delay = 50; @@ -41,37 +43,82 @@ JX.behavior('phabricator-object-selector', function(config) { return; } - var display = []; - button_list = {}; + display = []; for (var k in r) { handles[r[k].phid] = r[k]; - display.push(renderHandle(r[k], true)); + display.push({phid: r[k].phid}); } - if (!display.length) { - display = renderNote('No results.'); - } - - JX.DOM.setContent(JX.$(config.results), display); + redrawList(true); } function redrawAttached() { - var display = []; + var attached = []; for (var k in phids) { - display.push(renderHandle(handles[k], false)); + attached.push(renderHandle(handles[k], false).item); } - if (!display.length) { - display = renderNote('Nothing attached.'); + if (!attached.length) { + attached = renderNote('Nothing attached.'); } - JX.DOM.setContent(JX.$(config.current), display); + JX.DOM.setContent(JX.$(config.current), attached); phid_input.value = JX.keys(phids).join(';'); } - function renderHandle(h, attach) { + function redrawList(rebuild) { + var ii; + var content; + if (rebuild) { + if (display.length) { + var handle; + + content = []; + for (ii = 0; ii < display.length; ii++) { + handle = handles[display[ii].phid]; + + display[ii].node = renderHandle(handle, true); + content.push(display[ii].node.item); + } + } else { + content = renderNote('No results.'); + } + + JX.DOM.setContent(JX.$(config.results), content); + } + + var phid; + var is_disabled; + var button; + + var at_maximum = !canSelectMore(); + + for (ii = 0; ii < display.length; ii++) { + phid = display[ii].phid; + + is_disabled = false; + + // If this object is already selected, you can not select it again. + if (phids.hasOwnProperty(phid)) { + is_disabled = true; + } + + // If the maximum number of objects are already selected, you can + // not select more. + if (at_maximum) { + is_disabled = true; + } + + button = display[ii].node.button; + JX.DOM.alterClass(button, 'disabled', is_disabled); + button.disabled = is_disabled; + } + + } + + function renderHandle(h, attach) { var some_icon = JX.$N( 'span', {className: 'phui-icon-view phui-font-fa ' + @@ -111,15 +158,10 @@ JX.behavior('phabricator-object-selector', function(config) { meta: {handle: h, table:table}}, cells)); - if (attach) { - button_list[h.phid] = select_object_button; - if (h.phid in phids) { - JX.DOM.alterClass(select_object_button, 'disabled', true); - select_object_button.disabled = true; - } - } - - return table; + return { + item: table, + button: select_object_button + }; } function renderNote(note) { @@ -138,6 +180,18 @@ JX.behavior('phabricator-object-selector', function(config) { .send(); } + function canSelectMore() { + if (!config.maximum) { + return true; + } + + if (JX.keys(phids).length < config.maximum) { + return true; + } + + return false; + } + JX.DOM.listen( JX.$(config.results), 'click', @@ -151,10 +205,13 @@ JX.behavior('phabricator-object-selector', function(config) { return; } - phids[phid] = true; - JX.DOM.alterClass(button_list[phid], 'disabled', true); - button_list[phid].disabled = true; + if (!canSelectMore()) { + return; + } + phids[phid] = true; + + redrawList(false); redrawAttached(); }); @@ -170,13 +227,7 @@ JX.behavior('phabricator-object-selector', function(config) { delete phids[phid]; - // NOTE: We may not have a button in the button list, if this result is - // not visible in the current search results. - if (button_list[phid]) { - JX.DOM.alterClass(button_list[phid], 'disabled', false); - button_list[phid].disabled = false; - } - + redrawList(false); redrawAttached(); }); @@ -205,6 +256,7 @@ JX.behavior('phabricator-object-selector', function(config) { }); sendQuery(); - redrawAttached(); + redrawList(true); + redrawAttached(); }); diff --git a/webroot/rsrc/js/phui/behavior-phui-object-box-tabs.js b/webroot/rsrc/js/phui/behavior-phui-tab-group.js similarity index 55% rename from webroot/rsrc/js/phui/behavior-phui-object-box-tabs.js rename to webroot/rsrc/js/phui/behavior-phui-tab-group.js index 85cdf9345d..23c1ca5b68 100644 --- a/webroot/rsrc/js/phui/behavior-phui-object-box-tabs.js +++ b/webroot/rsrc/js/phui/behavior-phui-tab-group.js @@ -1,23 +1,25 @@ /** - * @provides javelin-behavior-phui-object-box-tabs + * @provides javelin-behavior-phui-tab-group * @requires javelin-behavior * javelin-stratcom * javelin-dom */ -JX.behavior('phui-object-box-tabs', function() { +JX.behavior('phui-tab-group', function() { JX.Stratcom.listen( 'click', - 'phui-object-box-tab', + 'phui-tab-view', function (e) { e.kill(); - var key = e.getNodeData('phui-object-box-tab').tabKey; - var map = e.getNodeData('phui-object-box').tabMap; - var tab = e.getNode('phui-object-box-tab'); - var box = e.getNode('phui-object-box'); - var tabs = JX.DOM.scry(box, 'li', 'phui-object-box-tab'); + var map = e.getNodeData('phui-tab-group-view').tabMap; + var key = e.getNodeData('phui-tab-view').tabKey; + + var group = e.getNode('phui-tab-group-view'); + var tab = e.getNode('phui-tab-view'); + var tabs = JX.DOM.scry(group, 'li', 'phui-tab-view'); + for (var ii = 0; ii < tabs.length; ii++) { JX.DOM.alterClass( tabs[ii],