diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 930beaa0eb..e50d8bbd1a 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,11 +9,11 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '24ffbe93', - 'core.pkg.js' => '2ff7879f', + 'core.pkg.css' => '5ffe8b79', + 'core.pkg.js' => 'e822b496', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '90b30783', - 'differential.pkg.js' => 'ddfeb49b', + 'differential.pkg.css' => '4d7dd14e', + 'differential.pkg.js' => '68a4fa60', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -64,9 +64,9 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => '41af6d25', + 'rsrc/css/application/differential/changeset-view.css' => '54774a28', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', - 'rsrc/css/application/differential/phui-inline-comment.css' => 'be663c95', + 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', @@ -116,7 +116,7 @@ return array( 'rsrc/css/core/core.css' => '9f4cb463', 'rsrc/css/core/remarkup.css' => 'd1a5e11e', 'rsrc/css/core/syntax.css' => 'cae95e89', - 'rsrc/css/core/z-index.css' => '0233d039', + 'rsrc/css/core/z-index.css' => '998f3ce1', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', 'rsrc/css/font/font-awesome.css' => 'e838e088', 'rsrc/css/font/font-lato.css' => 'c7ccd872', @@ -129,9 +129,9 @@ return array( 'rsrc/css/phui/calendar/phui-calendar.css' => '477acfaa', 'rsrc/css/phui/object-item/phui-oi-big-ui.css' => '19f9369b', 'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77', - 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'f12cbc9f', + 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => '08f4ccc3', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '7c8ec27a', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '412bef1a', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', 'rsrc/css/phui/phui-action-list.css' => 'c01858f4', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', @@ -145,7 +145,7 @@ return array( 'rsrc/css/phui/phui-comment-form.css' => '57af2e14', 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', 'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb', - 'rsrc/css/phui/phui-curtain-view.css' => '679743bb', + 'rsrc/css/phui/phui-curtain-view.css' => '55dd0e59', 'rsrc/css/phui/phui-document-pro.css' => '62c4dcbf', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => 'c32e8dec', @@ -160,7 +160,7 @@ return array( 'rsrc/css/phui/phui-icon.css' => '12b387a1', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', - 'rsrc/css/phui/phui-info-view.css' => 'ec92802a', + 'rsrc/css/phui/phui-info-view.css' => '6e217679', 'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0', 'rsrc/css/phui/phui-lightbox.css' => '0a035e40', 'rsrc/css/phui/phui-list.css' => '12eb8ce6', @@ -173,7 +173,7 @@ return array( 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => 'cc4fd402', - 'rsrc/css/phui/phui-timeline-view.css' => '1d7ef61d', + 'rsrc/css/phui/phui-timeline-view.css' => '313c7f22', 'rsrc/css/phui/phui-two-column-view.css' => 'ce9fa0b7', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', 'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455', @@ -237,7 +237,7 @@ return array( 'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => '7a94d6a5', 'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '6ea96ac9', 'rsrc/externals/javelin/lib/Cookie.js' => '62dfea03', - 'rsrc/externals/javelin/lib/DOM.js' => '805b806a', + 'rsrc/externals/javelin/lib/DOM.js' => '4976858c', 'rsrc/externals/javelin/lib/History.js' => 'd4505101', 'rsrc/externals/javelin/lib/JSON.js' => '69adf288', 'rsrc/externals/javelin/lib/Leader.js' => '7f243deb', @@ -390,17 +390,15 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', + 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', + 'rsrc/js/application/diff/DiffChangesetList.js' => '5c68c40c', + 'rsrc/js/application/diff/DiffInline.js' => '77e14b60', + 'rsrc/js/application/diff/ScrollObjective.js' => '0eee7a00', + 'rsrc/js/application/diff/ScrollObjectiveList.js' => '1ca4d9db', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', - 'rsrc/js/application/differential/ChangesetViewManager.js' => 'a2828756', - 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '2e3f9738', - 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', - 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', + 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '9a6b9324', - 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '4fbbc3e9', - 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '92904457', - 'rsrc/js/application/differential/behavior-populate.js' => '8694b1df', - 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', + 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', @@ -450,7 +448,7 @@ return array( 'rsrc/js/application/transactions/behavior-comment-actions.js' => '9a6dd75c', 'rsrc/js/application/transactions/behavior-reorder-configs.js' => 'd7a74243', 'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96', - 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '94c65b72', + 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => 'ae95d984', 'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => 'b23b49e6', 'rsrc/js/application/transactions/behavior-transaction-list.js' => '1f6794f6', 'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '635de1ec', @@ -475,7 +473,7 @@ return array( 'rsrc/js/core/FileUpload.js' => '680ea2c8', 'rsrc/js/core/Hovercard.js' => '1bd28176', 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', - 'rsrc/js/core/KeyboardShortcutManager.js' => '4a021c10', + 'rsrc/js/core/KeyboardShortcutManager.js' => 'c19dd9b9', 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', 'rsrc/js/core/Notification.js' => 'ccf1cbf8', 'rsrc/js/core/Prefab.js' => 'c5af80a2', @@ -507,7 +505,7 @@ return array( 'rsrc/js/core/behavior-more.js' => 'a80d0378', 'rsrc/js/core/behavior-object-selector.js' => 'e0ec7f2f', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', - 'rsrc/js/core/behavior-phabricator-nav.js' => '08675c6d', + 'rsrc/js/core/behavior-phabricator-nav.js' => '947753e0', 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'acd29eee', 'rsrc/js/core/behavior-read-only-warning.js' => 'ba158207', 'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b', @@ -556,7 +554,6 @@ return array( 'application-search-view-css' => '66ee5d46', 'auth-css' => '0877ed6e', 'bulk-job-css' => 'df9c1d4a', - 'changeset-view-manager' => 'a2828756', 'conduit-api-css' => '7bc725c4', 'config-options-css' => '0ede4c9b', 'config-page-css' => 'c1d5121b', @@ -570,9 +567,8 @@ return array( 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => '41af6d25', + 'differential-changeset-view-css' => '54774a28', 'differential-core-view-css' => '5b7b8ff4', - 'differential-inline-comment-editor' => '2e3f9738', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', 'differential-revision-history-css' => '0e8eb855', @@ -622,14 +618,9 @@ return array( 'javelin-behavior-detect-timezone' => '4c193c96', 'javelin-behavior-device' => 'bb1dd507', 'javelin-behavior-diff-preview-link' => '051c7832', - 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', - 'javelin-behavior-differential-dropdown-menus' => '9a6b9324', - 'javelin-behavior-differential-edit-inline-comments' => '4fbbc3e9', - 'javelin-behavior-differential-feedback-preview' => 'b064af76', - 'javelin-behavior-differential-keyboard-navigation' => '92904457', - 'javelin-behavior-differential-populate' => '8694b1df', - 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', + 'javelin-behavior-differential-feedback-preview' => '51c5ad07', + 'javelin-behavior-differential-populate' => '5e41c819', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', @@ -669,14 +660,14 @@ return array( 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', 'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0', 'javelin-behavior-phabricator-line-linker' => '1499a8cb', - 'javelin-behavior-phabricator-nav' => '08675c6d', + 'javelin-behavior-phabricator-nav' => '947753e0', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => 'e0ec7f2f', 'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-remarkup-assist' => 'acd29eee', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-search-typeahead' => 'eded9ee8', - 'javelin-behavior-phabricator-show-older-transactions' => '94c65b72', + 'javelin-behavior-phabricator-show-older-transactions' => 'ae95d984', 'javelin-behavior-phabricator-tooltips' => 'c420b0b9', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', 'javelin-behavior-phabricator-transaction-list' => '1f6794f6', @@ -722,7 +713,7 @@ return array( 'javelin-color' => '7e41274a', 'javelin-cookie' => '62dfea03', 'javelin-diffusion-locate-file-source' => 'c93358e3', - 'javelin-dom' => '805b806a', + 'javelin-dom' => '4976858c', 'javelin-dynval' => 'f6555212', 'javelin-event' => '2ee659ce', 'javelin-fx' => '54b612ba', @@ -786,6 +777,9 @@ return array( 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', + 'phabricator-diff-changeset' => 'cf4e2140', + 'phabricator-diff-changeset-list' => '5c68c40c', + 'phabricator-diff-inline' => '77e14b60', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -795,7 +789,7 @@ return array( 'phabricator-filetree-view-css' => 'fccf9f82', 'phabricator-flag-css' => 'bba8f811', 'phabricator-keyboard-shortcut' => '1ae869f2', - 'phabricator-keyboard-shortcut-manager' => '4a021c10', + 'phabricator-keyboard-shortcut-manager' => 'c19dd9b9', 'phabricator-main-menu-view' => '5294060f', 'phabricator-nav-view-css' => 'faf6a6fc', 'phabricator-notification' => 'ccf1cbf8', @@ -805,6 +799,8 @@ return array( 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', 'phabricator-remarkup-css' => 'd1a5e11e', + 'phabricator-scroll-objective' => '0eee7a00', + 'phabricator-scroll-objective-list' => '1ca4d9db', 'phabricator-search-results-css' => 'f87d23ad', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', @@ -824,7 +820,7 @@ return array( 'phabricator-uiexample-reactor-select' => 'a155550f', 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', - 'phabricator-zindex-css' => '0233d039', + 'phabricator-zindex-css' => '998f3ce1', 'phame-css' => 'b3a0b3a3', 'pholio-css' => 'ca89d380', 'pholio-edit-css' => '07676f51', @@ -850,7 +846,7 @@ return array( 'phui-comment-form-css' => '57af2e14', 'phui-comment-panel-css' => 'f50152ad', 'phui-crumbs-view-css' => '6ece3bbb', - 'phui-curtain-view-css' => '679743bb', + 'phui-curtain-view-css' => '55dd0e59', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => 'c32e8dec', 'phui-document-view-pro-css' => '62c4dcbf', @@ -867,17 +863,17 @@ return array( 'phui-icon-view-css' => '12b387a1', 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', - 'phui-info-view-css' => 'ec92802a', - 'phui-inline-comment-view-css' => 'be663c95', + 'phui-info-view-css' => '6e217679', + 'phui-inline-comment-view-css' => 'ffd1a542', 'phui-invisible-character-view-css' => '6993d9f0', 'phui-lightbox-css' => '0a035e40', 'phui-list-view-css' => '12eb8ce6', 'phui-object-box-css' => '9cff003c', 'phui-oi-big-ui-css' => '19f9369b', 'phui-oi-color-css' => 'cd2b9b77', - 'phui-oi-drag-ui-css' => 'f12cbc9f', + 'phui-oi-drag-ui-css' => '08f4ccc3', 'phui-oi-flush-ui-css' => '9d9685d6', - 'phui-oi-list-view-css' => '7c8ec27a', + 'phui-oi-list-view-css' => '412bef1a', 'phui-oi-simple-ui-css' => 'a8beebea', 'phui-pager-css' => '77d8a794', 'phui-pinboard-view-css' => '2495140e', @@ -888,7 +884,7 @@ return array( 'phui-status-list-view-css' => 'd5263e49', 'phui-tag-view-css' => 'cc4fd402', 'phui-theme-css' => '9f261c6b', - 'phui-timeline-view-css' => '1d7ef61d', + 'phui-timeline-view-css' => '313c7f22', 'phui-two-column-view-css' => 'ce9fa0b7', 'phui-workboard-color-css' => '783cdff5', 'phui-workboard-view-css' => '3bc85455', @@ -960,22 +956,15 @@ return array( 'javelin-stratcom', 'javelin-util', ), - '08675c6d' => array( - 'javelin-behavior', - 'javelin-behavior-device', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-magical-init', - 'javelin-vector', - 'javelin-request', - 'javelin-util', - ), '087e919c' => array( 'javelin-install', 'javelin-dom', 'javelin-stratcom', 'javelin-vector', ), + '08f4ccc3' => array( + 'phui-oi-list-view-css', + ), '0a0b10e9' => array( 'javelin-behavior', 'javelin-stratcom', @@ -988,6 +977,13 @@ return array( 'javelin-dom', 'javelin-router', ), + '0eee7a00' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -1038,6 +1034,14 @@ return array( 'javelin-request', 'javelin-uri', ), + '1ca4d9db' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'phabricator-scroll-objective', + ), '1def2711' => array( 'javelin-install', 'javelin-dom', @@ -1113,14 +1117,6 @@ return array( 'javelin-install', 'javelin-event', ), - '2e3f9738' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-request', - 'javelin-workflow', - ), '2ee659ce' => array( 'javelin-install', ), @@ -1191,9 +1187,6 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), - '41af6d25' => array( - 'phui-inline-comment-view-css', - ), 42126667 => array( 'javelin-behavior', 'javelin-dom', @@ -1252,18 +1245,18 @@ return array( 'javelin-uri', 'phabricator-notification', ), + '4976858c' => array( + 'javelin-magical-init', + 'javelin-install', + 'javelin-util', + 'javelin-vector', + 'javelin-stratcom', + ), '49ae8328' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', ), - '4a021c10' => array( - 'javelin-install', - 'javelin-util', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-vector', - ), '4b3c4443' => array( 'phuix-icon-view', ), @@ -1294,19 +1287,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - '4fbbc3e9' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - 'differential-inline-comment-editor', - ), - '4fdb476d' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - ), '503e17fd' => array( 'javelin-install', 'javelin-typeahead-source', @@ -1317,6 +1297,14 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), + '51c5ad07' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-request', + 'javelin-util', + 'phabricator-shaped-request', + ), '522431f7' => array( 'javelin-behavior', 'javelin-util', @@ -1328,6 +1316,9 @@ return array( '5294060f' => array( 'phui-theme-css', ), + '54774a28' => array( + 'phui-inline-comment-view-css', + ), '54b612ba' => array( 'javelin-color', 'javelin-install', @@ -1377,12 +1368,24 @@ return array( 'javelin-stratcom', 'javelin-dom', ), + '5c68c40c' => array( + 'javelin-install', + 'phabricator-scroll-objective-list', + ), '5e2634b9' => array( 'javelin-behavior', 'javelin-aphlict', 'phabricator-phtize', 'javelin-dom', ), + '5e41c819' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'phabricator-tooltip', + 'phabricator-diff-changeset-list', + 'phabricator-diff-changeset', + ), '5e9f347c' => array( 'javelin-behavior', 'multirow-row-manager', @@ -1483,6 +1486,9 @@ return array( 'javelin-reactor', 'javelin-util', ), + '77e14b60' => array( + 'javelin-dom', + ), '782ab6e7' => array( 'javelin-behavior', 'javelin-dom', @@ -1526,13 +1532,6 @@ return array( 'javelin-vector', 'javelin-stratcom', ), - '805b806a' => array( - 'javelin-magical-init', - 'javelin-install', - 'javelin-util', - 'javelin-vector', - 'javelin-stratcom', - ), '834a1173' => array( 'javelin-behavior', 'javelin-scrollbar', @@ -1558,13 +1557,6 @@ return array( 'phabricator-notification', 'conpherence-thread-manager', ), - '8694b1df' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-tooltip', - 'changeset-view-manager', - ), '88236f00' => array( 'javelin-behavior', 'phabricator-keyboard-shortcut', @@ -1623,12 +1615,6 @@ return array( 'javelin-dom', 'javelin-request', ), - 92904457 => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-keyboard-shortcut', - ), '92b9ec77' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1640,6 +1626,16 @@ return array( 'javelin-workflow', 'javelin-dom', ), + '947753e0' => array( + 'javelin-behavior', + 'javelin-behavior-device', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-magical-init', + 'javelin-vector', + 'javelin-request', + 'javelin-util', + ), '949c0fe5' => array( 'javelin-install', ), @@ -1653,12 +1649,6 @@ return array( 'javelin-resource', 'javelin-routable', ), - '94c65b72' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'phabricator-busy', - ), '960f6a39' => array( 'javelin-behavior', 'javelin-dom', @@ -1671,18 +1661,6 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), - '9a6b9324' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'phuix-dropdown-menu', - 'phuix-action-list-view', - 'phuix-action-view', - 'phabricator-phtize', - 'changeset-view-manager', - ), '9a6dd75c' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1725,16 +1703,6 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), - 'a2828756' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), 'a3a63478' => array( 'phui-workcard-view-css', ), @@ -1822,20 +1790,18 @@ return array( 'phuix-autocomplete', 'javelin-mask', ), + 'ae95d984' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phabricator-busy', + ), 'b003d4fb' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'phuix-dropdown-menu', ), - 'b064af76' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-request', - 'javelin-util', - 'phabricator-shaped-request', - ), 'b1f0ccee' => array( 'javelin-install', 'javelin-dom', @@ -1948,6 +1914,13 @@ return array( 'javelin-install', 'javelin-dom', ), + 'c19dd9b9' => array( + 'javelin-install', + 'javelin-util', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-vector', + ), 'c420b0b9' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -2005,12 +1978,6 @@ return array( 'phabricator-shaped-request', 'conpherence-thread-manager', ), - 'ca3f91eb' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-phtize', - ), 'caade6f2' => array( 'javelin-behavior', 'javelin-request', @@ -2035,6 +2002,17 @@ return array( 'cd2b9b77' => array( 'phui-oi-list-view-css', ), + 'cf4e2140' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), 'd0c516d5' => array( 'javelin-behavior', 'javelin-dom', @@ -2202,9 +2180,6 @@ return array( 'javelin-workflow', 'javelin-json', ), - 'f12cbc9f' => array( - 'phui-oi-list-view-css', - ), 'f50152ad' => array( 'phui-timeline-view-css', ), @@ -2449,21 +2424,19 @@ return array( 'phabricator-drag-and-drop-file-upload', 'phabricator-shaped-request', 'javelin-behavior-differential-feedback-preview', - 'javelin-behavior-differential-edit-inline-comments', 'javelin-behavior-differential-populate', 'javelin-behavior-differential-diff-radios', - 'javelin-behavior-differential-comment-jump', - 'javelin-behavior-differential-keyboard-navigation', 'javelin-behavior-aphront-drag-and-drop-textarea', 'javelin-behavior-phabricator-object-selector', 'javelin-behavior-repository-crossreference', 'javelin-behavior-load-blame', - 'differential-inline-comment-editor', - 'javelin-behavior-differential-dropdown-menus', - 'javelin-behavior-differential-toggle-files', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', - 'changeset-view-manager', + 'phabricator-scroll-objective', + 'phabricator-scroll-objective-list', + 'phabricator-diff-inline', + 'phabricator-diff-changeset', + 'phabricator-diff-changeset-list', ), 'diffusion.pkg.css' => array( 'diffusion-icons-css', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index d906c738da..afa6c456a6 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -193,22 +193,22 @@ return array( 'phabricator-shaped-request', 'javelin-behavior-differential-feedback-preview', - 'javelin-behavior-differential-edit-inline-comments', 'javelin-behavior-differential-populate', 'javelin-behavior-differential-diff-radios', - 'javelin-behavior-differential-comment-jump', - 'javelin-behavior-differential-keyboard-navigation', 'javelin-behavior-aphront-drag-and-drop-textarea', 'javelin-behavior-phabricator-object-selector', 'javelin-behavior-repository-crossreference', 'javelin-behavior-load-blame', - 'differential-inline-comment-editor', - 'javelin-behavior-differential-dropdown-menus', - 'javelin-behavior-differential-toggle-files', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', - 'changeset-view-manager', + + 'phabricator-scroll-objective', + 'phabricator-scroll-objective-list', + + 'phabricator-diff-inline', + 'phabricator-diff-changeset', + 'phabricator-diff-changeset-list', ), 'diffusion.pkg.css' => array( 'diffusion-icons-css', diff --git a/resources/sql/autopatches/20140321.mstatus.2.mig.php b/resources/sql/autopatches/20140321.mstatus.2.mig.php index 7f91e00e1b..654ca7881c 100644 --- a/resources/sql/autopatches/20140321.mstatus.2.mig.php +++ b/resources/sql/autopatches/20140321.mstatus.2.mig.php @@ -35,7 +35,8 @@ foreach (new LiskMigrationIterator(new ManiphestTransaction()) as $xaction) { $id = $xaction->getID(); echo pht('Migrating %d...', $id)."\n"; - if ($xaction->getTransactionType() == ManiphestTransaction::TYPE_STATUS) { + $xn_type = ManiphestTaskStatusTransaction::TRANSACTIONTYPE; + if ($xaction->getTransactionType() == $xn_type) { $old = $xaction->getOldValue(); if ($old !== null && isset($status_map[$old])) { $old = $status_map[$old]; diff --git a/resources/sql/patches/20131020.pxactionmig.php b/resources/sql/patches/20131020.pxactionmig.php index 3d593d6e34..9a7b82f3e8 100644 --- a/resources/sql/patches/20131020.pxactionmig.php +++ b/resources/sql/patches/20131020.pxactionmig.php @@ -32,9 +32,9 @@ foreach ($rows as $row) { $project_phid = $project_row['phid']; $type_map = array( - 'name' => PhabricatorProjectTransaction::TYPE_NAME, + 'name' => PhabricatorProjectNameTransaction::TRANSACTIONTYPE, 'members' => PhabricatorProjectTransaction::TYPE_MEMBERS, - 'status' => PhabricatorProjectTransaction::TYPE_STATUS, + 'status' => PhabricatorProjectStatusTransaction::TRANSACTIONTYPE, 'canview' => PhabricatorTransactions::TYPE_VIEW_POLICY, 'canedit' => PhabricatorTransactions::TYPE_EDIT_POLICY, 'canjoin' => PhabricatorTransactions::TYPE_JOIN_POLICY, diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 611b2c118c..cbf324c847 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1511,14 +1511,18 @@ phutil_register_library_map(array( 'ManiphestTaskAssignOtherHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignOtherHeraldAction.php', 'ManiphestTaskAssignSelfHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignSelfHeraldAction.php', 'ManiphestTaskAssigneeHeraldField' => 'applications/maniphest/herald/ManiphestTaskAssigneeHeraldField.php', + 'ManiphestTaskAttachTransaction' => 'applications/maniphest/xaction/ManiphestTaskAttachTransaction.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', + 'ManiphestTaskCoverImageTransaction' => 'applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php', 'ManiphestTaskDependedOnByTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependedOnByTaskEdgeType.php', 'ManiphestTaskDependsOnTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependsOnTaskEdgeType.php', 'ManiphestTaskDescriptionHeraldField' => 'applications/maniphest/herald/ManiphestTaskDescriptionHeraldField.php', + 'ManiphestTaskDescriptionTransaction' => 'applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php', 'ManiphestTaskDetailController' => 'applications/maniphest/controller/ManiphestTaskDetailController.php', + 'ManiphestTaskEdgeTransaction' => 'applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php', 'ManiphestTaskEditBulkJobType' => 'applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php', 'ManiphestTaskEditController' => 'applications/maniphest/controller/ManiphestTaskEditController.php', 'ManiphestTaskEditEngineLock' => 'applications/maniphest/editor/ManiphestTaskEditEngineLock.php', @@ -1541,14 +1545,20 @@ phutil_register_library_map(array( 'ManiphestTaskListView' => 'applications/maniphest/view/ManiphestTaskListView.php', 'ManiphestTaskMailReceiver' => 'applications/maniphest/mail/ManiphestTaskMailReceiver.php', 'ManiphestTaskMergeInRelationship' => 'applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php', + 'ManiphestTaskMergedFromTransaction' => 'applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php', + 'ManiphestTaskMergedIntoTransaction' => 'applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php', 'ManiphestTaskOpenStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskOpenStatusDatasource.php', + 'ManiphestTaskOwnerTransaction' => 'applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php', 'ManiphestTaskPHIDResolver' => 'applications/maniphest/httpparametertype/ManiphestTaskPHIDResolver.php', 'ManiphestTaskPHIDType' => 'applications/maniphest/phid/ManiphestTaskPHIDType.php', + 'ManiphestTaskParentTransaction' => 'applications/maniphest/xaction/ManiphestTaskParentTransaction.php', 'ManiphestTaskPoints' => 'applications/maniphest/constants/ManiphestTaskPoints.php', + 'ManiphestTaskPointsTransaction' => 'applications/maniphest/xaction/ManiphestTaskPointsTransaction.php', 'ManiphestTaskPriority' => 'applications/maniphest/constants/ManiphestTaskPriority.php', 'ManiphestTaskPriorityDatasource' => 'applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php', 'ManiphestTaskPriorityHeraldAction' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php', 'ManiphestTaskPriorityHeraldField' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldField.php', + 'ManiphestTaskPriorityTransaction' => 'applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php', 'ManiphestTaskQuery' => 'applications/maniphest/query/ManiphestTaskQuery.php', 'ManiphestTaskRelationship' => 'applications/maniphest/relationship/ManiphestTaskRelationship.php', 'ManiphestTaskRelationshipSource' => 'applications/search/relationship/ManiphestTaskRelationshipSource.php', @@ -1560,9 +1570,14 @@ phutil_register_library_map(array( 'ManiphestTaskStatusHeraldAction' => 'applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php', 'ManiphestTaskStatusHeraldField' => 'applications/maniphest/herald/ManiphestTaskStatusHeraldField.php', 'ManiphestTaskStatusTestCase' => 'applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php', + 'ManiphestTaskStatusTransaction' => 'applications/maniphest/xaction/ManiphestTaskStatusTransaction.php', + 'ManiphestTaskSubpriorityTransaction' => 'applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php', 'ManiphestTaskSubtypeDatasource' => 'applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php', 'ManiphestTaskTestCase' => 'applications/maniphest/__tests__/ManiphestTaskTestCase.php', 'ManiphestTaskTitleHeraldField' => 'applications/maniphest/herald/ManiphestTaskTitleHeraldField.php', + 'ManiphestTaskTitleTransaction' => 'applications/maniphest/xaction/ManiphestTaskTitleTransaction.php', + 'ManiphestTaskTransactionType' => 'applications/maniphest/xaction/ManiphestTaskTransactionType.php', + 'ManiphestTaskUnblockTransaction' => 'applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php', 'ManiphestTransaction' => 'applications/maniphest/storage/ManiphestTransaction.php', 'ManiphestTransactionComment' => 'applications/maniphest/storage/ManiphestTransactionComment.php', 'ManiphestTransactionEditor' => 'applications/maniphest/editor/ManiphestTransactionEditor.php', @@ -3569,6 +3584,7 @@ phutil_register_library_map(array( 'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php', 'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php', 'PhabricatorProjectCardView' => 'applications/project/view/PhabricatorProjectCardView.php', + 'PhabricatorProjectColorTransaction' => 'applications/project/xaction/PhabricatorProjectColorTransaction.php', 'PhabricatorProjectColorsConfigOptionType' => 'applications/project/config/PhabricatorProjectColorsConfigOptionType.php', 'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php', 'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php', @@ -3605,7 +3621,9 @@ phutil_register_library_map(array( 'PhabricatorProjectHeraldFieldGroup' => 'applications/project/herald/PhabricatorProjectHeraldFieldGroup.php', 'PhabricatorProjectHovercardEngineExtension' => 'applications/project/engineextension/PhabricatorProjectHovercardEngineExtension.php', 'PhabricatorProjectIconSet' => 'applications/project/icon/PhabricatorProjectIconSet.php', + 'PhabricatorProjectIconTransaction' => 'applications/project/xaction/PhabricatorProjectIconTransaction.php', 'PhabricatorProjectIconsConfigOptionType' => 'applications/project/config/PhabricatorProjectIconsConfigOptionType.php', + 'PhabricatorProjectImageTransaction' => 'applications/project/xaction/PhabricatorProjectImageTransaction.php', 'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php', 'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php', 'PhabricatorProjectListView' => 'applications/project/view/PhabricatorProjectListView.php', @@ -3629,6 +3647,7 @@ phutil_register_library_map(array( 'PhabricatorProjectMenuItemController' => 'applications/project/controller/PhabricatorProjectMenuItemController.php', 'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php', 'PhabricatorProjectNameContextFreeGrammar' => 'applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php', + 'PhabricatorProjectNameTransaction' => 'applications/project/xaction/PhabricatorProjectNameTransaction.php', 'PhabricatorProjectNoProjectsDatasource' => 'applications/project/typeahead/PhabricatorProjectNoProjectsDatasource.php', 'PhabricatorProjectObjectHasProjectEdgeType' => 'applications/project/edge/PhabricatorProjectObjectHasProjectEdgeType.php', 'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php', @@ -3650,8 +3669,10 @@ phutil_register_library_map(array( 'PhabricatorProjectSilenceController' => 'applications/project/controller/PhabricatorProjectSilenceController.php', 'PhabricatorProjectSilencedEdgeType' => 'applications/project/edge/PhabricatorProjectSilencedEdgeType.php', 'PhabricatorProjectSlug' => 'applications/project/storage/PhabricatorProjectSlug.php', + 'PhabricatorProjectSlugsTransaction' => 'applications/project/xaction/PhabricatorProjectSlugsTransaction.php', 'PhabricatorProjectStandardCustomField' => 'applications/project/customfield/PhabricatorProjectStandardCustomField.php', 'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php', + 'PhabricatorProjectStatusTransaction' => 'applications/project/xaction/PhabricatorProjectStatusTransaction.php', 'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php', 'PhabricatorProjectSubprojectsController' => 'applications/project/controller/PhabricatorProjectSubprojectsController.php', 'PhabricatorProjectSubprojectsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectSubprojectsProfileMenuItem.php', @@ -3659,6 +3680,7 @@ phutil_register_library_map(array( 'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php', 'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php', 'PhabricatorProjectTransactionQuery' => 'applications/project/query/PhabricatorProjectTransactionQuery.php', + 'PhabricatorProjectTransactionType' => 'applications/project/xaction/PhabricatorProjectTransactionType.php', 'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php', 'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php', 'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php', @@ -4361,9 +4383,12 @@ phutil_register_library_map(array( 'PholioDefaultViewCapability' => 'applications/pholio/capability/PholioDefaultViewCapability.php', 'PholioImage' => 'applications/pholio/storage/PholioImage.php', 'PholioImageDescriptionTransaction' => 'applications/pholio/xaction/PholioImageDescriptionTransaction.php', + 'PholioImageFileTransaction' => 'applications/pholio/xaction/PholioImageFileTransaction.php', 'PholioImageNameTransaction' => 'applications/pholio/xaction/PholioImageNameTransaction.php', 'PholioImagePHIDType' => 'applications/pholio/phid/PholioImagePHIDType.php', 'PholioImageQuery' => 'applications/pholio/query/PholioImageQuery.php', + 'PholioImageReplaceTransaction' => 'applications/pholio/xaction/PholioImageReplaceTransaction.php', + 'PholioImageSequenceTransaction' => 'applications/pholio/xaction/PholioImageSequenceTransaction.php', 'PholioImageTransactionType' => 'applications/pholio/xaction/PholioImageTransactionType.php', 'PholioImageUploadController' => 'applications/pholio/controller/PholioImageUploadController.php', 'PholioInlineController' => 'applications/pholio/controller/PholioInlineController.php', @@ -4383,6 +4408,7 @@ phutil_register_library_map(array( 'PholioMockHeraldField' => 'applications/pholio/herald/PholioMockHeraldField.php', 'PholioMockHeraldFieldGroup' => 'applications/pholio/herald/PholioMockHeraldFieldGroup.php', 'PholioMockImagesView' => 'applications/pholio/view/PholioMockImagesView.php', + 'PholioMockInlineTransaction' => 'applications/pholio/xaction/PholioMockInlineTransaction.php', 'PholioMockListController' => 'applications/pholio/controller/PholioMockListController.php', 'PholioMockMailReceiver' => 'applications/pholio/mail/PholioMockMailReceiver.php', 'PholioMockNameHeraldField' => 'applications/pholio/herald/PholioMockNameHeraldField.php', @@ -4598,11 +4624,14 @@ phutil_register_library_map(array( 'PhrictionDocumentHeraldAdapter' => 'applications/phriction/herald/PhrictionDocumentHeraldAdapter.php', 'PhrictionDocumentHeraldField' => 'applications/phriction/herald/PhrictionDocumentHeraldField.php', 'PhrictionDocumentHeraldFieldGroup' => 'applications/phriction/herald/PhrictionDocumentHeraldFieldGroup.php', + 'PhrictionDocumentMoveToTransaction' => 'applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php', 'PhrictionDocumentPHIDType' => 'applications/phriction/phid/PhrictionDocumentPHIDType.php', 'PhrictionDocumentPathHeraldField' => 'applications/phriction/herald/PhrictionDocumentPathHeraldField.php', 'PhrictionDocumentQuery' => 'applications/phriction/query/PhrictionDocumentQuery.php', 'PhrictionDocumentStatus' => 'applications/phriction/constants/PhrictionDocumentStatus.php', 'PhrictionDocumentTitleHeraldField' => 'applications/phriction/herald/PhrictionDocumentTitleHeraldField.php', + 'PhrictionDocumentTitleTransaction' => 'applications/phriction/xaction/PhrictionDocumentTitleTransaction.php', + 'PhrictionDocumentTransactionType' => 'applications/phriction/xaction/PhrictionDocumentTransactionType.php', 'PhrictionEditConduitAPIMethod' => 'applications/phriction/conduit/PhrictionEditConduitAPIMethod.php', 'PhrictionEditController' => 'applications/phriction/controller/PhrictionEditController.php', 'PhrictionHistoryConduitAPIMethod' => 'applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php', @@ -6569,14 +6598,18 @@ phutil_register_library_map(array( 'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction', 'ManiphestTaskAssignSelfHeraldAction' => 'ManiphestTaskAssignHeraldAction', 'ManiphestTaskAssigneeHeraldField' => 'ManiphestTaskHeraldField', + 'ManiphestTaskAttachTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskAuthorHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskAuthorPolicyRule' => 'PhabricatorPolicyRule', 'ManiphestTaskCloseAsDuplicateRelationship' => 'ManiphestTaskRelationship', 'ManiphestTaskClosedStatusDatasource' => 'PhabricatorTypeaheadDatasource', + 'ManiphestTaskCoverImageTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskDependedOnByTaskEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskDependsOnTaskEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskDescriptionHeraldField' => 'ManiphestTaskHeraldField', + 'ManiphestTaskDescriptionTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskDetailController' => 'ManiphestController', + 'ManiphestTaskEdgeTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskEditBulkJobType' => 'PhabricatorWorkerBulkJobType', 'ManiphestTaskEditController' => 'ManiphestController', 'ManiphestTaskEditEngineLock' => 'PhabricatorEditEngineLock', @@ -6599,14 +6632,20 @@ phutil_register_library_map(array( 'ManiphestTaskListView' => 'ManiphestView', 'ManiphestTaskMailReceiver' => 'PhabricatorObjectMailReceiver', 'ManiphestTaskMergeInRelationship' => 'ManiphestTaskRelationship', + 'ManiphestTaskMergedFromTransaction' => 'ManiphestTaskTransactionType', + 'ManiphestTaskMergedIntoTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskOpenStatusDatasource' => 'PhabricatorTypeaheadDatasource', + 'ManiphestTaskOwnerTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskPHIDResolver' => 'PhabricatorPHIDResolver', 'ManiphestTaskPHIDType' => 'PhabricatorPHIDType', + 'ManiphestTaskParentTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskPoints' => 'Phobject', + 'ManiphestTaskPointsTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskPriority' => 'ManiphestConstants', 'ManiphestTaskPriorityDatasource' => 'PhabricatorTypeaheadDatasource', 'ManiphestTaskPriorityHeraldAction' => 'HeraldAction', 'ManiphestTaskPriorityHeraldField' => 'ManiphestTaskHeraldField', + 'ManiphestTaskPriorityTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'ManiphestTaskRelationship' => 'PhabricatorObjectRelationship', 'ManiphestTaskRelationshipSource' => 'PhabricatorObjectRelationshipSource', @@ -6618,10 +6657,15 @@ phutil_register_library_map(array( 'ManiphestTaskStatusHeraldAction' => 'HeraldAction', 'ManiphestTaskStatusHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskStatusTestCase' => 'PhabricatorTestCase', + 'ManiphestTaskStatusTransaction' => 'ManiphestTaskTransactionType', + 'ManiphestTaskSubpriorityTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskSubtypeDatasource' => 'PhabricatorTypeaheadDatasource', 'ManiphestTaskTestCase' => 'PhabricatorTestCase', 'ManiphestTaskTitleHeraldField' => 'ManiphestTaskHeraldField', - 'ManiphestTransaction' => 'PhabricatorApplicationTransaction', + 'ManiphestTaskTitleTransaction' => 'ManiphestTaskTransactionType', + 'ManiphestTaskTransactionType' => 'PhabricatorModularTransactionType', + 'ManiphestTaskUnblockTransaction' => 'ManiphestTaskTransactionType', + 'ManiphestTransaction' => 'PhabricatorModularTransaction', 'ManiphestTransactionComment' => 'PhabricatorApplicationTransactionComment', 'ManiphestTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'ManiphestTransactionQuery' => 'PhabricatorApplicationTransactionQuery', @@ -8944,6 +8988,7 @@ phutil_register_library_map(array( 'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectCardView' => 'AphrontTagView', + 'PhabricatorProjectColorTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectColorsConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'PhabricatorProjectColumn' => array( 'PhabricatorProjectDAO', @@ -8993,7 +9038,9 @@ phutil_register_library_map(array( 'PhabricatorProjectHeraldFieldGroup' => 'HeraldFieldGroup', 'PhabricatorProjectHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'PhabricatorProjectIconSet' => 'PhabricatorIconSet', + 'PhabricatorProjectIconTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectIconsConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorProjectImageTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectListView' => 'AphrontView', 'PhabricatorProjectLockController' => 'PhabricatorProjectController', @@ -9016,6 +9063,7 @@ phutil_register_library_map(array( 'PhabricatorProjectMenuItemController' => 'PhabricatorProjectController', 'PhabricatorProjectMoveController' => 'PhabricatorProjectController', 'PhabricatorProjectNameContextFreeGrammar' => 'PhutilContextFreeGrammar', + 'PhabricatorProjectNameTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectNoProjectsDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorProjectObjectHasProjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource', @@ -9037,18 +9085,21 @@ phutil_register_library_map(array( 'PhabricatorProjectSilenceController' => 'PhabricatorProjectController', 'PhabricatorProjectSilencedEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectSlug' => 'PhabricatorProjectDAO', + 'PhabricatorProjectSlugsTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectStandardCustomField' => array( 'PhabricatorProjectCustomField', 'PhabricatorStandardCustomFieldInterface', ), 'PhabricatorProjectStatus' => 'Phobject', + 'PhabricatorProjectStatusTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectSubprojectWarningController' => 'PhabricatorProjectController', 'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController', 'PhabricatorProjectSubprojectsProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator', - 'PhabricatorProjectTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorProjectTransaction' => 'PhabricatorModularTransaction', 'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorProjectTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorProjectTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener', 'PhabricatorProjectUpdateController' => 'PhabricatorProjectController', 'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', @@ -9908,9 +9959,12 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'PholioImageDescriptionTransaction' => 'PholioImageTransactionType', + 'PholioImageFileTransaction' => 'PholioImageTransactionType', 'PholioImageNameTransaction' => 'PholioImageTransactionType', 'PholioImagePHIDType' => 'PhabricatorPHIDType', 'PholioImageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PholioImageReplaceTransaction' => 'PholioImageTransactionType', + 'PholioImageSequenceTransaction' => 'PholioImageTransactionType', 'PholioImageTransactionType' => 'PholioTransactionType', 'PholioImageUploadController' => 'PholioController', 'PholioInlineController' => 'PholioController', @@ -9943,6 +9997,7 @@ phutil_register_library_map(array( 'PholioMockHeraldField' => 'HeraldField', 'PholioMockHeraldFieldGroup' => 'HeraldFieldGroup', 'PholioMockImagesView' => 'AphrontView', + 'PholioMockInlineTransaction' => 'PholioMockTransactionType', 'PholioMockListController' => 'PholioController', 'PholioMockMailReceiver' => 'PhabricatorObjectMailReceiver', 'PholioMockNameHeraldField' => 'PholioMockHeraldField', @@ -10216,11 +10271,14 @@ phutil_register_library_map(array( 'PhrictionDocumentHeraldAdapter' => 'HeraldAdapter', 'PhrictionDocumentHeraldField' => 'HeraldField', 'PhrictionDocumentHeraldFieldGroup' => 'HeraldFieldGroup', + 'PhrictionDocumentMoveToTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentPHIDType' => 'PhabricatorPHIDType', 'PhrictionDocumentPathHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhrictionDocumentStatus' => 'PhrictionConstants', 'PhrictionDocumentTitleHeraldField' => 'PhrictionDocumentHeraldField', + 'PhrictionDocumentTitleTransaction' => 'PhrictionDocumentTransactionType', + 'PhrictionDocumentTransactionType' => 'PhabricatorModularTransactionType', 'PhrictionEditConduitAPIMethod' => 'PhrictionConduitAPIMethod', 'PhrictionEditController' => 'PhrictionController', 'PhrictionHistoryConduitAPIMethod' => 'PhrictionConduitAPIMethod', @@ -10234,7 +10292,7 @@ phutil_register_library_map(array( 'PhrictionReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhrictionSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhrictionSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhrictionTransaction' => 'PhabricatorApplicationTransaction', + 'PhrictionTransaction' => 'PhabricatorModularTransaction', 'PhrictionTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhrictionTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhrictionTransactionQuery' => 'PhabricatorApplicationTransactionQuery', diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index 58d8f6cf6f..c4d1b47c40 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -471,7 +471,7 @@ abstract class PhabricatorController extends AphrontController { ->setViewer($this->getViewer()); } - public function newCurtainView($object) { + public function newCurtainView($object = null) { $viewer = $this->getViewer(); $action_id = celerity_generate_unique_node_id(); @@ -491,9 +491,11 @@ abstract class PhabricatorController extends AphrontController { ->setViewer($viewer) ->setActionList($action_list); - $panels = PHUICurtainExtension::buildExtensionPanels($viewer, $object); - foreach ($panels as $panel) { - $curtain->addPanel($panel); + if ($object) { + $panels = PHUICurtainExtension::buildExtensionPanels($viewer, $object); + foreach ($panels as $panel) { + $curtain->addPanel($panel); + } } return $curtain; diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 6cb39c1510..769ac37e1a 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -465,7 +465,6 @@ final class DifferentialRevisionViewController extends DifferentialController { } Javelin::initBehavior('differential-user-select'); - Javelin::initBehavior('differential-keyboard-navigation'); $view = id(new PHUITwoColumnView()) ->setHeader($header) diff --git a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php index cbb5cbc6f4..7a694cfd98 100644 --- a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php @@ -41,8 +41,6 @@ final class DifferentialChangesetOneUpRenderer $column_width = 4; - $hidden = new PHUIDiffRevealIconView(); - $out = array(); foreach ($primitives as $k => $p) { $type = $p['type']; @@ -53,27 +51,6 @@ final class DifferentialChangesetOneUpRenderer case 'new-file': $is_old = ($type == 'old' || $type == 'old-file'); - $o_hidden = array(); - $n_hidden = array(); - - for ($look = $k + 1; isset($primitives[$look]); $look++) { - $next = $primitives[$look]; - switch ($next['type']) { - case 'inline': - $comment = $next['comment']; - if ($comment->isHidden()) { - if ($next['right']) { - $n_hidden[] = $comment; - } else { - $o_hidden[] = $comment; - } - } - break; - default: - break 2; - } - } - $cells = array(); if ($is_old) { if ($p['htype']) { @@ -93,9 +70,6 @@ final class DifferentialChangesetOneUpRenderer } $line = $p['line']; - if ($o_hidden) { - $line = array($hidden, $line); - } $cells[] = phutil_tag( 'th', @@ -122,9 +96,6 @@ final class DifferentialChangesetOneUpRenderer } $oline = $p['oline']; - if ($o_hidden) { - $oline = array($hidden, $oline); - } $cells[] = phutil_tag('th', array('id' => $left_id), $oline); } @@ -140,9 +111,6 @@ final class DifferentialChangesetOneUpRenderer } $line = $p['line']; - if ($n_hidden) { - $line = array($hidden, $line); - } $cells[] = phutil_tag( 'th', diff --git a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php index 9e4f9c049d..5d476f5136 100644 --- a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php @@ -69,8 +69,6 @@ final class DifferentialChangesetTwoUpRenderer $depths = $this->getDepths(); $mask = $this->getMask(); - $hidden = new PHUIDiffRevealIconView(); - for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) { if (empty($mask[$ii])) { // If we aren't going to show this line, we've just entered a gap. @@ -241,9 +239,6 @@ final class DifferentialChangesetTwoUpRenderer $new_comments = $this->getNewComments(); $scaffolds = array(); - $o_hidden = array(); - $n_hidden = array(); - if ($o_num && isset($old_comments[$o_num])) { foreach ($old_comments[$o_num] as $comment) { $inline = $this->buildInlineComment( @@ -251,10 +246,6 @@ final class DifferentialChangesetTwoUpRenderer $on_right = false); $scaffold = $this->getRowScaffoldForInline($inline); - if ($comment->isHidden()) { - $o_hidden[] = $comment; - } - if ($n_num && isset($new_comments[$n_num])) { foreach ($new_comments[$n_num] as $key => $new_comment) { if ($comment->isCompatible($new_comment)) { @@ -262,10 +253,6 @@ final class DifferentialChangesetTwoUpRenderer $new_comment, $on_right = true); - if ($new_comment->isHidden()) { - $n_hidden = $new_comment; - } - $scaffold->addInlineView($companion); unset($new_comments[$n_num][$key]); break; @@ -284,22 +271,10 @@ final class DifferentialChangesetTwoUpRenderer $comment, $on_right = true); - if ($comment->isHidden()) { - $n_hidden[] = $comment; - } - $scaffolds[] = $this->getRowScaffoldForInline($inline); } } - if ($o_hidden) { - $o_num = array($hidden, $o_num); - } - - if ($n_hidden) { - $n_num = array($hidden, $n_num); - } - // NOTE: This is a unicode zero-width space, which we use as a hint when // intercepting 'copy' events to make sure sensible text ends up on the // clipboard. See the 'phabricator-oncopy' behavior. @@ -348,6 +323,12 @@ final class DifferentialChangesetTwoUpRenderer $new = $this->renderImageStage($new_file); } + // If we don't have an explicit "vs" changeset, it's the left side of the + // "id" changeset. + if (!$vs) { + $vs = $id; + } + $html_old = array(); $html_new = array(); foreach ($this->getOldComments() as $on_line => $comment_group) { diff --git a/src/applications/differential/view/DifferentialChangesetDetailView.php b/src/applications/differential/view/DifferentialChangesetDetailView.php index eed5467b63..e80dc20ec7 100644 --- a/src/applications/differential/view/DifferentialChangesetDetailView.php +++ b/src/applications/differential/view/DifferentialChangesetDetailView.php @@ -150,15 +150,51 @@ final class DifferentialChangesetDetailView extends AphrontView { $renderer = DifferentialChangesetHTMLRenderer::getHTMLRendererByKey( $this->getRenderer()); + $changeset_id = $this->changeset->getID(); + + $vs_id = $this->getVsChangesetID(); + if (!$vs_id) { + // Showing a changeset normally. + $left_id = $changeset_id; + $right_id = $changeset_id; + } else if ($vs_id == -1) { + // Showing a synthetic "deleted" changeset for a file which was + // removed between changes. + $left_id = $changeset_id; + $right_id = null; + } else { + // Showing a diff-of-diffs. + $left_id = $vs_id; + $right_id = $changeset_id; + } + + // In the persistent banner, emphasize the current filename. + $path_part = dirname($display_filename); + $file_part = basename($display_filename); + $display_parts = array(); + if (strlen($path_part)) { + $path_part = $path_part.'/'; + $display_parts[] = phutil_tag( + 'span', + array( + 'class' => 'diff-banner-path', + ), + $path_part); + } + $display_parts[] = phutil_tag( + 'span', + array( + 'class' => 'diff-banner-file', + ), + $file_part); + return javelin_tag( 'div', array( 'sigil' => 'differential-changeset', 'meta' => array( - 'left' => nonempty( - $this->getVsChangesetID(), - $this->changeset->getID()), - 'right' => $this->changeset->getID(), + 'left' => $left_id, + 'right' => $right_id, 'renderURI' => $this->getRenderURI(), 'whitespace' => $this->getWhitespace(), 'highlight' => null, @@ -166,7 +202,10 @@ final class DifferentialChangesetDetailView extends AphrontView { 'ref' => $this->getRenderingRef(), 'autoload' => $this->getAutoload(), 'loaded' => $this->getLoaded(), - 'undoTemplates' => $renderer->renderUndoTemplates(), + 'undoTemplates' => hsprintf('%s', $renderer->renderUndoTemplates()), + 'displayPath' => hsprintf('%s', $display_parts), + 'objectiveName' => basename($display_filename), + 'icon' => $display_icon, ), 'class' => $class, 'id' => $id, diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 533f579b0a..4ac2612ecd 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -131,36 +131,6 @@ final class DifferentialChangesetListView extends AphrontView { $changesets = $this->changesets; - Javelin::initBehavior('differential-toggle-files', array( - 'pht' => array( - 'undo' => pht('Undo'), - 'collapsed' => pht('This file content has been collapsed.'), - ), - )); - - Javelin::initBehavior( - 'differential-dropdown-menus', - array( - 'pht' => array( - 'Open in Editor' => pht('Open in Editor'), - 'Show All Context' => pht('Show All Context'), - 'All Context Shown' => pht('All Context Shown'), - "Can't Toggle Unloaded File" => pht("Can't Toggle Unloaded File"), - 'Expand File' => pht('Expand File'), - 'Collapse File' => pht('Collapse File'), - 'Browse in Diffusion' => pht('Browse in Diffusion'), - 'View Standalone' => pht('View Standalone'), - 'Show Raw File (Left)' => pht('Show Raw File (Left)'), - 'Show Raw File (Right)' => pht('Show Raw File (Right)'), - 'Configure Editor' => pht('Configure Editor'), - 'Load Changes' => pht('Load Changes'), - 'View Side-by-Side' => pht('View Side-by-Side'), - 'View Unified' => pht('View Unified'), - 'Change Text Encoding...' => pht('Change Text Encoding...'), - 'Highlight As...' => pht('Highlight As...'), - ), - )); - $renderer = DifferentialChangesetParser::getDefaultRendererForViewer( $viewer); @@ -169,11 +139,6 @@ final class DifferentialChangesetListView extends AphrontView { foreach ($changesets as $key => $changeset) { $file = $changeset->getFilename(); - $class = 'differential-changeset'; - if (!$this->inlineURI) { - $class .= ' differential-changeset-noneditable'; - } - $ref = $this->references[$key]; $detail = id(new DifferentialChangesetDetailView()) @@ -238,20 +203,79 @@ final class DifferentialChangesetListView extends AphrontView { $this->requireResource('aphront-tooltip-css'); - $this->initBehavior('differential-populate', array( + $this->initBehavior( + 'differential-populate', + array( 'changesetViewIDs' => $ids, + 'inlineURI' => $this->inlineURI, + 'pht' => array( + 'Open in Editor' => pht('Open in Editor'), + 'Show All Context' => pht('Show All Context'), + 'All Context Shown' => pht('All Context Shown'), + "Can't Toggle Unloaded File" => pht("Can't Toggle Unloaded File"), + 'Expand File' => pht('Expand File'), + 'Collapse File' => pht('Collapse File'), + 'Browse in Diffusion' => pht('Browse in Diffusion'), + 'View Standalone' => pht('View Standalone'), + 'Show Raw File (Left)' => pht('Show Raw File (Left)'), + 'Show Raw File (Right)' => pht('Show Raw File (Right)'), + 'Configure Editor' => pht('Configure Editor'), + 'Load Changes' => pht('Load Changes'), + 'View Side-by-Side' => pht('View Side-by-Side'), + 'View Unified' => pht('View Unified'), + 'Change Text Encoding...' => pht('Change Text Encoding...'), + 'Highlight As...' => pht('Highlight As...'), + + 'Loading...' => pht('Loading...'), + + 'Jump to next change.' => pht('Jump to next change.'), + 'Jump to previous change.' => pht('Jump to previous change.'), + 'Jump to next file.' => pht('Jump to next file.'), + 'Jump to previous file.' => pht('Jump to previous file.'), + 'Jump to next inline comment.' => pht('Jump to next inline comment.'), + 'Jump to previous inline comment.' => + pht('Jump to previous inline comment.'), + 'Jump to the table of contents.' => + pht('Jump to the table of contents.'), + + 'Edit selected inline comment.' => + pht('Edit selected inline comment.'), + 'You must select a comment to edit.' => + pht('You must select a comment to edit.'), + + 'Reply to selected inline comment or change.' => + pht('Reply to selected inline comment or change.'), + 'You must select a comment or change to reply to.' => + pht('You must select a comment or change to reply to.'), + 'Reply and quote selected inline comment.' => + pht('Reply and quote selected inline comment.'), + + 'Mark or unmark selected inline comment as done.' => + pht('Mark or unmark selected inline comment as done.'), + 'You must select a comment to mark done.' => + pht('You must select a comment to mark done.'), + + 'Hide or show inline comment.' => + pht('Hide or show inline comment.'), + 'You must select a comment to hide.' => + pht('You must select a comment to hide.'), + + 'Jump to next inline comment, including hidden comments.' => + pht('Jump to next inline comment, including hidden comments.'), + 'Jump to previous inline comment, including hidden comments.' => + pht('Jump to previous inline comment, including hidden comments.'), + + 'This file content has been collapsed.' => + pht('This file content has been collapsed.'), + 'Show Content' => pht('Show Content'), + + 'Hide or show the current file.' => + pht('Hide or show the current file.'), + 'You must select a file to hide or show.' => + pht('You must select a file to hide or show.'), + ), )); - $this->initBehavior('differential-comment-jump', array()); - - if ($this->inlineURI) { - Javelin::initBehavior('differential-edit-inline-comments', array( - 'uri' => $this->inlineURI, - 'stage' => 'differential-review-stage', - 'revealIcon' => hsprintf('%s', new PHUIDiffRevealIconView()), - )); - } - if ($this->header) { $header = $this->header; } else { diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 10677b762f..554ad64e5e 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -720,8 +720,6 @@ final class DiffusionCommitController extends DiffusionController { $request = $this->getRequest(); $viewer = $request->getUser(); - Javelin::initBehavior('differential-keyboard-navigation'); - // TODO: This is pretty awkward, unify the CSS between Diffusion and // Differential better. require_celerity_resource('differential-core-view-css'); diff --git a/src/applications/files/controller/PhabricatorFileComposeController.php b/src/applications/files/controller/PhabricatorFileComposeController.php index 930ec61483..7afd99330a 100644 --- a/src/applications/files/controller/PhabricatorFileComposeController.php +++ b/src/applications/files/controller/PhabricatorFileComposeController.php @@ -48,7 +48,8 @@ final class PhabricatorFileComposeController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_IMAGE) + ->setTransactionType( + PhabricatorProjectImageTransaction::TRANSACTIONTYPE) ->setNewValue($file->getPHID()); $editor = id(new PhabricatorProjectTransactionEditor()) diff --git a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php index 936676b902..65dc0a12a2 100644 --- a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php +++ b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php @@ -94,11 +94,12 @@ final class PhabricatorFileTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setNewValue(pht('File Scramble Test Task')); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) + ->setTransactionType( + ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE) ->setNewValue('{'.$file->getMonogram().'}'); id(new ManiphestTransactionEditor()) diff --git a/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php b/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php index cf1da51bca..5f5c198a84 100644 --- a/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php +++ b/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php @@ -133,7 +133,7 @@ final class ManiphestTaskTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); @@ -169,11 +169,11 @@ final class ManiphestTaskTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) ->setNewValue($pri); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY) + ->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE) ->setNewValue($sub); return $this->applyTaskTransactions($viewer, $src, $xactions); @@ -192,11 +192,11 @@ final class ManiphestTaskTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) ->setNewValue($pri); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY) + ->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE) ->setNewValue($sub); return $this->applyTaskTransactions($viewer, $src, $xactions); diff --git a/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php b/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php index f4ca24863b..0e11530b51 100644 --- a/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php +++ b/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php @@ -76,9 +76,9 @@ final class ManiphestTaskEditBulkJobType $value_map = array(); $type_map = array( 'add_comment' => PhabricatorTransactions::TYPE_COMMENT, - 'assign' => ManiphestTransaction::TYPE_OWNER, - 'status' => ManiphestTransaction::TYPE_STATUS, - 'priority' => ManiphestTransaction::TYPE_PRIORITY, + 'assign' => ManiphestTaskOwnerTransaction::TRANSACTIONTYPE, + 'status' => ManiphestTaskStatusTransaction::TRANSACTIONTYPE, + 'priority' => ManiphestTaskPriorityTransaction::TRANSACTIONTYPE, 'add_project' => PhabricatorTransactions::TYPE_EDGE, 'remove_project' => PhabricatorTransactions::TYPE_EDGE, 'add_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS, @@ -114,13 +114,13 @@ final class ManiphestTaskEditBulkJobType case PhabricatorTransactions::TYPE_COMMENT: $current = null; break; - case ManiphestTransaction::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: $current = $task->getOwnerPHID(); break; - case ManiphestTransaction::TYPE_STATUS: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: $current = $task->getStatus(); break; - case ManiphestTransaction::TYPE_PRIORITY: + case ManiphestTaskPriorityTransaction::TRANSACTIONTYPE: $current = $task->getPriority(); break; case PhabricatorTransactions::TYPE_EDGE: @@ -153,7 +153,7 @@ final class ManiphestTaskEditBulkJobType } $value = head($value); break; - case ManiphestTransaction::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: if (empty($value)) { continue 2; } diff --git a/src/applications/maniphest/command/ManiphestAssignEmailCommand.php b/src/applications/maniphest/command/ManiphestAssignEmailCommand.php index 98e8a913a8..de014b6c66 100644 --- a/src/applications/maniphest/command/ManiphestAssignEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestAssignEmailCommand.php @@ -53,7 +53,7 @@ final class ManiphestAssignEmailCommand } $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) ->setNewValue($assign_phid); return $xactions; diff --git a/src/applications/maniphest/command/ManiphestClaimEmailCommand.php b/src/applications/maniphest/command/ManiphestClaimEmailCommand.php index 4a6a348dbb..babc853b67 100644 --- a/src/applications/maniphest/command/ManiphestClaimEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestClaimEmailCommand.php @@ -23,7 +23,7 @@ final class ManiphestClaimEmailCommand $xactions = array(); $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) ->setNewValue($viewer->getPHID()); return $xactions; diff --git a/src/applications/maniphest/command/ManiphestCloseEmailCommand.php b/src/applications/maniphest/command/ManiphestCloseEmailCommand.php index 8104fd8b8d..eab01ad615 100644 --- a/src/applications/maniphest/command/ManiphestCloseEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestCloseEmailCommand.php @@ -24,7 +24,7 @@ final class ManiphestCloseEmailCommand $xactions = array(); $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE) ->setNewValue(ManiphestTaskStatus::getDefaultClosedStatus()); return $xactions; diff --git a/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php b/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php index f774578240..5d7bbb1eea 100644 --- a/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php @@ -71,7 +71,7 @@ final class ManiphestPriorityEmailCommand } $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) ->setNewValue($priority); return $xactions; diff --git a/src/applications/maniphest/command/ManiphestStatusEmailCommand.php b/src/applications/maniphest/command/ManiphestStatusEmailCommand.php index dace0cb255..0387cae34e 100644 --- a/src/applications/maniphest/command/ManiphestStatusEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestStatusEmailCommand.php @@ -73,7 +73,7 @@ final class ManiphestStatusEmailCommand } $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE) ->setNewValue($status); return $xactions; diff --git a/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php index d2a8de7d81..5a6a8cea33 100644 --- a/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php +++ b/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php @@ -60,7 +60,7 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { if ($is_new) { $task->setTitle((string)$request->getValue('title')); $task->setDescription((string)$request->getValue('description')); - $changes[ManiphestTransaction::TYPE_STATUS] = + $changes[ManiphestTaskStatusTransaction::TRANSACTIONTYPE] = ManiphestTaskStatus::getDefaultStatus(); $changes[PhabricatorTransactions::TYPE_SUBSCRIBERS] = array('+' => array($request->getUser()->getPHID())); @@ -73,12 +73,12 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { $title = $request->getValue('title'); if ($title !== null) { - $changes[ManiphestTransaction::TYPE_TITLE] = $title; + $changes[ManiphestTaskTitleTransaction::TRANSACTIONTYPE] = $title; } $desc = $request->getValue('description'); if ($desc !== null) { - $changes[ManiphestTransaction::TYPE_DESCRIPTION] = $desc; + $changes[ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE] = $desc; } $status = $request->getValue('status'); @@ -88,7 +88,7 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { throw id(new ConduitException('ERR-INVALID-PARAMETER')) ->setErrorDescription(pht('Status set to invalid value.')); } - $changes[ManiphestTransaction::TYPE_STATUS] = $status; + $changes[ManiphestTaskStatusTransaction::TRANSACTIONTYPE] = $status; } } @@ -99,7 +99,7 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { throw id(new ConduitException('ERR-INVALID-PARAMETER')) ->setErrorDescription(pht('Priority set to invalid value.')); } - $changes[ManiphestTransaction::TYPE_PRIORITY] = $priority; + $changes[ManiphestTaskPriorityTransaction::TRANSACTIONTYPE] = $priority; } $owner_phid = $request->getValue('ownerPHID'); @@ -108,7 +108,7 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { array($owner_phid), PhabricatorPeopleUserPHIDType::TYPECONST, 'ownerPHID'); - $changes[ManiphestTransaction::TYPE_OWNER] = $owner_phid; + $changes[ManiphestTaskOwnerTransaction::TRANSACTIONTYPE] = $owner_phid; } $ccs = $request->getValue('ccPHIDs'); diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index f16281691b..3cc420a4c6 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -93,7 +93,7 @@ final class ManiphestReportController extends ManiphestController { ORDER BY x.dateCreated ASC', $table->getTableName(), $joins, - ManiphestTransaction::TYPE_STATUS); + ManiphestTaskStatusTransaction::TRANSACTIONTYPE); $stats = array(); $day_buckets = array(); diff --git a/src/applications/maniphest/controller/ManiphestSubpriorityController.php b/src/applications/maniphest/controller/ManiphestSubpriorityController.php index 25920212c8..e91cc65d4a 100644 --- a/src/applications/maniphest/controller/ManiphestSubpriorityController.php +++ b/src/applications/maniphest/controller/ManiphestSubpriorityController.php @@ -43,11 +43,11 @@ final class ManiphestSubpriorityController extends ManiphestController { $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) ->setNewValue($pri); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY) + ->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE) ->setNewValue($sub); $editor = id(new ManiphestTransactionEditor()) diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index 514090dda8..5384825de8 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -277,7 +277,7 @@ final class ManiphestTaskDetailController extends ManiphestController { $can_create = (bool)$edit_config; $can_reassign = $edit_engine->hasEditAccessToTransaction( - ManiphestTransaction::TYPE_OWNER); + ManiphestTaskOwnerTransaction::TRANSACTIONTYPE); if ($can_create) { $form_key = $edit_config->getIdentifier(); diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index ad0171592c..52491ce6e6 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -150,7 +150,7 @@ EODOCS ->setConduitDescription(pht('Create as a subtask of another task.')) ->setConduitTypeDescription(pht('PHID of the parent task.')) ->setAliases(array('parentPHID')) - ->setTransactionType(ManiphestTransaction::TYPE_PARENT) + ->setTransactionType(ManiphestTaskParentTransaction::TRANSACTIONTYPE) ->setHandleParameterType(new ManiphestTaskListHTTPParameterType()) ->setSingleValue(null) ->setIsReorderable(false) @@ -179,7 +179,7 @@ EODOCS ->setDescription(pht('Name of the task.')) ->setConduitDescription(pht('Rename the task.')) ->setConduitTypeDescription(pht('New task name.')) - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getTitle()), id(new PhabricatorUsersEditField()) @@ -190,7 +190,7 @@ EODOCS ->setConduitDescription(pht('Reassign the task.')) ->setConduitTypeDescription( pht('New task owner, or `null` to unassign.')) - ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setSingleValue($object->getOwnerPHID()) ->setCommentActionLabel(pht('Assign / Claim')) @@ -201,7 +201,7 @@ EODOCS ->setDescription(pht('Status of the task.')) ->setConduitDescription(pht('Change the task status.')) ->setConduitTypeDescription(pht('New task status constant.')) - ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setValue($object->getStatus()) ->setOptions($status_map) @@ -213,7 +213,7 @@ EODOCS ->setDescription(pht('Priority of the task.')) ->setConduitDescription(pht('Change the priority of the task.')) ->setConduitTypeDescription(pht('New task priority constant.')) - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setValue($object->getPriority()) ->setOptions($priority_map) @@ -230,7 +230,7 @@ EODOCS ->setDescription(pht('Point value of the task.')) ->setConduitDescription(pht('Change the task point value.')) ->setConduitTypeDescription(pht('New task point value.')) - ->setTransactionType(ManiphestTransaction::TYPE_POINTS) + ->setTransactionType(ManiphestTaskPointsTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setValue($object->getPoints()) ->setCommentActionLabel($action_label); @@ -242,7 +242,7 @@ EODOCS ->setDescription(pht('Task description.')) ->setConduitDescription(pht('Update the task description.')) ->setConduitTypeDescription(pht('New task description.')) - ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) + ->setTransactionType(ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE) ->setValue($object->getDescription()) ->setPreviewPanel( id(new PHUIRemarkupPreviewPanel()) diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php index 0a4ea8469a..c9e17cd4de 100644 --- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php +++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php @@ -18,18 +18,6 @@ final class ManiphestTransactionEditor $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_EDGE; - $types[] = ManiphestTransaction::TYPE_PRIORITY; - $types[] = ManiphestTransaction::TYPE_STATUS; - $types[] = ManiphestTransaction::TYPE_TITLE; - $types[] = ManiphestTransaction::TYPE_DESCRIPTION; - $types[] = ManiphestTransaction::TYPE_OWNER; - $types[] = ManiphestTransaction::TYPE_SUBPRIORITY; - $types[] = ManiphestTransaction::TYPE_MERGED_INTO; - $types[] = ManiphestTransaction::TYPE_MERGED_FROM; - $types[] = ManiphestTransaction::TYPE_UNBLOCK; - $types[] = ManiphestTransaction::TYPE_PARENT; - $types[] = ManiphestTransaction::TYPE_COVER_IMAGE; - $types[] = ManiphestTransaction::TYPE_POINTS; $types[] = PhabricatorTransactions::TYPE_COLUMNS; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -37,47 +25,19 @@ final class ManiphestTransactionEditor return $types; } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this task.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + protected function getCustomTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_PRIORITY: - if ($this->getIsNewObject()) { - return null; - } - return (int)$object->getPriority(); - case ManiphestTransaction::TYPE_STATUS: - if ($this->getIsNewObject()) { - return null; - } - return $object->getStatus(); - case ManiphestTransaction::TYPE_TITLE: - if ($this->getIsNewObject()) { - return null; - } - return $object->getTitle(); - case ManiphestTransaction::TYPE_DESCRIPTION: - if ($this->getIsNewObject()) { - return null; - } - return $object->getDescription(); - case ManiphestTransaction::TYPE_OWNER: - return nonempty($object->getOwnerPHID(), null); - case ManiphestTransaction::TYPE_SUBPRIORITY: - return $object->getSubpriority(); - case ManiphestTransaction::TYPE_COVER_IMAGE: - return $object->getCoverImageFilePHID(); - case ManiphestTransaction::TYPE_POINTS: - $points = $object->getPoints(); - if ($points !== null) { - $points = (double)$points; - } - return $points; - case ManiphestTransaction::TYPE_MERGED_INTO: - case ManiphestTransaction::TYPE_MERGED_FROM: - return null; - case ManiphestTransaction::TYPE_PARENT: case PhabricatorTransactions::TYPE_COLUMNS: return null; } @@ -88,31 +48,8 @@ final class ManiphestTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_PRIORITY: - return (int)$xaction->getNewValue(); - case ManiphestTransaction::TYPE_OWNER: - return nonempty($xaction->getNewValue(), null); - case ManiphestTransaction::TYPE_STATUS: - case ManiphestTransaction::TYPE_TITLE: - case ManiphestTransaction::TYPE_DESCRIPTION: - case ManiphestTransaction::TYPE_SUBPRIORITY: - case ManiphestTransaction::TYPE_MERGED_INTO: - case ManiphestTransaction::TYPE_MERGED_FROM: - case ManiphestTransaction::TYPE_UNBLOCK: - case ManiphestTransaction::TYPE_COVER_IMAGE: - return $xaction->getNewValue(); - case ManiphestTransaction::TYPE_PARENT: case PhabricatorTransactions::TYPE_COLUMNS: return $xaction->getNewValue(); - case ManiphestTransaction::TYPE_POINTS: - $value = $xaction->getNewValue(); - if (!strlen($value)) { - $value = null; - } - if ($value !== null) { - $value = (double)$value; - } - return $value; } } @@ -136,72 +73,6 @@ final class ManiphestTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_PRIORITY: - return $object->setPriority($xaction->getNewValue()); - case ManiphestTransaction::TYPE_STATUS: - return $object->setStatus($xaction->getNewValue()); - case ManiphestTransaction::TYPE_TITLE: - return $object->setTitle($xaction->getNewValue()); - case ManiphestTransaction::TYPE_DESCRIPTION: - return $object->setDescription($xaction->getNewValue()); - case ManiphestTransaction::TYPE_OWNER: - $phid = $xaction->getNewValue(); - - // Update the "ownerOrdering" column to contain the full name of the - // owner, if the task is assigned. - - $handle = null; - if ($phid) { - $handle = id(new PhabricatorHandleQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($phid)) - ->executeOne(); - } - - if ($handle) { - $object->setOwnerOrdering($handle->getName()); - } else { - $object->setOwnerOrdering(null); - } - - return $object->setOwnerPHID($phid); - case ManiphestTransaction::TYPE_SUBPRIORITY: - $object->setSubpriority($xaction->getNewValue()); - return; - case ManiphestTransaction::TYPE_MERGED_INTO: - $object->setStatus(ManiphestTaskStatus::getDuplicateStatus()); - return; - case ManiphestTransaction::TYPE_COVER_IMAGE: - $file_phid = $xaction->getNewValue(); - - if ($file_phid) { - $file = id(new PhabricatorFileQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($file_phid)) - ->executeOne(); - } else { - $file = null; - } - - if (!$file || !$file->isTransformableImage()) { - $object->setProperty('cover.filePHID', null); - $object->setProperty('cover.thumbnailPHID', null); - return; - } - - $xform_key = PhabricatorFileThumbnailTransform::TRANSFORM_WORKCARD; - - $xform = PhabricatorFileTransform::getTransformByKey($xform_key) - ->executeTransform($file); - - $object->setProperty('cover.filePHID', $file->getPHID()); - $object->setProperty('cover.thumbnailPHID', $xform->getPHID()); - return; - case ManiphestTransaction::TYPE_POINTS: - $object->setPoints($xaction->getNewValue()); - return; - case ManiphestTransaction::TYPE_MERGED_FROM: - case ManiphestTransaction::TYPE_PARENT: case PhabricatorTransactions::TYPE_COLUMNS: return; } @@ -212,22 +83,11 @@ final class ManiphestTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_PARENT: - $parent_phid = $xaction->getNewValue(); - $parent_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; - $task_phid = $object->getPHID(); - - id(new PhabricatorEdgeEditor()) - ->addEdge($parent_phid, $parent_type, $task_phid) - ->save(); - break; case PhabricatorTransactions::TYPE_COLUMNS: foreach ($xaction->getNewValue() as $move) { $this->applyBoardMove($object, $move); } break; - default: - break; } } @@ -240,7 +100,7 @@ final class ManiphestTransactionEditor $unblock_xaction = null; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_STATUS: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: $unblock_xaction = $xaction; break; } @@ -265,7 +125,8 @@ final class ManiphestTransactionEditor foreach ($blocked_tasks as $blocked_task) { $parent_xaction = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_UNBLOCK) + ->setTransactionType( + ManiphestTaskUnblockTransaction::TRANSACTIONTYPE) ->setOldValue(array($object->getPHID() => $old)) ->setNewValue(array($object->getPHID() => $new)); @@ -398,7 +259,7 @@ final class ManiphestTransactionEditor protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { - return $this->shouldSendMail($object, $xactions); + return true; } protected function supportsSearch() { @@ -426,11 +287,11 @@ final class ManiphestTransactionEditor parent::requireCapabilities($object, $xaction); $app_capability_map = array( - ManiphestTransaction::TYPE_PRIORITY => + ManiphestTaskPriorityTransaction::TRANSACTIONTYPE => ManiphestEditPriorityCapability::CAPABILITY, - ManiphestTransaction::TYPE_STATUS => + ManiphestTaskStatusTransaction::TRANSACTIONTYPE => ManiphestEditStatusCapability::CAPABILITY, - ManiphestTransaction::TYPE_OWNER => + ManiphestTaskOwnerTransaction::TRANSACTIONTYPE => ManiphestEditAssignCapability::CAPABILITY, PhabricatorTransactions::TYPE_EDIT_POLICY => ManiphestEditPoliciesCapability::CAPABILITY, @@ -471,7 +332,7 @@ final class ManiphestTransactionEditor $copy = parent::adjustObjectForPolicyChecks($object, $xactions); foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: $copy->setOwnerPHID($xaction->getNewValue()); break; default: @@ -629,157 +490,6 @@ final class ManiphestTransactionEditor return array($dst->getPriority(), $sub); } - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case ManiphestTransaction::TYPE_TITLE: - $missing = $this->validateIsEmptyTextField( - $object->getTitle(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Task title is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case ManiphestTransaction::TYPE_PARENT: - $with_effect = array(); - foreach ($xactions as $xaction) { - $task_phid = $xaction->getNewValue(); - if (!$task_phid) { - continue; - } - - $with_effect[] = $xaction; - - $task = id(new ManiphestTaskQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($task_phid)) - ->executeOne(); - if (!$task) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Parent task identifier "%s" does not identify a visible '. - 'task.', - $task_phid), - $xaction); - } - } - - if ($with_effect && !$this->getIsNewObject()) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'You can only select a parent task when creating a '. - 'transaction for the first time.'), - last($with_effect)); - } - break; - case ManiphestTransaction::TYPE_OWNER: - foreach ($xactions as $xaction) { - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - if (!strlen($new)) { - continue; - } - - if ($new === $old) { - continue; - } - - $assignee_list = id(new PhabricatorPeopleQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($new)) - ->execute(); - if (!$assignee_list) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'User "%s" is not a valid user.', - $new), - $xaction); - } - } - break; - case ManiphestTransaction::TYPE_COVER_IMAGE: - foreach ($xactions as $xaction) { - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - if (!$new) { - continue; - } - - if ($new === $old) { - continue; - } - - $file = id(new PhabricatorFileQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($new)) - ->executeOne(); - if (!$file) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('File "%s" is not valid.', $new), - $xaction); - continue; - } - - if (!$file->isTransformableImage()) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('File "%s" is not a valid image file.', $new), - $xaction); - continue; - } - } - break; - - case ManiphestTransaction::TYPE_POINTS: - foreach ($xactions as $xaction) { - $new = $xaction->getNewValue(); - if (strlen($new) && !is_numeric($new)) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('Points value must be numeric or empty.'), - $xaction); - continue; - } - - if ((double)$new < 0) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('Points value must be nonnegative.'), - $xaction); - continue; - } - } - break; - - } - - return $errors; - } - protected function validateAllTransactions( PhabricatorLiskDAO $object, array $xactions) { @@ -806,7 +516,8 @@ final class ManiphestTransactionEditor $any_assign = false; foreach ($xactions as $xaction) { - if ($xaction->getTransactionType() == ManiphestTransaction::TYPE_OWNER) { + if ($xaction->getTransactionType() == + ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) { $any_assign = true; break; } @@ -817,7 +528,7 @@ final class ManiphestTransactionEditor $new_status = null; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_STATUS: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: $new_status = $xaction->getNewValue(); break; } @@ -838,7 +549,7 @@ final class ManiphestTransactionEditor // Don't claim the task if the status is configured to not claim. if ($actor_phid && $is_claim) { $results[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) ->setNewValue($actor_phid); } } @@ -881,7 +592,7 @@ final class ManiphestTransactionEditor $this->moreValidationErrors[] = $error; } break; - case ManiphestTransaction::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: // If this is a no-op update, don't expand it. $old_value = $object->getOwnerPHID(); $new_value = $xaction->getNewValue(); @@ -906,20 +617,6 @@ final class ManiphestTransactionEditor return $results; } - protected function extractFilePHIDsFromCustomTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - $phids = parent::extractFilePHIDsFromCustomTransaction($object, $xaction); - - switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_COVER_IMAGE: - $phids[] = $xaction->getNewValue(); - break; - } - - return $phids; - } - private function buildMoveTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { diff --git a/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php index 00f539d4a7..fffd0bac7d 100644 --- a/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php +++ b/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php @@ -40,7 +40,7 @@ abstract class ManiphestTaskAssignHeraldAction } $xaction = $adapter->newTransaction() - ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) ->setNewValue($phid); $adapter->queueTransaction($xaction); diff --git a/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php index c055266a44..ff8420544d 100644 --- a/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php +++ b/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php @@ -40,7 +40,7 @@ final class ManiphestTaskPriorityHeraldAction } $xaction = $adapter->newTransaction() - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) ->setNewValue($priority); $adapter->queueTransaction($xaction); diff --git a/src/applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php index 26e212d9c7..c1ff3bcdc7 100644 --- a/src/applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php +++ b/src/applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php @@ -40,7 +40,7 @@ final class ManiphestTaskStatusHeraldAction } $xaction = $adapter->newTransaction() - ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE) ->setNewValue($status); $adapter->queueTransaction($xaction); diff --git a/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php b/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php index 404a36af8f..2d6a6807ce 100644 --- a/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php +++ b/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php @@ -22,15 +22,15 @@ final class PhabricatorManiphestTaskTestDataGenerator $template = new ManiphestTransaction(); // Accumulate Transactions $changes = array(); - $changes[ManiphestTransaction::TYPE_TITLE] = + $changes[ManiphestTaskTitleTransaction::TRANSACTIONTYPE] = $this->generateTitle(); - $changes[ManiphestTransaction::TYPE_DESCRIPTION] = + $changes[ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE] = $this->generateDescription(); - $changes[ManiphestTransaction::TYPE_OWNER] = + $changes[ManiphestTaskOwnerTransaction::TRANSACTIONTYPE] = $this->loadOwnerPHID(); - $changes[ManiphestTransaction::TYPE_STATUS] = + $changes[ManiphestTaskStatusTransaction::TRANSACTIONTYPE] = $this->generateTaskStatus(); - $changes[ManiphestTransaction::TYPE_PRIORITY] = + $changes[ManiphestTaskPriorityTransaction::TRANSACTIONTYPE] = $this->generateTaskPriority(); $changes[PhabricatorTransactions::TYPE_SUBSCRIBERS] = array('=' => $this->getCCPHIDs()); diff --git a/src/applications/maniphest/mail/ManiphestReplyHandler.php b/src/applications/maniphest/mail/ManiphestReplyHandler.php index bc9b2456d4..bbcfe86551 100644 --- a/src/applications/maniphest/mail/ManiphestReplyHandler.php +++ b/src/applications/maniphest/mail/ManiphestReplyHandler.php @@ -25,11 +25,12 @@ final class ManiphestReplyHandler if ($is_new) { $xactions[] = $this->newTransaction() - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setNewValue(nonempty($mail->getSubject(), pht('Untitled Task'))); $xactions[] = $this->newTransaction() - ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) + ->setTransactionType( + ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE) ->setNewValue($body); $actor_phid = $actor->getPHID(); diff --git a/src/applications/maniphest/relationship/ManiphestTaskRelationship.php b/src/applications/maniphest/relationship/ManiphestTaskRelationship.php index 928d49e87d..04f78b8523 100644 --- a/src/applications/maniphest/relationship/ManiphestTaskRelationship.php +++ b/src/applications/maniphest/relationship/ManiphestTaskRelationship.php @@ -19,7 +19,8 @@ abstract class ManiphestTaskRelationship protected function newMergeIntoTransactions(ManiphestTask $task) { return array( id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_MERGED_INTO) + ->setTransactionType( + ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE) ->setNewValue($task->getPHID()), ); } @@ -34,7 +35,8 @@ abstract class ManiphestTaskRelationship ->setNewValue(array('+' => $subscriber_phids)); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_MERGED_FROM) + ->setTransactionType( + ManiphestTaskMergedFromTransaction::TRANSACTIONTYPE) ->setNewValue(mpull($tasks, 'getPHID')); return $xactions; diff --git a/src/applications/maniphest/storage/ManiphestTransaction.php b/src/applications/maniphest/storage/ManiphestTransaction.php index b9b8560b34..0d27ddc5da 100644 --- a/src/applications/maniphest/storage/ManiphestTransaction.php +++ b/src/applications/maniphest/storage/ManiphestTransaction.php @@ -1,25 +1,7 @@ getTransactionType()) { - case self::TYPE_EDGE: - case self::TYPE_UNBLOCK: + case ManiphestTaskEdgeTransaction::TRANSACTIONTYPE: + case ManiphestTaskUnblockTransaction::TRANSACTIONTYPE: return false; } return parent::shouldGenerateOldValue(); } - protected function newRemarkupChanges() { - $changes = array(); - - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - $changes[] = $this->newRemarkupChange() - ->setOldValue($this->getOldValue()) - ->setNewValue($this->getNewValue()); - break; - } - - return $changes; - } - public function getRequiredHandlePHIDs() { $phids = parent::getRequiredHandlePHIDs(); @@ -75,7 +47,7 @@ final class ManiphestTransaction $old = $this->getOldValue(); switch ($this->getTransactionType()) { - case self::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: if ($new) { $phids[] = $new; } @@ -84,13 +56,13 @@ final class ManiphestTransaction $phids[] = $old; } break; - case self::TYPE_MERGED_INTO: + case ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE: $phids[] = $new; break; - case self::TYPE_MERGED_FROM: + case ManiphestTaskMergedFromTransaction::TRANSACTIONTYPE: $phids = array_merge($phids, $new); break; - case self::TYPE_EDGE: + case ManiphestTaskEdgeTransaction::TRANSACTIONTYPE: $phids = array_mergev( array( $phids, @@ -98,7 +70,7 @@ final class ManiphestTransaction array_keys(nonempty($new, array())), )); break; - case self::TYPE_ATTACH: + case ManiphestTaskAttachTransaction::TRANSACTIONTYPE: $old = nonempty($old, array()); $new = nonempty($new, array()); $phids = array_mergev( @@ -108,12 +80,12 @@ final class ManiphestTransaction array_keys(idx($old, 'FILE', array())), )); break; - case self::TYPE_UNBLOCK: + case ManiphestTaskUnblockTransaction::TRANSACTIONTYPE: foreach (array_keys($new) as $phid) { $phids[] = $phid; } break; - case self::TYPE_STATUS: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: $commit_phid = $this->getMetadataValue('commitPHID'); if ($commit_phid) { $phids[] = $commit_phid; @@ -124,275 +96,27 @@ final class ManiphestTransaction return $phids; } - public function shouldHide() { - switch ($this->getTransactionType()) { - case PhabricatorTransactions::TYPE_EDGE: - $commit_phid = $this->getMetadataValue('commitPHID'); - $edge_type = $this->getMetadataValue('edge:type'); - - if ($edge_type == ManiphestTaskHasCommitEdgeType::EDGECONST) { - if ($commit_phid) { - return true; - } - } - break; - case self::TYPE_DESCRIPTION: - case self::TYPE_PRIORITY: - case self::TYPE_STATUS: - if ($this->getOldValue() === null) { - return true; - } else { - return false; - } - break; - case self::TYPE_SUBPRIORITY: - case self::TYPE_PARENT: - return true; - case self::TYPE_COVER_IMAGE: - // At least for now, don't show these. - return true; - case self::TYPE_POINTS: - if (!ManiphestTaskPoints::getIsEnabled()) { - return true; - } - } - - return parent::shouldHide(); - } - - public function shouldHideForMail(array $xactions) { - switch ($this->getTransactionType()) { - case self::TYPE_POINTS: - return true; - } - - return parent::shouldHideForMail($xactions); - } - - public function shouldHideForFeed() { - switch ($this->getTransactionType()) { - case self::TYPE_UNBLOCK: - // Hide "alice created X, a task blocking Y." from feed because it - // will almost always appear adjacent to "alice created Y". - $is_new = $this->getMetadataValue('blocker.new'); - if ($is_new) { - return true; - } - break; - case self::TYPE_POINTS: - return true; - } - - return parent::shouldHideForFeed(); - } - - public function getActionStrength() { - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - return 1.4; - case self::TYPE_STATUS: - return 1.3; - case self::TYPE_OWNER: - return 1.2; - case self::TYPE_PRIORITY: - return 1.1; - } - - return parent::getActionStrength(); - } - - - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_OWNER: - if ($this->getAuthorPHID() == $new) { - return 'green'; - } else if (!$new) { - return 'black'; - } else if (!$old) { - return 'green'; - } else { - return 'green'; - } - - case self::TYPE_STATUS: - $color = ManiphestTaskStatus::getStatusColor($new); - if ($color !== null) { - return $color; - } - - if (ManiphestTaskStatus::isOpenStatus($new)) { - return 'green'; - } else { - return 'indigo'; - } - - case self::TYPE_PRIORITY: - if ($old == ManiphestTaskPriority::getDefaultPriority()) { - return 'green'; - } else if ($old > $new) { - return 'grey'; - } else { - return 'yellow'; - } - - case self::TYPE_MERGED_FROM: - return 'orange'; - - case self::TYPE_MERGED_INTO: - return 'indigo'; - } - - return parent::getColor(); - } - public function getActionName() { $old = $this->getOldValue(); $new = $this->getNewValue(); - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return pht('Created'); - } - - return pht('Retitled'); - - case self::TYPE_STATUS: - $action = ManiphestTaskStatus::getStatusActionName($new); - if ($action) { - return $action; - } - - $old_closed = ManiphestTaskStatus::isClosedStatus($old); - $new_closed = ManiphestTaskStatus::isClosedStatus($new); - - if ($new_closed && !$old_closed) { - return pht('Closed'); - } else if (!$new_closed && $old_closed) { - return pht('Reopened'); - } else { - return pht('Changed Status'); - } - - case self::TYPE_DESCRIPTION: - return pht('Edited'); - - case self::TYPE_OWNER: - if ($this->getAuthorPHID() == $new) { - return pht('Claimed'); - } else if (!$new) { - return pht('Unassigned'); - } else if (!$old) { - return pht('Assigned'); - } else { - return pht('Reassigned'); - } - case PhabricatorTransactions::TYPE_COLUMNS: return pht('Changed Project Column'); - - case self::TYPE_PRIORITY: - if ($old == ManiphestTaskPriority::getDefaultPriority()) { - return pht('Triaged'); - } else if ($old > $new) { - return pht('Lowered Priority'); - } else { - return pht('Raised Priority'); - } - - case self::TYPE_EDGE: - case self::TYPE_ATTACH: - return pht('Attached'); - - case self::TYPE_UNBLOCK: - $old_status = head($old); - $new_status = head($new); - - $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); - $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); - - if ($old_closed && !$new_closed) { - return pht('Block'); - } else if (!$old_closed && $new_closed) { - return pht('Unblock'); - } else { - return pht('Blocker'); - } - - case self::TYPE_MERGED_INTO: - case self::TYPE_MERGED_FROM: - return pht('Merged'); - } return parent::getActionName(); } public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - switch ($this->getTransactionType()) { - case self::TYPE_OWNER: - return 'fa-user'; - - case self::TYPE_TITLE: - if ($old === null) { - return 'fa-pencil'; - } - - return 'fa-pencil'; - - case self::TYPE_STATUS: - $action = ManiphestTaskStatus::getStatusIcon($new); - if ($action !== null) { - return $action; - } - - if (ManiphestTaskStatus::isClosedStatus($new)) { - return 'fa-check'; - } else { - return 'fa-pencil'; - } - - case self::TYPE_DESCRIPTION: - return 'fa-pencil'; - case PhabricatorTransactions::TYPE_COLUMNS: return 'fa-columns'; - - case self::TYPE_MERGED_INTO: - return 'fa-check'; - case self::TYPE_MERGED_FROM: - return 'fa-compress'; - - case self::TYPE_PRIORITY: - if ($old == ManiphestTaskPriority::getDefaultPriority()) { - return 'fa-arrow-right'; - } else if ($old > $new) { - return 'fa-arrow-down'; - } else { - return 'fa-arrow-up'; - } - - case self::TYPE_EDGE: - case self::TYPE_ATTACH: - return 'fa-thumb-tack'; - - case self::TYPE_UNBLOCK: - return 'fa-shield'; - } return parent::getIcon(); } - public function getTitle() { $author_phid = $this->getAuthorPHID(); @@ -400,244 +124,13 @@ final class ManiphestTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorTransactions::TYPE_CREATE: - return pht( - '%s created this task.', - $this->renderHandleLink($author_phid)); case PhabricatorTransactions::TYPE_SUBTYPE: return pht( '%s changed the subtype of this task from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderSubtypeName($old), $this->renderSubtypeName($new)); - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s created this task.', - $this->renderHandleLink($author_phid)); - } - return pht( - '%s changed the title from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - - case self::TYPE_DESCRIPTION: - return pht( - '%s edited the task description.', - $this->renderHandleLink($author_phid)); - - case self::TYPE_STATUS: - $old_closed = ManiphestTaskStatus::isClosedStatus($old); - $new_closed = ManiphestTaskStatus::isClosedStatus($new); - - $old_name = ManiphestTaskStatus::getTaskStatusName($old); - $new_name = ManiphestTaskStatus::getTaskStatusName($new); - - $commit_phid = $this->getMetadataValue('commitPHID'); - - if ($new_closed && !$old_closed) { - if ($new == ManiphestTaskStatus::getDuplicateStatus()) { - if ($commit_phid) { - return pht( - '%s closed this task as a duplicate by committing %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s closed this task as a duplicate.', - $this->renderHandleLink($author_phid)); - } - } else { - if ($commit_phid) { - return pht( - '%s closed this task as "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s closed this task as "%s".', - $this->renderHandleLink($author_phid), - $new_name); - } - } - } else if (!$new_closed && $old_closed) { - if ($commit_phid) { - return pht( - '%s reopened this task as "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s reopened this task as "%s".', - $this->renderHandleLink($author_phid), - $new_name); - } - } else { - if ($commit_phid) { - return pht( - '%s changed the task status from "%s" to "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $old_name, - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s changed the task status from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old_name, - $new_name); - } - } - - case self::TYPE_UNBLOCK: - $blocker_phid = key($new); - $old_status = head($old); - $new_status = head($new); - - $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); - $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); - - $old_name = ManiphestTaskStatus::getTaskStatusName($old_status); - $new_name = ManiphestTaskStatus::getTaskStatusName($new_status); - - if ($this->getMetadataValue('blocker.new')) { - return pht( - '%s created subtask %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid)); - } else if ($old_closed && !$new_closed) { - return pht( - '%s reopened subtask %s as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $new_name); - } else if (!$old_closed && $new_closed) { - return pht( - '%s closed subtask %s as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $new_name); - } else { - return pht( - '%s changed the status of subtask %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $old_name, - $new_name); - } - - case self::TYPE_OWNER: - if ($author_phid == $new) { - return pht( - '%s claimed this task.', - $this->renderHandleLink($author_phid)); - } else if (!$new) { - return pht( - '%s removed %s as the assignee of this task.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old)); - } else if (!$old) { - return pht( - '%s assigned this task to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); - } else { - return pht( - '%s reassigned this task from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - - case self::TYPE_PRIORITY: - $old_name = ManiphestTaskPriority::getTaskPriorityName($old); - $new_name = ManiphestTaskPriority::getTaskPriorityName($new); - - if ($old == ManiphestTaskPriority::getDefaultPriority()) { - return pht( - '%s triaged this task as "%s" priority.', - $this->renderHandleLink($author_phid), - $new_name); - } else if ($old > $new) { - return pht( - '%s lowered the priority of this task from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old_name, - $new_name); - } else { - return pht( - '%s raised the priority of this task from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old_name, - $new_name); - } - - case self::TYPE_ATTACH: - $old = nonempty($old, array()); - $new = nonempty($new, array()); - $new = array_keys(idx($new, 'FILE', array())); - $old = array_keys(idx($old, 'FILE', array())); - - $added = array_diff($new, $old); - $removed = array_diff($old, $new); - if ($added && !$removed) { - return pht( - '%s attached %s file(s): %s.', - $this->renderHandleLink($author_phid), - phutil_count($added), - $this->renderHandleList($added)); - } else if ($removed && !$added) { - return pht( - '%s detached %s file(s): %s.', - $this->renderHandleLink($author_phid), - phutil_count($removed), - $this->renderHandleList($removed)); - } else { - return pht( - '%s changed file(s), attached %s: %s; detached %s: %s.', - $this->renderHandleLink($author_phid), - phutil_count($added), - $this->renderHandleList($added), - phutil_count($removed), - $this->renderHandleList($removed)); - } - - case self::TYPE_MERGED_INTO: - return pht( - '%s closed this task as a duplicate of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); break; - - case self::TYPE_MERGED_FROM: - return pht( - '%s merged %s task(s): %s.', - $this->renderHandleLink($author_phid), - phutil_count($new), - $this->renderHandleList($new)); - break; - - case self::TYPE_POINTS: - if ($old === null) { - return pht( - '%s set the point value for this task to %s.', - $this->renderHandleLink($author_phid), - $new); - } else if ($new === null) { - return pht( - '%s removed the point value for this task.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s changed the point value for this task from %s to %s.', - $this->renderHandleLink($author_phid), - $old, - $new); - } - } return parent::getTitle(); @@ -651,236 +144,6 @@ final class ManiphestTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - - return pht( - '%s renamed %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old, - $new); - - case self::TYPE_DESCRIPTION: - return pht( - '%s edited the description of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - case self::TYPE_STATUS: - $old_closed = ManiphestTaskStatus::isClosedStatus($old); - $new_closed = ManiphestTaskStatus::isClosedStatus($new); - - $old_name = ManiphestTaskStatus::getTaskStatusName($old); - $new_name = ManiphestTaskStatus::getTaskStatusName($new); - - $commit_phid = $this->getMetadataValue('commitPHID'); - - if ($new_closed && !$old_closed) { - if ($new == ManiphestTaskStatus::getDuplicateStatus()) { - if ($commit_phid) { - return pht( - '%s closed %s as a duplicate by committing %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s closed %s as a duplicate.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - } else { - if ($commit_phid) { - return pht( - '%s closed %s as "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s closed %s as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new_name); - } - } - } else if (!$new_closed && $old_closed) { - if ($commit_phid) { - return pht( - '%s reopened %s as "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s reopened %s as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new_name); - } - } else { - if ($commit_phid) { - return pht( - '%s changed the status of %s from "%s" to "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old_name, - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s changed the status of %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old_name, - $new_name); - } - } - - case self::TYPE_UNBLOCK: - $blocker_phid = key($new); - $old_status = head($old); - $new_status = head($new); - - $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); - $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); - - $old_name = ManiphestTaskStatus::getTaskStatusName($old_status); - $new_name = ManiphestTaskStatus::getTaskStatusName($new_status); - - if ($old_closed && !$new_closed) { - return pht( - '%s reopened %s, a subtask of %s, as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $this->renderHandleLink($object_phid), - $new_name); - } else if (!$old_closed && $new_closed) { - return pht( - '%s closed %s, a subtask of %s, as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $this->renderHandleLink($object_phid), - $new_name); - } else { - return pht( - '%s changed the status of %s, a subtask of %s, '. - 'from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $this->renderHandleLink($object_phid), - $old_name, - $new_name); - } - - case self::TYPE_OWNER: - if ($author_phid == $new) { - return pht( - '%s claimed %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else if (!$new) { - return pht( - '%s placed %s up for grabs.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else if (!$old) { - return pht( - '%s assigned %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($new)); - } else { - return pht( - '%s reassigned %s from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - - case self::TYPE_PRIORITY: - $old_name = ManiphestTaskPriority::getTaskPriorityName($old); - $new_name = ManiphestTaskPriority::getTaskPriorityName($new); - - if ($old == ManiphestTaskPriority::getDefaultPriority()) { - return pht( - '%s triaged %s as "%s" priority.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new_name); - } else if ($old > $new) { - return pht( - '%s lowered the priority of %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old_name, - $new_name); - } else { - return pht( - '%s raised the priority of %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old_name, - $new_name); - } - - case self::TYPE_ATTACH: - $old = nonempty($old, array()); - $new = nonempty($new, array()); - $new = array_keys(idx($new, 'FILE', array())); - $old = array_keys(idx($old, 'FILE', array())); - - $added = array_diff($new, $old); - $removed = array_diff($old, $new); - if ($added && !$removed) { - return pht( - '%s attached %d file(s) of %s: %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - count($added), - $this->renderHandleList($added)); - } else if ($removed && !$added) { - return pht( - '%s detached %d file(s) of %s: %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - count($removed), - $this->renderHandleList($removed)); - } else { - return pht( - '%s changed file(s) for %s, attached %d: %s; detached %d: %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - count($added), - $this->renderHandleList($added), - count($removed), - $this->renderHandleList($removed)); - } - - case self::TYPE_MERGED_INTO: - return pht( - '%s merged task %s into %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($new)); - - case self::TYPE_MERGED_FROM: - return pht( - '%s merged %s task(s) %s into %s.', - $this->renderHandleLink($author_phid), - phutil_count($new), - $this->renderHandleList($new), - $this->renderHandleLink($object_phid)); - case PhabricatorTransactions::TYPE_SUBTYPE: return pht( '%s changed the subtype of %s from "%s" to "%s".', @@ -893,39 +156,14 @@ final class ManiphestTransaction return parent::getTitleForFeed(); } - private function renderSubtypeName($value) { - $object = $this->getObject(); - $map = $object->newEditEngineSubtypeMap(); - if (!isset($map[$value])) { - return $value; - } - - return $map[$value]->getName(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return true; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); - } - public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { - case self::TYPE_MERGED_INTO: - case self::TYPE_STATUS: + case ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_STATUS; break; - case self::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_OWNER; break; case PhabricatorTransactions::TYPE_SUBSCRIBERS: @@ -941,10 +179,10 @@ final class ManiphestTransaction break; } break; - case self::TYPE_PRIORITY: + case ManiphestTaskPriorityTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_PRIORITY; break; - case self::TYPE_UNBLOCK: + case ManiphestTaskUnblockTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_UNBLOCK; break; case PhabricatorTransactions::TYPE_COLUMNS: @@ -961,13 +199,12 @@ final class ManiphestTransaction } public function getNoEffectDescription() { - switch ($this->getTransactionType()) { - case self::TYPE_STATUS: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: return pht('The task already has the selected status.'); - case self::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: return pht('The task already has the selected owner.'); - case self::TYPE_PRIORITY: + case ManiphestTaskPriorityTransaction::TRANSACTIONTYPE: return pht('The task already has the selected priority.'); } diff --git a/src/applications/maniphest/xaction/ManiphestTaskAttachTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskAttachTransaction.php new file mode 100644 index 0000000000..e794c03bdc --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskAttachTransaction.php @@ -0,0 +1,91 @@ +getOldValue(); + $new = $this->getNewValue(); + + $old = nonempty($old, array()); + $new = nonempty($new, array()); + $new = array_keys(idx($new, 'FILE', array())); + $old = array_keys(idx($old, 'FILE', array())); + + $added = array_diff($new, $old); + $removed = array_diff($old, $new); + if ($added && !$removed) { + return pht( + '%s attached %s file(s): %s.', + $this->renderAuthor(), + phutil_count($added), + $this->renderHandleList($added)); + } else if ($removed && !$added) { + return pht( + '%s detached %s file(s): %s.', + $this->renderAuthor(), + phutil_count($removed), + $this->renderHandleList($removed)); + } else { + return pht( + '%s changed file(s), attached %s: %s; detached %s: %s.', + $this->renderAuthor(), + phutil_count($added), + $this->renderHandleList($added), + phutil_count($removed), + $this->renderHandleList($removed)); + } + + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old = nonempty($old, array()); + $new = nonempty($new, array()); + $new = array_keys(idx($new, 'FILE', array())); + $old = array_keys(idx($old, 'FILE', array())); + + $added = array_diff($new, $old); + $removed = array_diff($old, $new); + if ($added && !$removed) { + return pht( + '%s attached %d file(s) of %s: %s', + $this->renderAuthor(), + $this->renderObject(), + count($added), + $this->renderHandleList($added)); + } else if ($removed && !$added) { + return pht( + '%s detached %d file(s) of %s: %s', + $this->renderAuthor(), + $this->renderObject(), + count($removed), + $this->renderHandleList($removed)); + } else { + return pht( + '%s changed file(s) for %s, attached %d: %s; detached %d: %s', + $this->renderAuthor(), + $this->renderObject(), + count($added), + $this->renderHandleList($added), + count($removed), + $this->renderHandleList($removed)); + } + } + + public function getIcon() { + return 'fa-thumb-tack'; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php new file mode 100644 index 0000000000..eb29c711f8 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php @@ -0,0 +1,106 @@ +getCoverImageFilePHID(); + } + + public function applyInternalEffects($object, $value) { + $file_phid = $value; + + if ($file_phid) { + $file = id(new PhabricatorFileQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($file_phid)) + ->executeOne(); + } else { + $file = null; + } + + if (!$file || !$file->isTransformableImage()) { + $object->setProperty('cover.filePHID', null); + $object->setProperty('cover.thumbnailPHID', null); + return; + } + + $xform_key = PhabricatorFileThumbnailTransform::TRANSFORM_WORKCARD; + $xform = PhabricatorFileTransform::getTransformByKey($xform_key) + ->executeTransform($file); + + $object->setProperty('cover.filePHID', $file->getPHID()); + $object->setProperty('cover.thumbnailPHID', $xform->getPHID()); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s set the cover image to %s.', + $this->renderAuthor(), + $this->renderHandle($new)); + } + + return pht( + '%s updated the cover image to %s.', + $this->renderAuthor(), + $this->renderHandle($new)); + + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s added a cover image to %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + return pht( + '%s updated the cover image for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + $viewer = $this->getActor(); + + foreach ($xactions as $xaction) { + $file_phid = $xaction->getNewValue(); + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + + if (!$file) { + $errors[] = $this->newInvalidError( + pht('"%s" is not a valid file PHID.', + $file_phid)); + } else { + if (!$file->isViewableImage()) { + $mime_type = $file->getMimeType(); + $errors[] = $this->newInvalidError( + pht('File mime type of "%s" is not a valid viewable image.', + $mime_type)); + } + } + + } + + return $errors; + } + + public function getIcon() { + return 'fa-image'; + } + + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php new file mode 100644 index 0000000000..009327ed9b --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php @@ -0,0 +1,61 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getActionName() { + return pht('Edited'); + } + + public function getTitle() { + return pht( + '%s updated the task description.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the task description for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO TASK DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php new file mode 100644 index 0000000000..18f9a1da1f --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php @@ -0,0 +1,31 @@ +getMetadataValue('commitPHID'); + $edge_type = $this->getMetadataValue('edge:type'); + + if ($edge_type == ManiphestTaskHasCommitEdgeType::EDGECONST) { + if ($commit_phid) { + return true; + } + } + } + + public function getActionName() { + return pht('Attached'); + } + + public function getIcon() { + return 'fa-thumb-tack'; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php new file mode 100644 index 0000000000..a4a0440604 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php @@ -0,0 +1,45 @@ +getNewValue(); + + return pht( + '%s merged %s task(s): %s.', + $this->renderAuthor(), + phutil_count($new), + $this->renderHandleList($new)); + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + + return pht( + '%s merged %s task(s) %s into %s.', + $this->renderAuthor(), + phutil_count($new), + $this->renderHandleList($new), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-compress'; + } + + public function getColor() { + return 'orange'; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php new file mode 100644 index 0000000000..cd0cad6a39 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php @@ -0,0 +1,47 @@ +setStatus(ManiphestTaskStatus::getDuplicateStatus()); + } + + public function getActionName() { + return pht('Merged'); + } + + public function getTitle() { + $new = $this->getNewValue(); + + return pht( + '%s closed this task as a duplicate of %s.', + $this->renderAuthor(), + $this->renderHandle($new)); + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + + return pht( + '%s merged task %s into %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($new)); + } + + public function getIcon() { + return 'fa-check'; + } + + public function getColor() { + return 'indigo'; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php new file mode 100644 index 0000000000..d510fe8fbc --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php @@ -0,0 +1,158 @@ +getOwnerPHID(), null); + } + + public function applyInternalEffects($object, $value) { + // Update the "ownerOrdering" column to contain the full name of the + // owner, if the task is assigned. + + $handle = null; + if ($value) { + $handle = id(new PhabricatorHandleQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($value)) + ->executeOne(); + } + + if ($handle) { + $object->setOwnerOrdering($handle->getName()); + } else { + $object->setOwnerOrdering(null); + } + + $object->setOwnerPHID($value); + } + + public function getActionStrength() { + return 1.2; + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($this->getAuthorPHID() == $new) { + return pht('Claimed'); + } else if (!$new) { + return pht('Unassigned'); + } else if (!$old) { + return pht('Assigned'); + } else { + return pht('Reassigned'); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($this->getAuthorPHID() == $new) { + return pht( + '%s claimed this task.', + $this->renderAuthor()); + } else if (!$new) { + return pht( + '%s removed %s as the assignee of this task.', + $this->renderAuthor(), + $this->renderHandle($old)); + } else if (!$old) { + return pht( + '%s assigned this task to %s.', + $this->renderAuthor(), + $this->renderHandle($new)); + } else { + return pht( + '%s reassigned this task from %s to %s.', + $this->renderAuthor(), + $this->renderHandle($old), + $this->renderHandle($new)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($this->getAuthorPHID() == $new) { + return pht( + '%s claimed %s.', + $this->renderAuthor(), + $this->renderObject()); + } else if (!$new) { + return pht( + '%s placed %s up for grabs.', + $this->renderAuthor(), + $this->renderObject()); + } else if (!$old) { + return pht( + '%s assigned %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($new)); + } else { + return pht( + '%s reassigned %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($old), + $this->renderHandle($new)); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + if (!strlen($new)) { + continue; + } + + if ($new === $old) { + continue; + } + + $assignee_list = id(new PhabricatorPeopleQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($new)) + ->execute(); + + if (!$assignee_list) { + $errors[] = $this->newInvalidError( + pht('User "%s" is not a valid user.', + $new)); + } + } + return $errors; + } + + public function getIcon() { + return 'fa-user'; + } + + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($this->getAuthorPHID() == $new) { + return 'green'; + } else if (!$new) { + return 'black'; + } else if (!$old) { + return 'green'; + } else { + return 'green'; + } + + } + + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskParentTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskParentTransaction.php new file mode 100644 index 0000000000..d703adc8b5 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskParentTransaction.php @@ -0,0 +1,60 @@ +getPHID(); + + id(new PhabricatorEdgeEditor()) + ->addEdge($parent_phid, $parent_type, $task_phid) + ->save(); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $with_effect = array(); + foreach ($xactions as $xaction) { + $task_phid = $xaction->getNewValue(); + if (!$task_phid) { + continue; + } + + $with_effect[] = $xaction; + + $task = id(new ManiphestTaskQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($task_phid)) + ->executeOne(); + if (!$task) { + $errors[] = $this->newInvalidError( + pht( + 'Parent task identifier "%s" does not identify a visible '. + 'task.', + $task_phid)); + } + } + + if ($with_effect && !$this->isNewObject()) { + $errors[] = $this->newInvalidError( + pht( + 'You can only select a parent task when creating a '. + 'transaction for the first time.')); + } + + return $errors; + } +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php new file mode 100644 index 0000000000..6d8548fdb4 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php @@ -0,0 +1,88 @@ +getValueForPoints($object->getPoints()); + } + + public function generateNewValue($object, $value) { + return $this->getValueForPoints($value); + } + + public function applyInternalEffects($object, $value) { + $object->setPoints($value); + } + + public function shouldHideForFeed() { + return true; + } + + public function shouldHide() { + if (!ManiphestTaskPoints::getIsEnabled()) { + return true; + } + return false; + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s set the point value for this task to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else if ($new === null) { + return pht( + '%s removed the point value for this task.', + $this->renderAuthor()); + } else { + return pht( + '%s changed the point value for this task from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + if (strlen($new) && !is_numeric($new)) { + $errors[] = $this->newInvalidError( + pht('Points value must be numeric or empty.')); + continue; + } + + if ((double)$new < 0) { + $errors[] = $this->newInvalidError( + pht('Points value must be nonnegative.')); + continue; + } + } + + return $errors; + } + + public function getIcon() { + return 'fa-calculator'; + } + + private function getValueForPoints($value) { + if (!strlen($value)) { + $value = null; + } + if ($value !== null) { + $value = (double)$value; + } + return $value; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php new file mode 100644 index 0000000000..f7d58911ba --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php @@ -0,0 +1,119 @@ +isNewObject()) { + return null; + } + return $object->getPriority(); + } + + public function applyInternalEffects($object, $value) { + $object->setPriority($value); + } + + public function getActionStrength() { + return 1.1; + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old == ManiphestTaskPriority::getDefaultPriority()) { + return pht('Triaged'); + } else if ($old > $new) { + return pht('Lowered Priority'); + } else { + return pht('Raised Priority'); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old_name = ManiphestTaskPriority::getTaskPriorityName($old); + $new_name = ManiphestTaskPriority::getTaskPriorityName($new); + + if ($old == ManiphestTaskPriority::getDefaultPriority()) { + return pht( + '%s triaged this task as %s priority.', + $this->renderAuthor(), + $this->renderValue($new_name)); + } else if ($old > $new) { + return pht( + '%s lowered the priority of this task from %s to %s.', + $this->renderAuthor(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } else { + return pht( + '%s raised the priority of this task from %s to %s.', + $this->renderAuthor(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old_name = ManiphestTaskPriority::getTaskPriorityName($old); + $new_name = ManiphestTaskPriority::getTaskPriorityName($new); + + if ($old == ManiphestTaskPriority::getDefaultPriority()) { + return pht( + '%s triaged %s as %s priority.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($new_name)); + } else if ($old > $new) { + return pht( + '%s lowered the priority of %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } else { + return pht( + '%s raised the priority of %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + + public function getIcon() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old == ManiphestTaskPriority::getDefaultPriority()) { + return 'fa-arrow-right'; + } else if ($old > $new) { + return 'fa-arrow-down'; + } else { + return 'fa-arrow-up'; + } + } + + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old == ManiphestTaskPriority::getDefaultPriority()) { + return 'green'; + } else if ($old > $new) { + return 'grey'; + } else { + return 'yellow'; + } + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php new file mode 100644 index 0000000000..5e1cd44611 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php @@ -0,0 +1,232 @@ +isNewObject()) { + return null; + } + return $object->getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function shouldHide() { + if ($this->getOldValue() === null) { + return true; + } else { + return false; + } + } + + public function getActionStrength() { + return 1.3; + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $action = ManiphestTaskStatus::getStatusActionName($new); + if ($action) { + return $action; + } + + $old_closed = ManiphestTaskStatus::isClosedStatus($old); + $new_closed = ManiphestTaskStatus::isClosedStatus($new); + + if ($new_closed && !$old_closed) { + return pht('Closed'); + } else if (!$new_closed && $old_closed) { + return pht('Reopened'); + } else { + return pht('Changed Status'); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old_closed = ManiphestTaskStatus::isClosedStatus($old); + $new_closed = ManiphestTaskStatus::isClosedStatus($new); + + $old_name = ManiphestTaskStatus::getTaskStatusName($old); + $new_name = ManiphestTaskStatus::getTaskStatusName($new); + + $commit_phid = $this->getMetadataValue('commitPHID'); + + if ($new_closed && !$old_closed) { + if ($new == ManiphestTaskStatus::getDuplicateStatus()) { + if ($commit_phid) { + return pht( + '%s closed this task as a duplicate by committing %s.', + $this->renderAuthor(), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s closed this task as a duplicate.', + $this->renderAuthor()); + } + } else { + if ($commit_phid) { + return pht( + '%s closed this task as %s by committing %s.', + $this->renderAuthor(), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s closed this task as %s.', + $this->renderAuthor(), + $this->renderValue($new_name)); + } + } + } else if (!$new_closed && $old_closed) { + if ($commit_phid) { + return pht( + '%s reopened this task as %s by committing %s.', + $this->renderAuthor(), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s reopened this task as %s.', + $this->renderAuthor(), + $this->renderValue($new_name)); + } + } else { + if ($commit_phid) { + return pht( + '%s changed the task status from %s to %s by committing %s.', + $this->renderAuthor(), + $this->renderValue($old_name), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s changed the task status from %s to %s.', + $this->renderAuthor(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old_closed = ManiphestTaskStatus::isClosedStatus($old); + $new_closed = ManiphestTaskStatus::isClosedStatus($new); + + $old_name = ManiphestTaskStatus::getTaskStatusName($old); + $new_name = ManiphestTaskStatus::getTaskStatusName($new); + + $commit_phid = $this->getMetadataValue('commitPHID'); + + if ($new_closed && !$old_closed) { + if ($new == ManiphestTaskStatus::getDuplicateStatus()) { + if ($commit_phid) { + return pht( + '%s closed %s as a duplicate by committing %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s closed %s as a duplicate.', + $this->renderAuthor(), + $this->renderObject()); + } + } else { + if ($commit_phid) { + return pht( + '%s closed %s as %s by committing %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s closed %s as %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($new_name)); + } + } + } else if (!$new_closed && $old_closed) { + if ($commit_phid) { + return pht( + '%s reopened %s as %s by committing %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s reopened %s as "%s".', + $this->renderAuthor(), + $this->renderObject(), + $new_name); + } + } else { + if ($commit_phid) { + return pht( + '%s changed the status of %s from %s to %s by committing %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($old_name), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s changed the status of %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + } + + public function getIcon() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $action = ManiphestTaskStatus::getStatusIcon($new); + if ($action !== null) { + return $action; + } + + if (ManiphestTaskStatus::isClosedStatus($new)) { + return 'fa-check'; + } else { + return 'fa-pencil'; + } + } + + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $color = ManiphestTaskStatus::getStatusColor($new); + if ($color !== null) { + return $color; + } + + if (ManiphestTaskStatus::isOpenStatus($new)) { + return 'green'; + } else { + return 'indigo'; + } + + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php new file mode 100644 index 0000000000..49d227b7f1 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php @@ -0,0 +1,21 @@ +getSubpriority(); + } + + public function applyInternalEffects($object, $value) { + $object->setSubpriority($value); + } + + public function shouldHide() { + return true; + } + + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php new file mode 100644 index 0000000000..5fade77d07 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php @@ -0,0 +1,74 @@ +getTitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setTitle($value); + } + + public function getActionStrength() { + return 1.4; + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + if ($old === null) { + return pht('Created'); + } + + return pht('Retitled'); + } + + public function getTitle() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s created this task.', + $this->renderAuthor()); + } + + return pht( + '%s renamed this task from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getTitle(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Tasks must have a title.')); + } + + return $errors; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskTransactionType.php b/src/applications/maniphest/xaction/ManiphestTaskTransactionType.php new file mode 100644 index 0000000000..699ef11631 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskTransactionType.php @@ -0,0 +1,16 @@ +getObject(); + $map = $object->newEditEngineSubtypeMap(); + if (!isset($map[$value])) { + return $value; + } + + return $map[$value]->getName(); + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php new file mode 100644 index 0000000000..905554a3a3 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php @@ -0,0 +1,125 @@ +getMetadataValue('blocker.new'); + if ($is_new) { + return true; + } + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old_status = head($old); + $new_status = head($new); + + $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); + $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); + + if ($old_closed && !$new_closed) { + return pht('Block'); + } else if (!$old_closed && $new_closed) { + return pht('Unblock'); + } else { + return pht('Blocker'); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $blocker_phid = key($new); + $old_status = head($old); + $new_status = head($new); + + $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); + $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); + + $old_name = ManiphestTaskStatus::getTaskStatusName($old_status); + $new_name = ManiphestTaskStatus::getTaskStatusName($new_status); + + if ($this->getMetadataValue('blocker.new')) { + return pht( + '%s created subtask %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid)); + } else if ($old_closed && !$new_closed) { + return pht( + '%s reopened subtask %s as %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderValue($new_name)); + } else if (!$old_closed && $new_closed) { + return pht( + '%s closed subtask %s as %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderValue($new_name)); + } else { + return pht( + '%s changed the status of subtask %s from %s to %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + $blocker_phid = key($new); + $old_status = head($old); + $new_status = head($new); + + $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); + $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); + + $old_name = ManiphestTaskStatus::getTaskStatusName($old_status); + $new_name = ManiphestTaskStatus::getTaskStatusName($new_status); + + if ($old_closed && !$new_closed) { + return pht( + '%s reopened %s, a subtask of %s, as %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderObject(), + $this->renderValue($new_name)); + } else if (!$old_closed && $new_closed) { + return pht( + '%s closed %s, a subtask of %s, as %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderObject(), + $this->renderValue($new_name)); + } else { + return pht( + '%s changed the status of %s, a subtask of %s, '. + 'from %s to %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderObject(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + + public function getIcon() { + return 'fa-shield'; + } + + +} diff --git a/src/applications/nuance/item/NuanceGitHubEventItemType.php b/src/applications/nuance/item/NuanceGitHubEventItemType.php index 3fef8b3da7..b7b90690b7 100644 --- a/src/applications/nuance/item/NuanceGitHubEventItemType.php +++ b/src/applications/nuance/item/NuanceGitHubEventItemType.php @@ -390,12 +390,14 @@ final class NuanceGitHubEventItemType $state = $xobj->getProperty('task.state'); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType( + ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title) ->setDateCreated($created); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) + ->setTransactionType( + ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE) ->setNewValue($description) ->setDateCreated($created); diff --git a/src/applications/people/query/PhabricatorPeopleSearchEngine.php b/src/applications/people/query/PhabricatorPeopleSearchEngine.php index e800a8ee1a..fa363c4428 100644 --- a/src/applications/people/query/PhabricatorPeopleSearchEngine.php +++ b/src/applications/people/query/PhabricatorPeopleSearchEngine.php @@ -265,6 +265,7 @@ final class PhabricatorPeopleSearchEngine if ($user->getIsDisabled()) { $item->addIcon('fa-ban', pht('Disabled')); + $item->setDisabled(true); } if (!$is_approval) { diff --git a/src/applications/pholio/controller/PholioMockCommentController.php b/src/applications/pholio/controller/PholioMockCommentController.php index b127d0b1da..1888c7369e 100644 --- a/src/applications/pholio/controller/PholioMockCommentController.php +++ b/src/applications/pholio/controller/PholioMockCommentController.php @@ -45,7 +45,7 @@ final class PholioMockCommentController extends PholioController { foreach ($inline_comments as $inline_comment) { $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PholioTransaction::TYPE_INLINE) + ->setTransactionType(PholioMockInlineTransaction::TRANSACTIONTYPE) ->attachComment($inline_comment); } diff --git a/src/applications/pholio/controller/PholioMockEditController.php b/src/applications/pholio/controller/PholioMockEditController.php index e97af5da27..2d477c70aa 100644 --- a/src/applications/pholio/controller/PholioMockEditController.php +++ b/src/applications/pholio/controller/PholioMockEditController.php @@ -151,7 +151,7 @@ final class PholioMockEditController extends PholioController { ->setSequence($sequence); $xactions[] = id(new PholioTransaction()) ->setTransactionType( - PholioTransaction::TYPE_IMAGE_REPLACE) + PholioImageReplaceTransaction::TRANSACTIONTYPE) ->setNewValue($replace_image); $posted_mock_images[] = $replace_image; } else if (!$existing_image) { // this is an add @@ -162,7 +162,7 @@ final class PholioMockEditController extends PholioController { ->setDescription($description) ->setSequence($sequence); $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PholioTransaction::TYPE_IMAGE_FILE) + ->setTransactionType(PholioImageFileTransaction::TRANSACTIONTYPE) ->setNewValue( array('+' => array($add_image))); $posted_mock_images[] = $add_image; @@ -178,7 +178,7 @@ final class PholioMockEditController extends PholioController { array($existing_image->getPHID() => $description)); $xactions[] = id(new PholioTransaction()) ->setTransactionType( - PholioTransaction::TYPE_IMAGE_SEQUENCE) + PholioImageSequenceTransaction::TRANSACTIONTYPE) ->setNewValue( array($existing_image->getPHID() => $sequence)); @@ -189,7 +189,7 @@ final class PholioMockEditController extends PholioController { if (!isset($files[$file_phid]) && !isset($replaces[$file_phid])) { // this is an outright delete $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PholioTransaction::TYPE_IMAGE_FILE) + ->setTransactionType(PholioImageFileTransaction::TRANSACTIONTYPE) ->setNewValue( array('-' => array($mock_image))); } diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php index 322eaa7267..ebd5703170 100644 --- a/src/applications/pholio/editor/PholioMockEditor.php +++ b/src/applications/pholio/editor/PholioMockEditor.php @@ -17,7 +17,8 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $this->newImages = $new_images; return $this; } - private function getNewImages() { + + public function getNewImages() { return $this->newImages; } @@ -29,111 +30,17 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = PholioTransaction::TYPE_INLINE; - - $types[] = PholioTransaction::TYPE_IMAGE_FILE; - $types[] = PholioTransaction::TYPE_IMAGE_REPLACE; - $types[] = PholioTransaction::TYPE_IMAGE_SEQUENCE; - return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_IMAGE_FILE: - $images = $object->getImages(); - return mpull($images, 'getPHID'); - case PholioTransaction::TYPE_IMAGE_REPLACE: - $raw = $xaction->getNewValue(); - return $raw->getReplacesImagePHID(); - case PholioTransaction::TYPE_IMAGE_SEQUENCE: - $sequence = null; - $phid = null; - $image = $this->getImageForXaction($object, $xaction); - if ($image) { - $sequence = $image->getSequence(); - $phid = $image->getPHID(); - } - return array($phid => $sequence); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_IMAGE_SEQUENCE: - return $xaction->getNewValue(); - case PholioTransaction::TYPE_IMAGE_REPLACE: - $raw = $xaction->getNewValue(); - return $raw->getPHID(); - case PholioTransaction::TYPE_IMAGE_FILE: - $raw_new_value = $xaction->getNewValue(); - $new_value = array(); - foreach ($raw_new_value as $key => $images) { - $new_value[$key] = mpull($images, 'getPHID'); - } - $xaction->setNewValue($new_value); - return $this->getPHIDTransactionNewValue($xaction); - } - } - - protected function extractFilePHIDsFromCustomTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $images = $this->getNewImages(); - $images = mpull($images, null, 'getPHID'); - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_IMAGE_FILE: - $file_phids = array(); - foreach ($xaction->getNewValue() as $image_phid) { - $image = idx($images, $image_phid); - if (!$image) { - continue; - } - $file_phids[] = $image->getFilePHID(); - } - return $file_phids; - case PholioTransaction::TYPE_IMAGE_REPLACE: - $image_phid = $xaction->getNewValue(); - $image = idx($images, $image_phid); - - if ($image) { - return array($image->getFilePHID()); - } - break; - } - - return parent::extractFilePHIDsFromCustomTransaction($object, $xaction); - } - - - protected function transactionHasEffect( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_INLINE: - return true; - } - - return parent::transactionHasEffect($object, $xaction); - } - protected function shouldApplyInitialEffects( PhabricatorLiskDAO $object, array $xactions) { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_IMAGE_FILE: - case PholioTransaction::TYPE_IMAGE_REPLACE: + case PholioImageFileTransaction::TRANSACTIONTYPE: + case PholioImageReplaceTransaction::TRANSACTIONTYPE: return true; break; } @@ -148,7 +55,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $new_images = array(); foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_IMAGE_FILE: + case PholioImageFileTransaction::TRANSACTIONTYPE: $new_value = $xaction->getNewValue(); foreach ($new_value as $key => $txn_images) { if ($key != '+') { @@ -160,7 +67,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { } } break; - case PholioTransaction::TYPE_IMAGE_REPLACE: + case PholioImageReplaceTransaction::TRANSACTIONTYPE: $image = $xaction->getNewValue(); $image->save(); $new_images[] = $image; @@ -170,67 +77,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $this->setNewImages($new_images); } - private function getImageForXaction( - PholioMock $mock, - PhabricatorApplicationTransaction $xaction) { - $raw_new_value = $xaction->getNewValue(); - $image_phid = key($raw_new_value); - $images = $mock->getImages(); - foreach ($images as $image) { - if ($image->getPHID() == $image_phid) { - return $image; - } - } - return null; - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - return; - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_IMAGE_FILE: - $old_map = array_fuse($xaction->getOldValue()); - $new_map = array_fuse($xaction->getNewValue()); - - $obsolete_map = array_diff_key($old_map, $new_map); - $images = $object->getImages(); - foreach ($images as $seq => $image) { - if (isset($obsolete_map[$image->getPHID()])) { - $image->setIsObsolete(1); - $image->save(); - unset($images[$seq]); - } - } - $object->attachImages($images); - break; - case PholioTransaction::TYPE_IMAGE_REPLACE: - $old = $xaction->getOldValue(); - $images = $object->getImages(); - foreach ($images as $seq => $image) { - if ($image->getPHID() == $old) { - $image->setIsObsolete(1); - $image->save(); - unset($images[$seq]); - } - } - $object->attachImages($images); - break; - case PholioTransaction::TYPE_IMAGE_SEQUENCE: - $image = $this->getImageForXaction($object, $xaction); - $value = (int)head($xaction->getNewValue()); - $image->setSequence($value); - $image->save(); - break; - } - } - protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { @@ -244,35 +90,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { return $xactions; } - protected function mergeTransactions( - PhabricatorApplicationTransaction $u, - PhabricatorApplicationTransaction $v) { - - $type = $u->getTransactionType(); - switch ($type) { - case PholioTransaction::TYPE_IMAGE_REPLACE: - $u_img = $u->getNewValue(); - $v_img = $v->getNewValue(); - if ($u_img->getReplacesImagePHID() == $v_img->getReplacesImagePHID()) { - return $v; - } - break; - case PholioTransaction::TYPE_IMAGE_FILE: - return $this->mergePHIDOrEdgeTransactions($u, $v); - case PholioTransaction::TYPE_IMAGE_SEQUENCE: - $raw_new_value_u = $u->getNewValue(); - $raw_new_value_v = $v->getNewValue(); - $phid_u = key($raw_new_value_u); - $phid_v = key($raw_new_value_v); - if ($phid_u == $phid_v) { - return $v; - } - break; - } - - return parent::mergeTransactions($u, $v); - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { @@ -316,7 +133,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { } $comment = $xaction->getComment(); switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: if ($comment && strlen($comment->getContent())) { $inline_comments[] = $comment; } @@ -406,7 +223,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { // Move inline comments to the end, so the comments precede them. foreach ($xactions as $xaction) { $type = $xaction->getTransactionType(); - if ($type == PholioTransaction::TYPE_INLINE) { + if ($type == PholioMockInlineTransaction::TRANSACTIONTYPE) { $tail[] = $xaction; } else { $head[] = $xaction; @@ -421,7 +238,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: return true; } diff --git a/src/applications/pholio/storage/PholioTransaction.php b/src/applications/pholio/storage/PholioTransaction.php index 84a833b29f..a932cf2a60 100644 --- a/src/applications/pholio/storage/PholioTransaction.php +++ b/src/applications/pholio/storage/PholioTransaction.php @@ -2,14 +2,6 @@ final class PholioTransaction extends PhabricatorModularTransaction { - // Edits to images within the mock - const TYPE_IMAGE_FILE = 'image-file'; - const TYPE_IMAGE_REPLACE = 'image-replace'; - const TYPE_IMAGE_SEQUENCE = 'image-sequence'; - - // Your witty commentary at the mock : image : x,y level - const TYPE_INLINE = 'inline'; - const MAILTAG_STATUS = 'pholio-status'; const MAILTAG_COMMENT = 'pholio-comment'; const MAILTAG_UPDATED = 'pholio-updated'; @@ -35,65 +27,10 @@ final class PholioTransaction extends PhabricatorModularTransaction { return new PholioTransactionView(); } - public function getRequiredHandlePHIDs() { - $phids = parent::getRequiredHandlePHIDs(); - $phids[] = $this->getObjectPHID(); - - $new = $this->getNewValue(); - $old = $this->getOldValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_IMAGE_FILE: - $phids = array_merge($phids, $new, $old); - break; - case self::TYPE_IMAGE_REPLACE: - $phids[] = $new; - $phids[] = $old; - break; - case PholioImageDescriptionTransaction::TRANSACTIONTYPE: - case PholioImageNameTransaction::TRANSACTIONTYPE: - case self::TYPE_IMAGE_SEQUENCE: - $phids[] = key($new); - break; - } - - return $phids; - } - - public function shouldHide() { - $old = $this->getOldValue(); - - switch ($this->getTransactionType()) { - // this is boring / silly to surface; changing sequence is NBD - case self::TYPE_IMAGE_SEQUENCE: - return true; - } - - return parent::shouldHide(); - } - - public function getIcon() { - - $new = $this->getNewValue(); - $old = $this->getOldValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_INLINE: - return 'fa-comment'; - case self::TYPE_IMAGE_SEQUENCE: - return 'fa-pencil'; - case self::TYPE_IMAGE_FILE: - case self::TYPE_IMAGE_REPLACE: - return 'fa-picture-o'; - } - - return parent::getIcon(); - } - public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { - case self::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: case PhabricatorTransactions::TYPE_COMMENT: $tags[] = self::MAILTAG_COMMENT; break; @@ -104,9 +41,9 @@ final class PholioTransaction extends PhabricatorModularTransaction { case PholioMockDescriptionTransaction::TRANSACTIONTYPE: case PholioImageNameTransaction::TRANSACTIONTYPE: case PholioImageDescriptionTransaction::TRANSACTIONTYPE: - case self::TYPE_IMAGE_SEQUENCE: - case self::TYPE_IMAGE_FILE: - case self::TYPE_IMAGE_REPLACE: + case PholioImageSequenceTransaction::TRANSACTIONTYPE: + case PholioImageFileTransaction::TRANSACTIONTYPE: + case PholioImageReplaceTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_UPDATED; break; default: @@ -116,124 +53,4 @@ final class PholioTransaction extends PhabricatorModularTransaction { return $tags; } - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_INLINE: - $count = 1; - foreach ($this->getTransactionGroup() as $xaction) { - if ($xaction->getTransactionType() == $type) { - $count++; - } - } - - return pht( - '%s added %d inline comment(s).', - $this->renderHandleLink($author_phid), - $count); - break; - case self::TYPE_IMAGE_REPLACE: - return pht( - '%s replaced %s with %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - break; - case self::TYPE_IMAGE_FILE: - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - - if ($add && $rem) { - return pht( - '%s edited image(s), added %d: %s; removed %d: %s.', - $this->renderHandleLink($author_phid), - count($add), - $this->renderHandleList($add), - count($rem), - $this->renderHandleList($rem)); - } else if ($add) { - return pht( - '%s added %d image(s): %s.', - $this->renderHandleLink($author_phid), - count($add), - $this->renderHandleList($add)); - } else { - return pht( - '%s removed %d image(s): %s.', - $this->renderHandleLink($author_phid), - count($rem), - $this->renderHandleList($rem)); - } - break; - case self::TYPE_IMAGE_SEQUENCE: - return pht( - '%s updated an image\'s (%s) sequence.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink(key($new))); - break; - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_INLINE: - return pht( - '%s added an inline comment to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_IMAGE_REPLACE: - case self::TYPE_IMAGE_FILE: - return pht( - '%s updated images of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_IMAGE_SEQUENCE: - return pht( - '%s updated image sequence of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - } - - return parent::getTitleForFeed(); - } - - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_IMAGE_REPLACE: - return PhabricatorTransactions::COLOR_YELLOW; - case self::TYPE_IMAGE_FILE: - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - if ($add && $rem) { - return PhabricatorTransactions::COLOR_YELLOW; - } else if ($add) { - return PhabricatorTransactions::COLOR_GREEN; - } else { - return PhabricatorTransactions::COLOR_RED; - } - } - - return parent::getColor(); - } - } diff --git a/src/applications/pholio/view/PholioTransactionView.php b/src/applications/pholio/view/PholioTransactionView.php index 92624feec6..69613c5428 100644 --- a/src/applications/pholio/view/PholioTransactionView.php +++ b/src/applications/pholio/view/PholioTransactionView.php @@ -30,14 +30,14 @@ final class PholioTransactionView switch ($u->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: - case PholioTransaction::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: break; default: return false; } switch ($v->getTransactionType()) { - case PholioTransaction::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: return true; } @@ -50,7 +50,8 @@ final class PholioTransactionView $out = array(); $group = $xaction->getTransactionGroup(); - if ($xaction->getTransactionType() == PholioTransaction::TYPE_INLINE) { + $type = $xaction->getTransactionType(); + if ($type == PholioMockInlineTransaction::TRANSACTIONTYPE) { array_unshift($group, $xaction); } else { $out[] = parent::renderTransactionContent($xaction); @@ -63,7 +64,7 @@ final class PholioTransactionView $inlines = array(); foreach ($group as $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: $inlines[] = $xaction; break; default: diff --git a/src/applications/pholio/xaction/PholioImageFileTransaction.php b/src/applications/pholio/xaction/PholioImageFileTransaction.php new file mode 100644 index 0000000000..5f68dad9f1 --- /dev/null +++ b/src/applications/pholio/xaction/PholioImageFileTransaction.php @@ -0,0 +1,120 @@ +getImages(); + return array_values(mpull($images, 'getPHID')); + } + + public function generateNewValue($object, $value) { + $new_value = array(); + foreach ($value as $key => $images) { + $new_value[$key] = mpull($images, 'getPHID'); + } + $old = array_fuse($this->getOldValue()); + return $this->getEditor()->getPHIDList($old, $new_value); + } + + public function applyInternalEffects($object, $value) { + $old_map = array_fuse($this->getOldValue()); + $new_map = array_fuse($this->getNewValue()); + + $obsolete_map = array_diff_key($old_map, $new_map); + $images = $object->getImages(); + foreach ($images as $seq => $image) { + if (isset($obsolete_map[$image->getPHID()])) { + $image->setIsObsolete(1); + $image->save(); + unset($images[$seq]); + } + } + $object->attachImages($images); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + if ($add && $rem) { + return pht( + '%s edited image(s), added %d: %s; removed %d: %s.', + $this->renderAuthor(), + count($add), + $this->renderHandleList($add), + count($rem), + $this->renderHandleList($rem)); + } else if ($add) { + return pht( + '%s added %d image(s): %s.', + $this->renderAuthor(), + count($add), + $this->renderHandleList($add)); + } else { + return pht( + '%s removed %d image(s): %s.', + $this->renderAuthor(), + count($rem), + $this->renderHandleList($rem)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s updated images of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-picture-o'; + } + + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + if ($add && $rem) { + return PhabricatorTransactions::COLOR_YELLOW; + } else if ($add) { + return PhabricatorTransactions::COLOR_GREEN; + } else { + return PhabricatorTransactions::COLOR_RED; + } + } + + public function extractFilePHIDs($object, $value) { + $images = $this->getEditor()->getNewImages(); + $images = mpull($images, null, 'getPHID'); + + + $file_phids = array(); + foreach ($value as $image_phid) { + $image = idx($images, $image_phid); + if (!$image) { + continue; + } + $file_phids[] = $image->getFilePHID(); + } + return $file_phids; + } + + public function mergeTransactions( + $object, + PhabricatorApplicationTransaction $u, + PhabricatorApplicationTransaction $v) { + return $this->getEditor()->mergePHIDOrEdgeTransactions($u, $v); + } + +} diff --git a/src/applications/pholio/xaction/PholioImageReplaceTransaction.php b/src/applications/pholio/xaction/PholioImageReplaceTransaction.php new file mode 100644 index 0000000000..e6d45dfc7a --- /dev/null +++ b/src/applications/pholio/xaction/PholioImageReplaceTransaction.php @@ -0,0 +1,68 @@ +getNewValue(); + return $new_image->getReplacesImagePHID(); + } + + public function generateNewValue($object, $value) { + return $value->getPHID(); + } + + public function applyInternalEffects($object, $value) { + $old = $this->getOldValue(); + $images = $object->getImages(); + foreach ($images as $seq => $image) { + if ($image->getPHID() == $old) { + $image->setIsObsolete(1); + $image->save(); + unset($images[$seq]); + } + } + $object->attachImages($images); + } + + public function getTitle() { + return pht( + '%s replaced %s with %s.', + $this->renderAuthor(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + + public function getTitleForFeed() { + return pht( + '%s updated images of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-picture-o'; + } + + public function getColor() { + return PhabricatorTransactions::COLOR_YELLOW; + } + + public function mergeTransactions( + $object, + PhabricatorApplicationTransaction $u, + PhabricatorApplicationTransaction $v) { + $u_img = $u->getNewValue(); + $v_img = $v->getNewValue(); + if ($u_img->getReplacesImagePHID() == $v_img->getReplacesImagePHID()) { + return $v; + } + } + + public function extractFilePHIDs($object, $value) { + return array($value); + } + +} diff --git a/src/applications/pholio/xaction/PholioImageSequenceTransaction.php b/src/applications/pholio/xaction/PholioImageSequenceTransaction.php new file mode 100644 index 0000000000..c98c199adf --- /dev/null +++ b/src/applications/pholio/xaction/PholioImageSequenceTransaction.php @@ -0,0 +1,60 @@ +getImageForXaction($object); + if ($image) { + $sequence = $image->getSequence(); + $phid = $image->getPHID(); + } + return array($phid => $sequence); + } + + public function applyInternalEffects($object, $value) { + $image = $this->getImageForXaction($object); + $value = (int)head($this->getNewValue()); + $image->setSequence($value); + $image->save(); + } + + public function getTitle() { + $new = $this->getNewValue(); + + return pht( + '%s updated an image\'s (%s) sequence.', + $this->renderAuthor(), + $this->renderHandleLink(key($new))); + } + + public function getTitleForFeed() { + return pht( + '%s updated image sequence of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function shouldHide() { + // this is boring / silly to surface; changing sequence is NBD + return true; + } + + public function mergeTransactions( + $object, + PhabricatorApplicationTransaction $u, + PhabricatorApplicationTransaction $v) { + $raw_new_value_u = $u->getNewValue(); + $raw_new_value_v = $v->getNewValue(); + $phid_u = key($raw_new_value_u); + $phid_v = key($raw_new_value_v); + if ($phid_u == $phid_v) { + return $v; + } + } + +} diff --git a/src/applications/pholio/xaction/PholioMockDescriptionTransaction.php b/src/applications/pholio/xaction/PholioMockDescriptionTransaction.php index ed14d43c99..75293168e1 100644 --- a/src/applications/pholio/xaction/PholioMockDescriptionTransaction.php +++ b/src/applications/pholio/xaction/PholioMockDescriptionTransaction.php @@ -44,4 +44,14 @@ final class PholioMockDescriptionTransaction ->setNewText($this->getNewValue()); } + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + } diff --git a/src/applications/pholio/xaction/PholioMockInlineTransaction.php b/src/applications/pholio/xaction/PholioMockInlineTransaction.php new file mode 100644 index 0000000000..f89fed0c3a --- /dev/null +++ b/src/applications/pholio/xaction/PholioMockInlineTransaction.php @@ -0,0 +1,33 @@ +renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s added an inline comment to %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-comment'; + } + + public function getTransactionHasEffect($object, $old, $new) { + return true; + } + +} diff --git a/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php b/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php index daf3daa11d..2e78d37d5c 100644 --- a/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php +++ b/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php @@ -43,8 +43,8 @@ final class PhortuneSubscriptionViewController extends PhortuneController { $curtain->addAction( id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Subscription')) + ->setIcon('fa-credit-card') + ->setName(pht('Manage Autopay')) ->setHref($edit_uri) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); diff --git a/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php index 36c74f4a68..4b05874410 100644 --- a/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php @@ -47,7 +47,7 @@ final class PhrictionCreateConduitAPIMethod extends PhrictionConduitAPIMethod { $xactions = array(); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_TITLE) + ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('title')); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) diff --git a/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php index e99a866529..d4c2b63b5a 100644 --- a/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php @@ -42,7 +42,7 @@ final class PhrictionEditConduitAPIMethod extends PhrictionConduitAPIMethod { $xactions = array(); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_TITLE) + ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('title')); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php index 038861d136..1917348c24 100644 --- a/src/applications/phriction/controller/PhrictionEditController.php +++ b/src/applications/phriction/controller/PhrictionEditController.php @@ -133,7 +133,7 @@ final class PhrictionEditController $xactions = array(); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_TITLE) + ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) @@ -174,7 +174,8 @@ final class PhrictionEditController } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_title = nonempty( - $ex->getShortMessage(PhrictionTransaction::TYPE_TITLE), + $ex->getShortMessage( + PhrictionDocumentTitleTransaction::TRANSACTIONTYPE), true); $e_content = nonempty( $ex->getShortMessage(PhrictionTransaction::TYPE_CONTENT), diff --git a/src/applications/phriction/controller/PhrictionMoveController.php b/src/applications/phriction/controller/PhrictionMoveController.php index 53e5ae16ec..f25465c3c0 100644 --- a/src/applications/phriction/controller/PhrictionMoveController.php +++ b/src/applications/phriction/controller/PhrictionMoveController.php @@ -64,7 +64,8 @@ final class PhrictionMoveController extends PhrictionController { $xactions = array(); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_MOVE_TO) + ->setTransactionType( + PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE) ->setNewValue($document); $target_document = id(new PhrictionDocumentQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) @@ -88,7 +89,8 @@ final class PhrictionMoveController extends PhrictionController { return id(new AphrontRedirectResponse())->setURI($redir_uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; - $e_slug = $ex->getShortMessage(PhrictionTransaction::TYPE_MOVE_TO); + $e_slug = $ex->getShortMessage( + PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE); } } diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php index af238dfcf9..9ab91d5db9 100644 --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -29,7 +29,7 @@ final class PhrictionTransactionEditor return $this; } - private function getOldContent() { + public function getOldContent() { return $this->oldContent; } @@ -38,7 +38,7 @@ final class PhrictionTransactionEditor return $this; } - private function getNewContent() { + public function getNewContent() { return $this->newContent; } @@ -69,6 +69,11 @@ final class PhrictionTransactionEditor return $this->processContentVersionError; } + public function setMoveAwayDocument(PhrictionDocument $document) { + $this->moveAwayDocument = $document; + return $this; + } + public function getEditorApplicationClass() { return 'PhabricatorPhrictionApplication'; } @@ -80,10 +85,8 @@ final class PhrictionTransactionEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhrictionTransaction::TYPE_TITLE; $types[] = PhrictionTransaction::TYPE_CONTENT; $types[] = PhrictionTransaction::TYPE_DELETE; - $types[] = PhrictionTransaction::TYPE_MOVE_TO; $types[] = PhrictionTransaction::TYPE_MOVE_AWAY; $types[] = PhabricatorTransactions::TYPE_EDGE; @@ -99,18 +102,12 @@ final class PhrictionTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: - if ($this->getIsNewObject()) { - return null; - } - return $this->getOldContent()->getTitle(); case PhrictionTransaction::TYPE_CONTENT: if ($this->getIsNewObject()) { return null; } return $this->getOldContent()->getContent(); case PhrictionTransaction::TYPE_DELETE: - case PhrictionTransaction::TYPE_MOVE_TO: case PhrictionTransaction::TYPE_MOVE_AWAY: return null; } @@ -121,21 +118,9 @@ final class PhrictionTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: case PhrictionTransaction::TYPE_CONTENT: case PhrictionTransaction::TYPE_DELETE: return $xaction->getNewValue(); - case PhrictionTransaction::TYPE_MOVE_TO: - $document = $xaction->getNewValue(); - // grab the real object now for the sub-editor to come - $this->moveAwayDocument = $document; - $dict = array( - 'id' => $document->getID(), - 'phid' => $document->getPHID(), - 'content' => $document->getContent()->getContent(), - 'title' => $document->getContent()->getTitle(), - ); - return $dict; case PhrictionTransaction::TYPE_MOVE_AWAY: $document = $xaction->getNewValue(); $dict = array( @@ -154,10 +139,10 @@ final class PhrictionTransactionEditor foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: case PhrictionTransaction::TYPE_CONTENT: case PhrictionTransaction::TYPE_DELETE: - case PhrictionTransaction::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: case PhrictionTransaction::TYPE_MOVE_AWAY: return true; } @@ -178,9 +163,7 @@ final class PhrictionTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: case PhrictionTransaction::TYPE_CONTENT: - case PhrictionTransaction::TYPE_MOVE_TO: $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); return; case PhrictionTransaction::TYPE_MOVE_AWAY: @@ -210,7 +193,7 @@ final class PhrictionTransactionEditor ->setMetadataValue('contentDelete', true); } break; - case PhrictionTransaction::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: $document = $xaction->getNewValue(); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) @@ -232,9 +215,6 @@ final class PhrictionTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: - $this->getNewContent()->setTitle($xaction->getNewValue()); - break; case PhrictionTransaction::TYPE_CONTENT: $this->getNewContent()->setContent($xaction->getNewValue()); break; @@ -243,14 +223,6 @@ final class PhrictionTransactionEditor $this->getNewContent()->setChangeType( PhrictionChangeType::CHANGE_DELETE); break; - case PhrictionTransaction::TYPE_MOVE_TO: - $dict = $xaction->getNewValue(); - $this->getNewContent()->setContent($dict['content']); - $this->getNewContent()->setTitle($dict['title']); - $this->getNewContent()->setChangeType( - PhrictionChangeType::CHANGE_MOVE_HERE); - $this->getNewContent()->setChangeRef($dict['id']); - break; case PhrictionTransaction::TYPE_MOVE_AWAY: $dict = $xaction->getNewValue(); $this->getNewContent()->setContent(''); @@ -270,11 +242,11 @@ final class PhrictionTransactionEditor $save_content = false; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: case PhrictionTransaction::TYPE_CONTENT: case PhrictionTransaction::TYPE_DELETE: case PhrictionTransaction::TYPE_MOVE_AWAY: - case PhrictionTransaction::TYPE_MOVE_TO: $save_content = true; break; default: @@ -312,7 +284,8 @@ final class PhrictionTransactionEditor $slug); $stub_xactions = array(); $stub_xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_TITLE) + ->setTransactionType( + PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue(PhabricatorSlug::getDefaultTitle($slug)) ->setMetadataValue('stub:create:phid', $object->getPHID()); $stub_xactions[] = id(new PhrictionTransaction()) @@ -458,7 +431,7 @@ final class PhrictionTransactionEditor foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: $dict = $xaction->getNewValue(); $phids[] = $dict['phid']; break; @@ -477,30 +450,6 @@ final class PhrictionTransactionEditor foreach ($xactions as $xaction) { switch ($type) { - case PhrictionTransaction::TYPE_TITLE: - $title = $object->getContent()->getTitle(); - $missing = $this->validateIsEmptyTextField( - $title, - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Document title is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } else if ($this->getProcessContentVersionError()) { - $error = $this->validateContentVersion($object, $type, $xaction); - if ($error) { - $this->setProcessContentVersionError(false); - $errors[] = $error; - } - } - break; - case PhrictionTransaction::TYPE_CONTENT: if ($xaction->getMetadataValue('stub:create:phid')) { continue; @@ -544,31 +493,8 @@ final class PhrictionTransactionEditor break; - case PhrictionTransaction::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: $source_document = $xaction->getNewValue(); - switch ($source_document->getStatus()) { - case PhrictionDocumentStatus::STATUS_DELETED: - $e_text = pht('A deleted document can not be moved.'); - break; - case PhrictionDocumentStatus::STATUS_MOVED: - $e_text = pht('A moved document can not be moved again.'); - break; - case PhrictionDocumentStatus::STATUS_STUB: - $e_text = pht('A stub document can not be moved.'); - break; - default: - $e_text = null; - break; - } - - if ($e_text) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Can not move document.'), - $e_text, - $xaction); - $errors[] = $error; - } $ancestry_errors = $this->validateAncestry( $object, @@ -645,7 +571,7 @@ final class PhrictionTransactionEditor return $errors; } - private function validateAncestry( + public function validateAncestry( PhabricatorLiskDAO $object, $type, PhabricatorApplicationTransaction $xaction, diff --git a/src/applications/phriction/storage/PhrictionTransaction.php b/src/applications/phriction/storage/PhrictionTransaction.php index a3ae6adb9d..d3d78ff665 100644 --- a/src/applications/phriction/storage/PhrictionTransaction.php +++ b/src/applications/phriction/storage/PhrictionTransaction.php @@ -1,12 +1,10 @@ getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: case self::TYPE_MOVE_AWAY: $phids[] = $new['phid']; break; - case self::TYPE_TITLE: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: if ($this->getMetadataValue('stub:create:phid')) { $phids[] = $this->getMetadataValue('stub:create:phid'); } break; } - return $phids; } @@ -74,10 +75,10 @@ final class PhrictionTransaction public function shouldHideForMail(array $xactions) { switch ($this->getTransactionType()) { - case self::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: case self::TYPE_MOVE_AWAY: return true; - case self::TYPE_TITLE: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: return $this->getMetadataValue('stub:create:phid', false); } return parent::shouldHideForMail($xactions); @@ -85,10 +86,10 @@ final class PhrictionTransaction public function shouldHideForFeed() { switch ($this->getTransactionType()) { - case self::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: case self::TYPE_MOVE_AWAY: return true; - case self::TYPE_TITLE: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: return $this->getMetadataValue('stub:create:phid', false); } return parent::shouldHideForFeed(); @@ -96,13 +97,10 @@ final class PhrictionTransaction public function getActionStrength() { switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - return 1.4; case self::TYPE_CONTENT: return 1.3; case self::TYPE_DELETE: return 1.5; - case self::TYPE_MOVE_TO: case self::TYPE_MOVE_AWAY: return 1.0; } @@ -115,29 +113,12 @@ final class PhrictionTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - if ($this->getMetadataValue('stub:create:phid')) { - return pht('Stubbed'); - } else { - return pht('Created'); - } - } - - return pht('Retitled'); - case self::TYPE_CONTENT: return pht('Edited'); - case self::TYPE_DELETE: return pht('Deleted'); - - case self::TYPE_MOVE_TO: - return pht('Moved'); - case self::TYPE_MOVE_AWAY: return pht('Moved Away'); - } return parent::getActionName(); @@ -148,12 +129,10 @@ final class PhrictionTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: case self::TYPE_CONTENT: return 'fa-pencil'; case self::TYPE_DELETE: return 'fa-times'; - case self::TYPE_MOVE_TO: case self::TYPE_MOVE_AWAY: return 'fa-arrows'; } @@ -169,26 +148,6 @@ final class PhrictionTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - if ($this->getMetadataValue('stub:create:phid')) { - return pht( - '%s stubbed out this document when creating %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink( - $this->getMetadataValue('stub:create:phid'))); - } else { - return pht( - '%s created this document.', - $this->renderHandleLink($author_phid)); - } - } - return pht( - '%s changed the title from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - case self::TYPE_CONTENT: return pht( '%s edited the document content.', @@ -199,12 +158,6 @@ final class PhrictionTransaction '%s deleted this document.', $this->renderHandleLink($author_phid)); - case self::TYPE_MOVE_TO: - return pht( - '%s moved this document from %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new['phid'])); - case self::TYPE_MOVE_AWAY: return pht( '%s moved this document to %s', @@ -224,20 +177,6 @@ final class PhrictionTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - - return pht( - '%s renamed %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old, - $new); case self::TYPE_CONTENT: return pht( @@ -273,7 +212,7 @@ final class PhrictionTransaction public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_TITLE; break; case self::TYPE_CONTENT: diff --git a/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php new file mode 100644 index 0000000000..a95e70ebdb --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php @@ -0,0 +1,105 @@ + $document->getID(), + 'phid' => $document->getPHID(), + 'content' => $document->getContent()->getContent(), + 'title' => $document->getContent()->getTitle(), + ); + + $editor = $this->getEditor(); + $editor->setMoveAwayDocument($document); + + return $dict; + } + + public function applyInternalEffects($object, $value) { + $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); + } + + public function applyExternalEffects($object, $value) { + $dict = $value; + $this->getEditor()->getNewContent()->setContent($dict['content']); + $this->getEditor()->getNewContent()->setTitle($dict['title']); + $this->getEditor()->getNewContent()->setChangeType( + PhrictionChangeType::CHANGE_MOVE_HERE); + $this->getEditor()->getNewContent()->setChangeRef($dict['id']); + } + + public function getActionStrength() { + return 1.0; + } + + public function getActionName() { + return pht('Moved'); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s moved this document from %s', + $this->renderAuthor(), + $this->renderHandle($new['phid'])); + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s moved %s from %s', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($new['phid'])); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $e_text = null; + foreach ($xactions as $xaction) { + $source_document = $xaction->getNewValue(); + switch ($source_document->getStatus()) { + case PhrictionDocumentStatus::STATUS_DELETED: + $e_text = pht('A deleted document can not be moved.'); + break; + case PhrictionDocumentStatus::STATUS_MOVED: + $e_text = pht('A moved document can not be moved again.'); + break; + case PhrictionDocumentStatus::STATUS_STUB: + $e_text = pht('A stub document can not be moved.'); + break; + default: + $e_text = null; + break; + } + + if ($e_text !== null) { + $errors[] = $this->newInvalidError($e_text); + } + + } + + // TODO: Move Ancestry validation here once all types are converted. + + return $errors; + } + + public function getIcon() { + return 'fa-arrows'; + } + +} diff --git a/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php new file mode 100644 index 0000000000..4f1ba850a7 --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php @@ -0,0 +1,97 @@ +isNewObject()) { + return null; + } + return $this->getEditor()->getOldContent()->getTitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); + } + + public function applyExternalEffects($object, $value) { + $this->getEditor()->getNewContent()->setTitle($value); + } + + public function getActionStrength() { + return 1.4; + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + if ($this->getMetadataValue('stub:create:phid')) { + return pht('Stubbed'); + } else { + return pht('Created'); + } + } + return pht('Retitled'); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + if ($this->getMetadataValue('stub:create:phid')) { + return pht( + '%s stubbed out this document when creating %s.', + $this->renderAuthor(), + $this->renderHandleLink( + $this->getMetadataValue('stub:create:phid'))); + } else { + return pht( + '%s created this document.', + $this->renderAuthor()); + } + } + + return pht( + '%s changed the title from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $title = $object->getContent()->getTitle(); + if ($this->isEmptyTextTransaction($title, $xactions)) { + $errors[] = $this->newRequiredError( + pht('Documents must have a title.')); + } + + return $errors; + } + +} diff --git a/src/applications/phriction/xaction/PhrictionDocumentTransactionType.php b/src/applications/phriction/xaction/PhrictionDocumentTransactionType.php new file mode 100644 index 0000000000..d99b53a303 --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentTransactionType.php @@ -0,0 +1,4 @@ +setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setNewValue($name); $this->applyTransactions($project, $user, $xactions); $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($name)); $this->applyTransactions($project, $user, $xactions); @@ -382,11 +382,11 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setNewValue($name2); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($name2)); $this->applyTransactions($project, $user, $xactions); @@ -416,7 +416,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($input, $input)); $this->applyTransactions($project, $user, $xactions); @@ -448,7 +448,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($input)); $this->applyTransactions($project, $user, $xactions); @@ -474,7 +474,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($input)); $caught = null; @@ -503,7 +503,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setNewValue($name); $xactions[] = id(new PhabricatorProjectTransaction()) @@ -601,11 +601,11 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setNewValue($name); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($slug)); $this->applyTransactions($project, $user, $xactions); @@ -1290,7 +1290,8 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $new_name = $proj->getName().' '.mt_rand(); $xaction = new PhabricatorProjectTransaction(); - $xaction->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME); + $xaction->setTransactionType( + PhabricatorProjectNameTransaction::TRANSACTIONTYPE); $xaction->setNewValue($new_name); $this->applyTransactions($proj, $user, array($xaction)); @@ -1337,7 +1338,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setNewValue($name); if ($projects) { @@ -1440,7 +1441,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setNewValue($name); if ($parent) { diff --git a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php index 66075e7248..cbaa006951 100644 --- a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php +++ b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php @@ -42,7 +42,7 @@ final class ProjectCreateConduitAPIMethod extends ProjectConduitAPIMethod { $user); $project = PhabricatorProject::initializeNewProject($user); - $type_name = PhabricatorProjectTransaction::TYPE_NAME; + $type_name = PhabricatorProjectNameTransaction::TRANSACTIONTYPE; $members = $request->getValue('members'); $xactions = array(); @@ -52,19 +52,22 @@ final class ProjectCreateConduitAPIMethod extends ProjectConduitAPIMethod { if ($request->getValue('icon')) { $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_ICON) + ->setTransactionType( + PhabricatorProjectIconTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('icon')); } if ($request->getValue('color')) { $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_COLOR) + ->setTransactionType( + PhabricatorProjectColorTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('color')); } if ($request->getValue('tags')) { $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType( + PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('tags')); } diff --git a/src/applications/project/controller/PhabricatorProjectArchiveController.php b/src/applications/project/controller/PhabricatorProjectArchiveController.php index ee974bf8e8..b97d5391ab 100644 --- a/src/applications/project/controller/PhabricatorProjectArchiveController.php +++ b/src/applications/project/controller/PhabricatorProjectArchiveController.php @@ -32,7 +32,8 @@ final class PhabricatorProjectArchiveController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_STATUS) + ->setTransactionType( + PhabricatorProjectStatusTransaction::TRANSACTIONTYPE) ->setNewValue($new_status); id(new PhabricatorProjectTransactionEditor()) diff --git a/src/applications/project/controller/PhabricatorProjectBoardManageController.php b/src/applications/project/controller/PhabricatorProjectBoardManageController.php index aecb5aa42a..5c71dcfb61 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardManageController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardManageController.php @@ -31,6 +31,7 @@ final class PhabricatorProjectBoardManageController $board_id = $board->getID(); $header = $this->buildHeaderView($board); + $curtain = $this->buildCurtainView($board); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Workboard'), "/project/board/{$board_id}/"); @@ -40,9 +41,14 @@ final class PhabricatorProjectBoardManageController $nav = $this->getProfileMenu(); $columns_list = $this->buildColumnsList($board, $columns); + require_celerity_resource('project-view-css'); + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFooter($columns_list); + ->addClass('project-view-home') + ->addClass('project-view-people-home') + ->setCurtain($curtain) + ->setMainColumn($columns_list); $title = array( pht('Manage Workboard'), @@ -59,30 +65,35 @@ final class PhabricatorProjectBoardManageController private function buildHeaderView(PhabricatorProject $board) { $viewer = $this->getViewer(); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Workboard: %s', $board->getDisplayName())) + ->setUser($viewer); + + return $header; + } + + private function buildCurtainView(PhabricatorProject $board) { + $viewer = $this->getViewer(); + $id = $board->getID(); + + $curtain = $this->newCurtainView(); + $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $board, PhabricatorPolicyCapability::CAN_EDIT); - $id = $board->getID(); $disable_uri = $this->getApplicationURI("board/{$id}/disable/"); - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-ban') - ->setText(pht('Disable Board')) - ->setHref($disable_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(true); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-ban') + ->setName(pht('Disable Workboard')) + ->setHref($disable_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(true)); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Workboard: %s', $board->getDisplayName())) - ->setUser($viewer) - ->setPolicyObject($board) - ->setProfileHeader(true) - ->addActionLink($button); - - return $header; + return $curtain; } private function buildColumnsList( @@ -123,6 +134,7 @@ final class PhabricatorProjectBoardManageController return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Columns')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($view); } diff --git a/src/applications/project/controller/PhabricatorProjectBoardReorderController.php b/src/applications/project/controller/PhabricatorProjectBoardReorderController.php index fc348bb02f..8cf75ab2a4 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardReorderController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardReorderController.php @@ -94,7 +94,8 @@ final class PhabricatorProjectBoardReorderController $list = id(new PHUIObjectItemListView()) ->setUser($viewer) ->setID($list_id) - ->setFlush(true); + ->setFlush(true) + ->setDrag(true); foreach ($columns as $column) { // Don't allow milestone columns to be reordered. @@ -134,14 +135,9 @@ final class PhabricatorProjectBoardReorderController 'reorderURI' => $reorder_uri, )); - $note = id(new PHUIInfoView()) - ->appendChild(pht('Drag and drop columns to reorder them.')) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE); - return $this->newDialog() ->setTitle(pht('Reorder Columns')) ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($note) ->appendChild($list) ->addSubmitButton(pht('Done')); } diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 6a7083b10d..da1f0ccb95 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -879,12 +879,6 @@ final class PhabricatorProjectBoardViewController ->setWorkflow(true); } - $details_uri = 'board/'.$this->id.'/column/'.$column->getID().'/'; - $column_items[] = id(new PhabricatorActionView()) - ->setName(pht('Column History')) - ->setIcon('fa-columns') - ->setHref($this->getApplicationURI($details_uri)); - $column_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($column_items as $item) { diff --git a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php index d922d24714..24efec5ebb 100644 --- a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php +++ b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php @@ -44,36 +44,38 @@ final class PhabricatorProjectColumnDetailController $title = $column->getDisplayName(); $header = $this->buildHeaderView($column); - $actions = $this->buildActionView($column); - $properties = $this->buildPropertyView($column, $actions); + $properties = $this->buildPropertyView($column); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Workboard'), "/project/board/{$project_id}/"); $crumbs->addTextCrumb(pht('Column: %s', $title)); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $crumbs->setBorder(true); $nav = $this->getProfileMenu(); + require_celerity_resource('project-view-css'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->addClass('project-view-home') + ->addClass('project-view-people-home') + ->setMainColumn(array( + $properties, + $timeline, + )); return $this->newPage() ->setTitle($title) ->setNavigation($nav) ->setCrumbs($crumbs) - ->appendChild( - array( - $box, - $timeline, - )); + ->appendChild($view); } private function buildHeaderView(PhabricatorProjectColumn $column) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $header = id(new PHUIHeaderView()) - ->setUser($viewer) - ->setHeader($column->getDisplayName()); + ->setHeader(pht('Column: %s', $column->getDisplayName())) + ->setUser($viewer); if ($column->isHidden()) { $header->setStatus('fa-ban', 'dark', pht('Hidden')); @@ -82,41 +84,13 @@ final class PhabricatorProjectColumnDetailController return $header; } - private function buildActionView(PhabricatorProjectColumn $column) { - $viewer = $this->getRequest()->getUser(); - - $id = $column->getID(); - $project_id = $this->getProject()->getID(); - $base_uri = '/board/'.$project_id.'/'; - - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $column, - PhabricatorPolicyCapability::CAN_EDIT); - - $actions->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Column')) - ->setIcon('fa-pencil') - ->setHref($this->getApplicationURI($base_uri.'edit/'.$id.'/')) - ->setDisabled(!$can_edit) - ->setWorkflow(true)); - - return $actions; - } - private function buildPropertyView( - PhabricatorProjectColumn $column, - PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + PhabricatorProjectColumn $column) { + $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) - ->setObject($column) - ->setActionList($actions); + ->setObject($column); $limit = $column->getPointLimit(); if ($limit === null) { @@ -126,7 +100,12 @@ final class PhabricatorProjectColumnDetailController } $properties->addProperty(pht('Point Limit'), $limit_text); - return $properties; + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Details')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($properties); + + return $box; } } diff --git a/src/applications/project/controller/PhabricatorProjectColumnHideController.php b/src/applications/project/controller/PhabricatorProjectColumnHideController.php index 27dbb17c47..1dd5e47ecb 100644 --- a/src/applications/project/controller/PhabricatorProjectColumnHideController.php +++ b/src/applications/project/controller/PhabricatorProjectColumnHideController.php @@ -65,7 +65,8 @@ final class PhabricatorProjectColumnHideController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_STATUS) + ->setTransactionType( + PhabricatorProjectStatusTransaction::TRANSACTIONTYPE) ->setNewValue($new_status); id(new PhabricatorProjectTransactionEditor()) diff --git a/src/applications/project/controller/PhabricatorProjectCoverController.php b/src/applications/project/controller/PhabricatorProjectCoverController.php index 22f787e56b..98f6c1c995 100644 --- a/src/applications/project/controller/PhabricatorProjectCoverController.php +++ b/src/applications/project/controller/PhabricatorProjectCoverController.php @@ -36,7 +36,7 @@ final class PhabricatorProjectCoverController $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_COVER_IMAGE) + ->setTransactionType(ManiphestTaskCoverImageTransaction::TRANSACTIONTYPE) ->setNewValue($file->getPHID()); $editor = id(new ManiphestTransactionEditor()) diff --git a/src/applications/project/controller/PhabricatorProjectEditPictureController.php b/src/applications/project/controller/PhabricatorProjectEditPictureController.php index 4352ec6ad8..95d3bbd855 100644 --- a/src/applications/project/controller/PhabricatorProjectEditPictureController.php +++ b/src/applications/project/controller/PhabricatorProjectEditPictureController.php @@ -23,8 +23,7 @@ final class PhabricatorProjectEditPictureController $this->setProject($project); - $edit_uri = $this->getApplicationURI('profile/'.$project->getID().'/'); - $view_uri = $this->getApplicationURI('profile/'.$project->getID().'/'); + $manage_uri = $this->getApplicationURI('manage/'.$project->getID().'/'); $supported_formats = PhabricatorFile::getTransformableImageFormats(); $e_file = true; @@ -78,7 +77,8 @@ final class PhabricatorProjectEditPictureController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_IMAGE) + ->setTransactionType( + PhabricatorProjectImageTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); $editor = id(new PhabricatorProjectTransactionEditor()) @@ -89,7 +89,7 @@ final class PhabricatorProjectEditPictureController $editor->applyTransactions($project, $xactions); - return id(new AphrontRedirectResponse())->setURI($edit_uri); + return id(new AphrontRedirectResponse())->setURI($manage_uri); } } @@ -242,7 +242,7 @@ final class PhabricatorProjectEditPictureController pht('Supported formats: %s', implode(', ', $supported_formats)))) ->appendChild( id(new AphrontFormSubmitControl()) - ->addCancelButton($edit_uri) + ->addCancelButton($manage_uri) ->setValue(pht('Upload Picture'))); $form_box = id(new PHUIObjectBoxView()) diff --git a/src/applications/project/controller/PhabricatorProjectMembersViewController.php b/src/applications/project/controller/PhabricatorProjectMembersViewController.php index 959927c9f3..ad553eb664 100644 --- a/src/applications/project/controller/PhabricatorProjectMembersViewController.php +++ b/src/applications/project/controller/PhabricatorProjectMembersViewController.php @@ -20,26 +20,21 @@ final class PhabricatorProjectMembersViewController $this->setProject($project); $title = pht('Members and Watchers'); - - $properties = $this->buildProperties($project); $curtain = $this->buildCurtainView($project); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Details')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($properties); - $member_list = id(new PhabricatorProjectMemberListView()) ->setUser($viewer) ->setProject($project) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setUserPHIDs($project->getMemberPHIDs()); + ->setUserPHIDs($project->getMemberPHIDs()) + ->setShowNote(true); $watcher_list = id(new PhabricatorProjectWatcherListView()) ->setUser($viewer) ->setProject($project) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setUserPHIDs($project->getWatcherPHIDs()); + ->setUserPHIDs($project->getWatcherPHIDs()) + ->setShowNote(true); $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorProject::ITEM_MEMBERS); @@ -52,16 +47,18 @@ final class PhabricatorProjectMembersViewController ->setHeader($title) ->setHeaderIcon('fa-group'); + require_celerity_resource('project-view-css'); + $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) + ->addClass('project-view-home') + ->addClass('project-view-people-home') ->setMainColumn(array( - $object_box, $member_list, $watcher_list, )); - return $this->newPage() ->setNavigation($nav) ->setCrumbs($crumbs) @@ -69,110 +66,11 @@ final class PhabricatorProjectMembersViewController ->appendChild($view); } - private function buildProperties(PhabricatorProject $project) { - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($project); - - if ($project->isMilestone()) { - $icon_key = PhabricatorProjectIconSet::getMilestoneIconKey(); - $icon = PhabricatorProjectIconSet::getIconIcon($icon_key); - $target = PhabricatorProjectIconSet::getIconName($icon_key); - $note = pht( - 'Members of the parent project are members of this project.'); - $show_join = false; - } else if ($project->getHasSubprojects()) { - $icon = 'fa-sitemap'; - $target = pht('Parent Project'); - $note = pht( - 'Members of all subprojects are members of this project.'); - $show_join = false; - } else if ($project->getIsMembershipLocked()) { - $icon = 'fa-lock'; - $target = pht('Locked Project'); - $note = pht( - 'Users with access may join this project, but may not leave.'); - $show_join = true; - } else { - $icon = 'fa-briefcase'; - $target = pht('Normal Project'); - $note = pht('Users with access may join and leave this project.'); - $show_join = true; - } - - $item = id(new PHUIStatusItemView()) - ->setIcon($icon) - ->setTarget(phutil_tag('strong', array(), $target)) - ->setNote($note); - - $status = id(new PHUIStatusListView()) - ->addItem($item); - - $view->addProperty(pht('Membership'), $status); - - if ($show_join) { - $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( - $viewer, - $project); - - $view->addProperty( - pht('Joinable By'), - $descriptions[PhabricatorPolicyCapability::CAN_JOIN]); - } - - $viewer_phid = $viewer->getPHID(); - - if ($project->isUserWatcher($viewer_phid)) { - $watch_item = id(new PHUIStatusItemView()) - ->setIcon('fa-eye green') - ->setTarget(phutil_tag('strong', array(), pht('Watching'))) - ->setNote( - pht( - 'You will receive mail about changes made to any related '. - 'object.')); - - $watch_status = id(new PHUIStatusListView()) - ->addItem($watch_item); - - $view->addProperty(pht('Watching'), $watch_status); - } - - if ($project->isUserMember($viewer_phid)) { - $is_silenced = $this->isProjectSilenced($project); - if ($is_silenced) { - $mail_icon = 'fa-envelope-o grey'; - $mail_target = pht('Disabled'); - $mail_note = pht( - 'When mail is sent to project members, you will not receive '. - 'a copy.'); - } else { - $mail_icon = 'fa-envelope-o green'; - $mail_target = pht('Enabled'); - $mail_note = pht( - 'You will receive mail that is sent to project members.'); - } - - $mail_item = id(new PHUIStatusItemView()) - ->setIcon($mail_icon) - ->setTarget(phutil_tag('strong', array(), $mail_target)) - ->setNote($mail_note); - - $mail_status = id(new PHUIStatusListView()) - ->addItem($mail_item); - - $view->addProperty(pht('Mail to Members'), $mail_status); - } - - return $view; - } - private function buildCurtainView(PhabricatorProject $project) { $viewer = $this->getViewer(); $id = $project->getID(); - $curtain = $this->newCurtainView($project); + $curtain = $this->newCurtainView(); $is_locked = $project->getIsMembershipLocked(); @@ -272,6 +170,42 @@ final class PhabricatorProjectMembersViewController ->setDisabled(!$can_lock) ->setWorkflow(true)); + if ($project->isMilestone()) { + $icon_key = PhabricatorProjectIconSet::getMilestoneIconKey(); + $header = PhabricatorProjectIconSet::getIconName($icon_key); + $note = pht( + 'Members of the parent project are members of this project.'); + $show_join = false; + } else if ($project->getHasSubprojects()) { + $header = pht('Parent Project'); + $note = pht( + 'Members of all subprojects are members of this project.'); + $show_join = false; + } else if ($project->getIsMembershipLocked()) { + $header = pht('Locked Project'); + $note = pht( + 'Users with access may join this project, but may not leave.'); + $show_join = true; + } else { + $header = pht('Normal Project'); + $note = pht('Users with access may join and leave this project.'); + $show_join = true; + } + + $curtain->newPanel() + ->setHeaderText($header) + ->appendChild($note); + + if ($show_join) { + $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( + $viewer, + $project); + + $curtain->newPanel() + ->setHeaderText(pht('Joinable By')) + ->appendChild($descriptions[PhabricatorPolicyCapability::CAN_JOIN]); + } + return $curtain; } diff --git a/src/applications/project/controller/PhabricatorProjectMoveController.php b/src/applications/project/controller/PhabricatorProjectMoveController.php index c92c843204..68915664cb 100644 --- a/src/applications/project/controller/PhabricatorProjectMoveController.php +++ b/src/applications/project/controller/PhabricatorProjectMoveController.php @@ -153,10 +153,11 @@ final class PhabricatorProjectMoveController $xactions = array(); if ($pri !== null) { $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) ->setNewValue($pri); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY) + ->setTransactionType( + ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE) ->setNewValue($sub); } diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index 4c726f5884..32dbec3c0f 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -113,10 +113,7 @@ final class PhabricatorProjectProfileController ->setCrumbs($crumbs) ->setTitle($project->getDisplayName()) ->setPageObjectPHIDs(array($project->getPHID())) - ->appendChild( - array( - $home, - )); + ->appendChild($home); } private function buildPropertyListView( diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php index 4a89bb4cde..269e80a080 100644 --- a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php +++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php @@ -26,6 +26,9 @@ final class PhabricatorProjectSubprojectsController $allows_subprojects = $project->supportsSubprojects(); $allows_milestones = $project->supportsMilestones(); + $subproject_list = null; + $milestone_list = null; + if ($allows_subprojects) { $subprojects = id(new PhabricatorProjectQuery()) ->setViewer($viewer) @@ -33,6 +36,16 @@ final class PhabricatorProjectSubprojectsController ->needImages(true) ->withIsMilestone(false) ->execute(); + + $subproject_list = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('%s Subprojects', $project->getName())) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList( + id(new PhabricatorProjectListView()) + ->setUser($viewer) + ->setProjects($subprojects) + ->setNoDataString(pht('This project has no subprojects.')) + ->renderList()); } else { $subprojects = array(); } @@ -45,52 +58,25 @@ final class PhabricatorProjectSubprojectsController ->withIsMilestone(true) ->setOrderVector(array('milestoneNumber', 'id')) ->execute(); - } else { - $milestones = array(); - } - if ($milestones) { $milestone_list = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Milestones')) + ->setHeaderText(pht('%s Milestones', $project->getName())) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList( id(new PhabricatorProjectListView()) ->setUser($viewer) ->setProjects($milestones) + ->setNoDataString(pht('This project has no milestones.')) ->renderList()); } else { - $milestone_list = null; + $milestones = array(); } - if ($subprojects) { - $subproject_list = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Subprojects')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setObjectList( - id(new PhabricatorProjectListView()) - ->setUser($viewer) - ->setProjects($subprojects) - ->renderList()); - } else { - $subproject_list = null; - } - - $property_list = $this->buildPropertyList( - $project, - $milestones, - $subprojects); - $curtain = $this->buildCurtainView( $project, $milestones, $subprojects); - - $details = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Details')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($property_list); - $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorProject::ITEM_SUBPROJECTS); @@ -102,11 +88,24 @@ final class PhabricatorProjectSubprojectsController ->setHeader(pht('Subprojects and Milestones')) ->setHeaderIcon('fa-sitemap'); + require_celerity_resource('project-view-css'); + + // This page isn't reachable via UI, but make it pretty anyways. + $info_view = null; + if (!$milestone_list && !$subproject_list) { + $info_view = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild(pht('Milestone projects do not support subprojects '. + 'or milestones.')); + } + $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) + ->addClass('project-view-home') + ->addClass('project-view-people-home') ->setMainColumn(array( - $details, + $info_view, $milestone_list, $subproject_list, )); @@ -118,73 +117,6 @@ final class PhabricatorProjectSubprojectsController ->appendChild($view); } - private function buildPropertyList( - PhabricatorProject $project, - array $milestones, - array $subprojects) { - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer); - - $view->addProperty( - pht('Prototype'), - $this->renderStatus( - 'fa-exclamation-triangle red', - pht('Warning'), - pht('Subprojects and milestones are only partially implemented.'))); - - if (!$project->supportsMilestones()) { - $milestone_status = $this->renderStatus( - 'fa-times grey', - pht('Already Milestone'), - pht( - 'This project is already a milestone, and milestones may not '. - 'have their own milestones.')); - } else { - if (!$milestones) { - $milestone_status = $this->renderStatus( - 'fa-check grey', - pht('None Created'), - pht( - 'You can create milestones for this project.')); - } else { - $milestone_status = $this->renderStatus( - 'fa-check green', - pht('Has Milestones'), - pht('This project has milestones.')); - } - } - - $view->addProperty(pht('Milestones'), $milestone_status); - - if (!$project->supportsSubprojects()) { - $subproject_status = $this->renderStatus( - 'fa-times grey', - pht('Milestone'), - pht( - 'This project is a milestone, and milestones may not have '. - 'subprojects.')); - } else { - if (!$subprojects) { - $subproject_status = $this->renderStatus( - 'fa-check grey', - pht('None Created'), - pht('You can create subprojects for this project.')); - } else { - $subproject_status = $this->renderStatus( - 'fa-check green', - pht('Has Subprojects'), - pht( - 'This project has subprojects.')); - } - } - - $view->addProperty(pht('Subprojects'), $subproject_status); - - return $view; - } - private function buildCurtainView( PhabricatorProject $project, array $milestones, @@ -203,7 +135,7 @@ final class PhabricatorProjectSubprojectsController $allows_milestones = $project->supportsMilestones(); $allows_subprojects = $project->supportsSubprojects(); - $curtain = $this->newCurtainView($project); + $curtain = $this->newCurtainView(); if ($allows_milestones && $milestones) { $milestone_text = pht('Create Next Milestone'); @@ -244,6 +176,39 @@ final class PhabricatorProjectSubprojectsController ->setDisabled($subproject_disabled) ->setWorkflow($subproject_workflow)); + + if (!$project->supportsMilestones()) { + $note = pht( + 'This project is already a milestone, and milestones may not '. + 'have their own milestones.'); + } else { + if (!$milestones) { + $note = pht('Milestones can be created for this project.'); + } else { + $note = pht('This project has milestones.'); + } + } + + $curtain->newPanel() + ->setHeaderText(pht('Milestones')) + ->appendChild($note); + + if (!$project->supportsSubprojects()) { + $note = pht( + 'This project is a milestone, and milestones may not have '. + 'subprojects.'); + } else { + if (!$subprojects) { + $note = pht('Subprojects can be created for this project.'); + } else { + $note = pht('This project has subprojects.'); + } + } + + $curtain->newPanel() + ->setHeaderText(pht('Subprojects')) + ->appendChild($note); + return $curtain; } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index aa8c0852ab..2f86afc1ac 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -10,7 +10,7 @@ final class PhabricatorProjectTransactionEditor return $this; } - private function getIsMilestone() { + public function getIsMilestone() { return $this->isMilestone; } @@ -30,12 +30,6 @@ final class PhabricatorProjectTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; - $types[] = PhabricatorProjectTransaction::TYPE_NAME; - $types[] = PhabricatorProjectTransaction::TYPE_SLUGS; - $types[] = PhabricatorProjectTransaction::TYPE_STATUS; - $types[] = PhabricatorProjectTransaction::TYPE_IMAGE; - $types[] = PhabricatorProjectTransaction::TYPE_ICON; - $types[] = PhabricatorProjectTransaction::TYPE_COLOR; $types[] = PhabricatorProjectTransaction::TYPE_LOCKED; $types[] = PhabricatorProjectTransaction::TYPE_PARENT; $types[] = PhabricatorProjectTransaction::TYPE_MILESTONE; @@ -52,21 +46,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_NAME: - return $object->getName(); - case PhabricatorProjectTransaction::TYPE_SLUGS: - $slugs = $object->getSlugs(); - $slugs = mpull($slugs, 'getSlug', 'getSlug'); - unset($slugs[$object->getPrimarySlug()]); - return array_keys($slugs); - case PhabricatorProjectTransaction::TYPE_STATUS: - return $object->getStatus(); - case PhabricatorProjectTransaction::TYPE_IMAGE: - return $object->getProfileImagePHID(); - case PhabricatorProjectTransaction::TYPE_ICON: - return $object->getIcon(); - case PhabricatorProjectTransaction::TYPE_COLOR: - return $object->getColor(); case PhabricatorProjectTransaction::TYPE_LOCKED: return (int)$object->getIsMembershipLocked(); case PhabricatorProjectTransaction::TYPE_HASWORKBOARD: @@ -90,11 +69,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_NAME: - case PhabricatorProjectTransaction::TYPE_STATUS: - case PhabricatorProjectTransaction::TYPE_IMAGE: - case PhabricatorProjectTransaction::TYPE_ICON: - case PhabricatorProjectTransaction::TYPE_COLOR: case PhabricatorProjectTransaction::TYPE_LOCKED: case PhabricatorProjectTransaction::TYPE_PARENT: case PhabricatorProjectTransaction::TYPE_MILESTONE: @@ -109,8 +83,6 @@ final class PhabricatorProjectTransactionEditor return null; } return $value; - case PhabricatorProjectTransaction::TYPE_SLUGS: - return $this->normalizeSlugs($xaction->getNewValue()); } return parent::getCustomTransactionNewValue($object, $xaction); @@ -121,27 +93,6 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_NAME: - $name = $xaction->getNewValue(); - $object->setName($name); - if (!$this->getIsMilestone()) { - $object->setPrimarySlug(PhabricatorSlug::normalizeProjectSlug($name)); - } - return; - case PhabricatorProjectTransaction::TYPE_SLUGS: - return; - case PhabricatorProjectTransaction::TYPE_STATUS: - $object->setStatus($xaction->getNewValue()); - return; - case PhabricatorProjectTransaction::TYPE_IMAGE: - $object->setProfileImagePHID($xaction->getNewValue()); - return; - case PhabricatorProjectTransaction::TYPE_ICON: - $object->setIcon($xaction->getNewValue()); - return; - case PhabricatorProjectTransaction::TYPE_COLOR: - $object->setColor($xaction->getNewValue()); - return; case PhabricatorProjectTransaction::TYPE_LOCKED: $object->setIsMembershipLocked($xaction->getNewValue()); return; @@ -178,32 +129,6 @@ final class PhabricatorProjectTransactionEditor $new = $xaction->getNewValue(); switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_NAME: - // First, add the old name as a secondary slug; this is helpful - // for renames and generally a good thing to do. - if (!$this->getIsMilestone()) { - if ($old !== null) { - $this->addSlug($object, $old, false); - } - $this->addSlug($object, $new, false); - } - return; - case PhabricatorProjectTransaction::TYPE_SLUGS: - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - - foreach ($add as $slug) { - $this->addSlug($object, $slug, true); - } - - $this->removeSlugs($object, $rem); - return; - case PhabricatorProjectTransaction::TYPE_STATUS: - case PhabricatorProjectTransaction::TYPE_IMAGE: - case PhabricatorProjectTransaction::TYPE_ICON: - case PhabricatorProjectTransaction::TYPE_COLOR: case PhabricatorProjectTransaction::TYPE_LOCKED: case PhabricatorProjectTransaction::TYPE_PARENT: case PhabricatorProjectTransaction::TYPE_MILESTONE: @@ -299,118 +224,6 @@ final class PhabricatorProjectTransactionEditor $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { - case PhabricatorProjectTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Project name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - - if (!$xactions) { - break; - } - - if ($this->getIsMilestone()) { - break; - } - - $name = last($xactions)->getNewValue(); - - if (!PhabricatorSlug::isValidProjectSlug($name)) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Project names must contain at least one letter or number.'), - last($xactions)); - break; - } - - $slug = PhabricatorSlug::normalizeProjectSlug($name); - - $slug_used_already = id(new PhabricatorProjectSlug()) - ->loadOneWhere('slug = %s', $slug); - if ($slug_used_already && - $slug_used_already->getProjectPHID() != $object->getPHID()) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Duplicate'), - pht( - 'Project name generates the same hashtag ("%s") as another '. - 'existing project. Choose a unique name.', - '#'.$slug), - nonempty(last($xactions), null)); - $errors[] = $error; - } - break; - case PhabricatorProjectTransaction::TYPE_SLUGS: - if (!$xactions) { - break; - } - - $slug_xaction = last($xactions); - - $new = $slug_xaction->getNewValue(); - - $invalid = array(); - foreach ($new as $slug) { - if (!PhabricatorSlug::isValidProjectSlug($slug)) { - $invalid[] = $slug; - } - } - - if ($invalid) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Hashtags must contain at least one letter or number. %s '. - 'project hashtag(s) are invalid: %s.', - phutil_count($invalid), - implode(', ', $invalid)), - $slug_xaction); - break; - } - - $new = $this->normalizeSlugs($new); - - if ($new) { - $slugs_used_already = id(new PhabricatorProjectSlug()) - ->loadAllWhere('slug IN (%Ls)', $new); - } else { - // The project doesn't have any extra slugs. - $slugs_used_already = array(); - } - - $slugs_used_already = mgroup($slugs_used_already, 'getProjectPHID'); - foreach ($slugs_used_already as $project_phid => $used_slugs) { - if ($project_phid == $object->getPHID()) { - continue; - } - - $used_slug_strs = mpull($used_slugs, 'getSlug'); - - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - '%s project hashtag(s) are already used by other projects: %s.', - phutil_count($used_slug_strs), - implode(', ', $used_slug_strs)), - $slug_xaction); - $errors[] = $error; - } - - break; case PhabricatorProjectTransaction::TYPE_PARENT: case PhabricatorProjectTransaction::TYPE_MILESTONE: if (!$xactions) { @@ -497,11 +310,11 @@ final class PhabricatorProjectTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_NAME: - case PhabricatorProjectTransaction::TYPE_STATUS: - case PhabricatorProjectTransaction::TYPE_IMAGE: - case PhabricatorProjectTransaction::TYPE_ICON: - case PhabricatorProjectTransaction::TYPE_COLOR: + case PhabricatorProjectNameTransaction::TRANSACTIONTYPE: + case PhabricatorProjectStatusTransaction::TRANSACTIONTYPE: + case PhabricatorProjectImageTransaction::TRANSACTIONTYPE: + case PhabricatorProjectIconTransaction::TRANSACTIONTYPE: + case PhabricatorProjectColorTransaction::TRANSACTIONTYPE: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, @@ -638,22 +451,6 @@ final class PhabricatorProjectTransactionEditor return true; } - protected function extractFilePHIDsFromCustomTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_IMAGE: - $new = $xaction->getNewValue(); - if ($new) { - return array($new); - } - break; - } - - return parent::extractFilePHIDsFromCustomTransaction($object, $xaction); - } - protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { @@ -717,7 +514,7 @@ final class PhabricatorProjectTransactionEditor return parent::applyFinalEffects($object, $xactions); } - private function addSlug(PhabricatorProject $project, $slug, $force) { + public function addSlug(PhabricatorProject $project, $slug, $force) { $slug = PhabricatorSlug::normalizeProjectSlug($slug); $table = new PhabricatorProjectSlug(); $project_phid = $project->getPHID(); @@ -748,7 +545,7 @@ final class PhabricatorProjectTransactionEditor ->save(); } - private function removeSlugs(PhabricatorProject $project, array $slugs) { + public function removeSlugs(PhabricatorProject $project, array $slugs) { if (!$slugs) { return; } @@ -770,7 +567,7 @@ final class PhabricatorProjectTransactionEditor } } - private function normalizeSlugs(array $slugs) { + public function normalizeSlugs(array $slugs) { foreach ($slugs as $key => $slug) { $slugs[$key] = PhabricatorSlug::normalizeProjectSlug($slug); } diff --git a/src/applications/project/engine/PhabricatorProjectEditEngine.php b/src/applications/project/engine/PhabricatorProjectEditEngine.php index 286e9bab0f..cc377bed44 100644 --- a/src/applications/project/engine/PhabricatorProjectEditEngine.php +++ b/src/applications/project/engine/PhabricatorProjectEditEngine.php @@ -112,8 +112,8 @@ final class PhabricatorProjectEditEngine PhabricatorTransactions::TYPE_VIEW_POLICY, PhabricatorTransactions::TYPE_EDIT_POLICY, PhabricatorTransactions::TYPE_JOIN_POLICY, - PhabricatorProjectTransaction::TYPE_ICON, - PhabricatorProjectTransaction::TYPE_COLOR, + PhabricatorProjectIconTransaction::TRANSACTIONTYPE, + PhabricatorProjectColorTransaction::TRANSACTIONTYPE, ); $unavailable = array_fuse($unavailable); @@ -235,7 +235,7 @@ final class PhabricatorProjectEditEngine id(new PhabricatorTextEditField()) ->setKey('name') ->setLabel(pht('Name')) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setDescription(pht('Project name.')) ->setConduitDescription(pht('Rename the project')) @@ -244,7 +244,7 @@ final class PhabricatorProjectEditEngine id(new PhabricatorIconSetEditField()) ->setKey('icon') ->setLabel(pht('Icon')) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_ICON) + ->setTransactionType(PhabricatorProjectIconTransaction::TRANSACTIONTYPE) ->setIconSet(new PhabricatorProjectIconSet()) ->setDescription(pht('Project icon.')) ->setConduitDescription(pht('Change the project icon.')) @@ -253,7 +253,8 @@ final class PhabricatorProjectEditEngine id(new PhabricatorSelectEditField()) ->setKey('color') ->setLabel(pht('Color')) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_COLOR) + ->setTransactionType( + PhabricatorProjectColorTransaction::TRANSACTIONTYPE) ->setOptions(PhabricatorProjectIconSet::getColorMap()) ->setDescription(pht('Project tag color.')) ->setConduitDescription(pht('Change the project tag color.')) @@ -262,7 +263,8 @@ final class PhabricatorProjectEditEngine id(new PhabricatorStringListEditField()) ->setKey('slugs') ->setLabel(pht('Additional Hashtags')) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType( + PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setDescription(pht('Additional project slugs.')) ->setConduitDescription(pht('Change project slugs.')) ->setConduitTypeDescription(pht('New list of slugs.')) diff --git a/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php b/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php index 0fd5bd66e7..e29ee13f2d 100644 --- a/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php +++ b/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php @@ -16,11 +16,11 @@ final class PhabricatorProjectTestDataGenerator $xactions = array(); $xactions[] = $this->newTransaction( - PhabricatorProjectTransaction::TYPE_NAME, + PhabricatorProjectNameTransaction::TRANSACTIONTYPE, $this->newProjectTitle()); $xactions[] = $this->newTransaction( - PhabricatorProjectTransaction::TYPE_STATUS, + PhabricatorProjectStatusTransaction::TRANSACTIONTYPE, $this->newProjectStatus()); // Almost always make the author a member. diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index fb9a25eda6..b0d442ae57 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -1,14 +1,8 @@ getOldValue(); $new = $this->getNewValue(); @@ -44,31 +42,11 @@ final class PhabricatorProjectTransaction $rem = array_diff($old, $new); $req_phids = array_merge($add, $rem); break; - case self::TYPE_IMAGE: - $req_phids[] = $old; - $req_phids[] = $new; - break; } return array_merge($req_phids, parent::getRequiredHandlePHIDs()); } - public function getColor() { - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_STATUS: - if ($old == 0) { - return 'red'; - } else { - return 'green'; - } - } - return parent::getColor(); - } - public function shouldHide() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_EDGE: @@ -113,26 +91,14 @@ final class PhabricatorProjectTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_STATUS: - if ($old == 0) { - return 'fa-ban'; - } else { - return 'fa-check'; - } case self::TYPE_LOCKED: if ($new) { return 'fa-lock'; } else { return 'fa-unlock'; } - case self::TYPE_ICON: - return PhabricatorProjectIconSet::getIconIcon($new); - case self::TYPE_IMAGE: - return 'fa-photo'; case self::TYPE_MEMBERS: return 'fa-user'; - case self::TYPE_SLUGS: - return 'fa-tag'; } return parent::getIcon(); } @@ -149,68 +115,6 @@ final class PhabricatorProjectTransaction '%s created this project.', $this->renderHandleLink($author_phid)); - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this project.', - $author_handle); - } else { - return pht( - '%s renamed this project from "%s" to "%s".', - $author_handle, - $old, - $new); - } - break; - - case self::TYPE_STATUS: - if ($old == 0) { - return pht( - '%s archived this project.', - $author_handle); - } else { - return pht( - '%s activated this project.', - $author_handle); - } - break; - - case self::TYPE_IMAGE: - // TODO: Some day, it would be nice to show the images. - if (!$old) { - return pht( - "%s set this project's image to %s.", - $author_handle, - $this->renderHandleLink($new)); - } else if (!$new) { - return pht( - "%s removed this project's image.", - $author_handle); - } else { - return pht( - "%s updated this project's image from %s to %s.", - $author_handle, - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - break; - - case self::TYPE_ICON: - $set = new PhabricatorProjectIconSet(); - - return pht( - "%s set this project's icon to %s.", - $author_handle, - $set->getIconLabel($new)); - break; - - case self::TYPE_COLOR: - return pht( - "%s set this project's color to %s.", - $author_handle, - PHUITagView::getShadeName($new)); - break; - case self::TYPE_LOCKED: if ($new) { return pht( @@ -223,33 +127,6 @@ final class PhabricatorProjectTransaction } break; - case self::TYPE_SLUGS: - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - - if ($add && $rem) { - return pht( - '%s changed project hashtag(s), added %d: %s; removed %d: %s.', - $author_handle, - count($add), - $this->renderSlugList($add), - count($rem), - $this->renderSlugList($rem)); - } else if ($add) { - return pht( - '%s added %d project hashtag(s): %s.', - $author_handle, - count($add), - $this->renderSlugList($add)); - } else if ($rem) { - return pht( - '%s removed %d project hashtag(s): %s.', - $author_handle, - count($rem), - $this->renderSlugList($rem)); - } - break; - case self::TYPE_MEMBERS: $add = array_diff($new, $old); $rem = array_diff($old, $new); @@ -329,112 +206,18 @@ final class PhabricatorProjectTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created %s.', - $author_handle, - $object_handle); - } else { - return pht( - '%s renamed %s from "%s" to "%s".', - $author_handle, - $object_handle, - $old, - $new); - } - case self::TYPE_STATUS: - if ($old == 0) { - return pht( - '%s archived %s.', - $author_handle, - $object_handle); - } else { - return pht( - '%s activated %s.', - $author_handle, - $object_handle); - } - case self::TYPE_IMAGE: - // TODO: Some day, it would be nice to show the images. - if (!$old) { - return pht( - '%s set the image for %s to %s.', - $author_handle, - $object_handle, - $this->renderHandleLink($new)); - } else if (!$new) { - return pht( - '%s removed the image for %s.', - $author_handle, - $object_handle); - } else { - return pht( - '%s updated the image for %s from %s to %s.', - $author_handle, - $object_handle, - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - - case self::TYPE_ICON: - $set = new PhabricatorProjectIconSet(); - - return pht( - '%s set the icon for %s to %s.', - $author_handle, - $object_handle, - $set->getIconLabel($new)); - - case self::TYPE_COLOR: - return pht( - '%s set the color for %s to %s.', - $author_handle, - $object_handle, - PHUITagView::getShadeName($new)); - case self::TYPE_LOCKED: if ($new) { return pht( - '%s locked %s membership.', + '%s locked membership for %s.', $author_handle, $object_handle); } else { return pht( - '%s unlocked %s membership.', + '%s unlocked membership for %s.', $author_handle, $object_handle); } - - case self::TYPE_SLUGS: - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - - if ($add && $rem) { - return pht( - '%s changed %s hashtag(s), added %d: %s; removed %d: %s.', - $author_handle, - $object_handle, - count($add), - $this->renderSlugList($add), - count($rem), - $this->renderSlugList($rem)); - } else if ($add) { - return pht( - '%s added %d %s hashtag(s): %s.', - $author_handle, - count($add), - $object_handle, - $this->renderSlugList($add)); - } else if ($rem) { - return pht( - '%s removed %d %s hashtag(s): %s.', - $author_handle, - count($rem), - $object_handle, - $this->renderSlugList($rem)); - } - } return parent::getTitleForFeed(); @@ -443,11 +226,11 @@ final class PhabricatorProjectTransaction public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { - case self::TYPE_NAME: - case self::TYPE_SLUGS: - case self::TYPE_IMAGE: - case self::TYPE_ICON: - case self::TYPE_COLOR: + case PhabricatorProjectNameTransaction::TRANSACTIONTYPE: + case PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE: + case PhabricatorProjectImageTransaction::TRANSACTIONTYPE: + case PhabricatorProjectIconTransaction::TRANSACTIONTYPE: + case PhabricatorProjectColorTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_METADATA; break; case PhabricatorTransactions::TYPE_EDGE: @@ -463,7 +246,7 @@ final class PhabricatorProjectTransaction $tags[] = self::MAILTAG_OTHER; } break; - case self::TYPE_STATUS: + case PhabricatorProjectStatusTransaction::TRANSACTIONTYPE: case self::TYPE_LOCKED: default: $tags[] = self::MAILTAG_OTHER; @@ -472,8 +255,4 @@ final class PhabricatorProjectTransaction return $tags; } - private function renderSlugList($slugs) { - return implode(', ', $slugs); - } - } diff --git a/src/applications/project/view/PhabricatorProjectListView.php b/src/applications/project/view/PhabricatorProjectListView.php index 994e78ce76..7496a401bb 100644 --- a/src/applications/project/view/PhabricatorProjectListView.php +++ b/src/applications/project/view/PhabricatorProjectListView.php @@ -5,6 +5,7 @@ final class PhabricatorProjectListView extends AphrontView { private $projects; private $showMember; private $showWatching; + private $noDataString; public function setProjects(array $projects) { $this->projects = $projects; @@ -25,6 +26,11 @@ final class PhabricatorProjectListView extends AphrontView { return $this; } + public function setNoDataString($text) { + $this->noDataString = $text; + return $this; + } + public function renderList() { $viewer = $this->getUser(); $viewer_phid = $viewer->getPHID(); @@ -32,8 +38,14 @@ final class PhabricatorProjectListView extends AphrontView { $handles = $viewer->loadHandles(mpull($projects, 'getPHID')); + $no_data = pht('No projects found.'); + if ($this->noDataString) { + $no_data = $this->noDataString; + } + $list = id(new PHUIObjectItemListView()) - ->setUser($viewer); + ->setUser($viewer) + ->setNoDataString($no_data); foreach ($projects as $key => $project) { $id = $project->getID(); @@ -56,7 +68,7 @@ final class PhabricatorProjectListView extends AphrontView { )); if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED) { - $item->addIcon('delete-grey', pht('Archived')); + $item->addIcon('fa-ban', pht('Archived')); $item->setDisabled(true); } diff --git a/src/applications/project/view/PhabricatorProjectMemberListView.php b/src/applications/project/view/PhabricatorProjectMemberListView.php index 12b7cc7a76..cf8a3a1465 100644 --- a/src/applications/project/view/PhabricatorProjectMemberListView.php +++ b/src/applications/project/view/PhabricatorProjectMemberListView.php @@ -4,7 +4,7 @@ final class PhabricatorProjectMemberListView extends PhabricatorProjectUserListView { protected function canEditList() { - $viewer = $this->getUser(); + $viewer = $this->getViewer(); $project = $this->getProject(); if (!$project->supportsEditMembers()) { @@ -31,4 +31,35 @@ final class PhabricatorProjectMemberListView return pht('Members'); } + protected function getMembershipNote() { + $viewer = $this->getViewer(); + $viewer_phid = $viewer->getPHID(); + $project = $this->getProject(); + + if (!$viewer_phid) { + return null; + } + + $note = null; + if ($project->isUserMember($viewer_phid)) { + $edge_type = PhabricatorProjectSilencedEdgeType::EDGECONST; + $silenced = PhabricatorEdgeQuery::loadDestinationPHIDs( + $project->getPHID(), + $edge_type); + $silenced = array_fuse($silenced); + $is_silenced = isset($silenced[$viewer_phid]); + if ($is_silenced) { + $note = pht( + 'You have disabled mail. When mail is sent to project members, '. + 'you will not receive a copy.'); + } else { + $note = pht( + 'You are a member and you will receive mail that is sent to all '. + 'project members.'); + } + } + + return $note; + } + } diff --git a/src/applications/project/view/PhabricatorProjectUserListView.php b/src/applications/project/view/PhabricatorProjectUserListView.php index d590cbb559..51c2ced6d1 100644 --- a/src/applications/project/view/PhabricatorProjectUserListView.php +++ b/src/applications/project/view/PhabricatorProjectUserListView.php @@ -6,6 +6,7 @@ abstract class PhabricatorProjectUserListView extends AphrontView { private $userPHIDs; private $limit; private $background; + private $showNote; public function setProject(PhabricatorProject $project) { $this->project = $project; @@ -39,10 +40,16 @@ abstract class PhabricatorProjectUserListView extends AphrontView { return $this; } + public function setShowNote($show) { + $this->showNote = $show; + return $this; + } + abstract protected function canEditList(); abstract protected function getNoDataString(); abstract protected function getRemoveURI($phid); abstract protected function getHeaderText(); + abstract protected function getMembershipNote(); public function render() { $viewer = $this->getViewer(); @@ -135,6 +142,15 @@ abstract class PhabricatorProjectUserListView extends AphrontView { ->setHeader($header) ->setObjectList($list); + if ($this->showNote) { + if ($this->getMembershipNote()) { + $info = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_PLAIN) + ->appendChild($this->getMembershipNote()); + $box->setInfoView($info); + } + } + if ($this->background) { $box->setBackground($this->background); } diff --git a/src/applications/project/view/PhabricatorProjectWatcherListView.php b/src/applications/project/view/PhabricatorProjectWatcherListView.php index 7aa9638afc..2d16cc7ec2 100644 --- a/src/applications/project/view/PhabricatorProjectWatcherListView.php +++ b/src/applications/project/view/PhabricatorProjectWatcherListView.php @@ -4,7 +4,7 @@ final class PhabricatorProjectWatcherListView extends PhabricatorProjectUserListView { protected function canEditList() { - $viewer = $this->getUser(); + $viewer = $this->getViewer(); $project = $this->getProject(); return PhabricatorPolicyFilter::hasCapability( @@ -27,4 +27,17 @@ final class PhabricatorProjectWatcherListView return pht('Watchers'); } + protected function getMembershipNote() { + $viewer = $this->getViewer(); + $viewer_phid = $viewer->getPHID(); + $project = $this->getProject(); + + $note = null; + if ($project->isUserWatcher($viewer_phid)) { + $note = pht('You are watching this project and will receive mail about '. + 'changes made to any related object.'); + } + return $note; + } + } diff --git a/src/applications/project/xaction/PhabricatorProjectColorTransaction.php b/src/applications/project/xaction/PhabricatorProjectColorTransaction.php new file mode 100644 index 0000000000..9f36179b12 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectColorTransaction.php @@ -0,0 +1,33 @@ +getColor(); + } + + public function applyInternalEffects($object, $value) { + $object->setColor($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + return pht( + "%s set this project's color to %s.", + $this->renderAuthor(), + $this->renderValue(PHUITagView::getShadeName($new))); + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + return pht( + '%s set the color for %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue(PHUITagView::getShadeName($new))); + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectIconTransaction.php b/src/applications/project/xaction/PhabricatorProjectIconTransaction.php new file mode 100644 index 0000000000..932ce4bdc4 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectIconTransaction.php @@ -0,0 +1,42 @@ +getIcon(); + } + + public function applyInternalEffects($object, $value) { + $object->setIcon($value); + } + + public function getTitle() { + $set = new PhabricatorProjectIconSet(); + $new = $this->getNewValue(); + + return pht( + "%s set this project's icon to %s.", + $this->renderAuthor(), + $this->renderValue($set->getIconLabel($new))); + } + + public function getTitleForFeed() { + $set = new PhabricatorProjectIconSet(); + $new = $this->getNewValue(); + + return pht( + '%s set the icon for %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($set->getIconLabel($new))); + } + + public function getIcon() { + $new = $this->getNewValue(); + return PhabricatorProjectIconSet::getIconIcon($new); + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectImageTransaction.php b/src/applications/project/xaction/PhabricatorProjectImageTransaction.php new file mode 100644 index 0000000000..f6d10f0961 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectImageTransaction.php @@ -0,0 +1,136 @@ +getProfileImagePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setProfileImagePHID($value); + } + + public function applyExternalEffects($object, $value) { + $old = $this->getOldValue(); + $new = $value; + $all = array(); + if ($old) { + $all[] = $old; + } + if ($new) { + $all[] = $new; + } + + $files = id(new PhabricatorFileQuery()) + ->setViewer($this->getActor()) + ->withPHIDs($all) + ->execute(); + $files = mpull($files, null, 'getPHID'); + + $old_file = idx($files, $old); + if ($old_file) { + $old_file->detachFromObject($object->getPHID()); + } + + $new_file = idx($files, $new); + if ($new_file) { + $new_file->attachToObject($object->getPHID()); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + // TODO: Some day, it would be nice to show the images. + if (!$old) { + return pht( + "%s set this project's image to %s.", + $this->renderAuthor(), + $this->renderNewHandle()); + } else if (!$new) { + return pht( + "%s removed this project's image.", + $this->renderAuthor()); + } else { + return pht( + "%s updated this project's image from %s to %s.", + $this->renderAuthor(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + // TODO: Some day, it would be nice to show the images. + if (!$old) { + return pht( + '%s set the image for %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderNewHandle()); + } else if (!$new) { + return pht( + '%s removed the image for %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s updated the image for %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + } + + public function getIcon() { + return 'fa-photo'; + } + + public function extractFilePHIDs($object, $value) { + if ($value) { + return array($value); + } + return array(); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + $viewer = $this->getActor(); + + foreach ($xactions as $xaction) { + $file_phid = $xaction->getNewValue(); + + // Only validate if file was uploaded + if ($file_phid) { + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + + if (!$file) { + $errors[] = $this->newInvalidError( + pht('"%s" is not a valid file PHID.', + $file_phid)); + } else { + if (!$file->isViewableImage()) { + $mime_type = $file->getMimeType(); + $errors[] = $this->newInvalidError( + pht('File mime type of "%s" is not a valid viewable image.', + $mime_type)); + } + } + } + } + + return $errors; + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectNameTransaction.php b/src/applications/project/xaction/PhabricatorProjectNameTransaction.php new file mode 100644 index 0000000000..512d899f0b --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectNameTransaction.php @@ -0,0 +1,112 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + if (!$this->getEditor()->getIsMilestone()) { + $object->setPrimarySlug(PhabricatorSlug::normalizeProjectSlug($value)); + } + } + + public function applyExternalEffects($object, $value) { + $old = $this->getOldValue(); + + // First, add the old name as a secondary slug; this is helpful + // for renames and generally a good thing to do. + if (!$this->getEditor()->getIsMilestone()) { + if ($old !== null) { + $this->getEditor()->addSlug($object, $old, false); + } + $this->getEditor()->addSlug($object, $value, false); + } + return; + } + + public function getTitle() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s created this project.', + $this->renderAuthor()); + } else { + return pht( + '%s renamed this project from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError(pht('Projects must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Project names must not be longer than %s character(s).', + new PhutilNumber($max_length))); + } + } + + if ($this->getEditor()->getIsMilestone() || !$xactions) { + return $errors; + } + + $name = last($xactions)->getNewValue(); + + if (!PhabricatorSlug::isValidProjectSlug($name)) { + $errors[] = $this->newInvalidError( + pht('Project names must contain at least one letter or number.')); + } + + $slug = PhabricatorSlug::normalizeProjectSlug($name); + + $slug_used_already = id(new PhabricatorProjectSlug()) + ->loadOneWhere('slug = %s', $slug); + if ($slug_used_already && + $slug_used_already->getProjectPHID() != $object->getPHID()) { + + $errors[] = $this->newInvalidError( + pht( + 'Project name generates the same hashtag ("%s") as another '. + 'existing project. Choose a unique name.', + '#'.$slug)); + } + + return $errors; + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php b/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php new file mode 100644 index 0000000000..e1f22b3746 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php @@ -0,0 +1,174 @@ +getSlugs(); + $slugs = mpull($slugs, 'getSlug', 'getSlug'); + unset($slugs[$object->getPrimarySlug()]); + return array_keys($slugs); + } + + public function generateNewValue($object, $value) { + return $this->getEditor()->normalizeSlugs($value); + } + + public function applyInternalEffects($object, $value) { + return; + } + + public function applyExternalEffects($object, $value) { + $old = $this->getOldValue(); + $new = $value; + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + foreach ($add as $slug) { + $this->getEditor()->addSlug($object, $slug, true); + } + + $this->getEditor()->removeSlugs($object, $rem); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + $add = $this->renderHashtags($add); + $rem = $this->renderHashtags($rem); + + if ($add && $rem) { + return pht( + '%s changed project hashtag(s), added %d: %s; removed %d: %s.', + $this->renderAuthor(), + count($add), + $this->renderValueList($add), + count($rem), + $this->renderValueList($rem)); + } else if ($add) { + return pht( + '%s added %d project hashtag(s): %s.', + $this->renderAuthor(), + count($add), + $this->renderValueList($add)); + } else if ($rem) { + return pht( + '%s removed %d project hashtag(s): %s.', + $this->renderAuthor(), + count($rem), + $this->renderValueList($rem)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + $add = $this->renderHashtags($add); + $rem = $this->renderHashtags($rem); + + if ($add && $rem) { + return pht( + '%s changed %s hashtag(s), added %d: %s; removed %d: %s.', + $this->renderAuthor(), + $this->renderObject(), + count($add), + $this->renderValueList($add), + count($rem), + $this->renderValueList($rem)); + } else if ($add) { + return pht( + '%s added %d %s hashtag(s): %s.', + $this->renderAuthor(), + count($add), + $this->renderObject(), + $this->renderValueList($add)); + } else if ($rem) { + return pht( + '%s removed %d %s hashtag(s): %s.', + $this->renderAuthor(), + count($rem), + $this->renderObject(), + $this->renderValueList($rem)); + } + } + + public function getIcon() { + return 'fa-tag'; + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if (!$xactions) { + return $errors; + } + + $slug_xaction = last($xactions); + + $new = $slug_xaction->getNewValue(); + + $invalid = array(); + foreach ($new as $slug) { + if (!PhabricatorSlug::isValidProjectSlug($slug)) { + $invalid[] = $slug; + } + } + + if ($invalid) { + $errors[] = $this->newInvalidError( + pht( + 'Hashtags must contain at least one letter or number. %s '. + 'project hashtag(s) are invalid: %s.', + phutil_count($invalid), + implode(', ', $invalid))); + + return $errors; + } + + $new = $this->getEditor()->normalizeSlugs($new); + + if ($new) { + $slugs_used_already = id(new PhabricatorProjectSlug()) + ->loadAllWhere('slug IN (%Ls)', $new); + } else { + // The project doesn't have any extra slugs. + $slugs_used_already = array(); + } + + $slugs_used_already = mgroup($slugs_used_already, 'getProjectPHID'); + foreach ($slugs_used_already as $project_phid => $used_slugs) { + if ($project_phid == $object->getPHID()) { + continue; + } + + $used_slug_strs = mpull($used_slugs, 'getSlug'); + + $errors[] = $this->newInvalidError( + pht( + '%s project hashtag(s) are already used by other projects: %s.', + phutil_count($used_slug_strs), + implode(', ', $used_slug_strs))); + } + + return $errors; + } + + private function renderHashtags(array $tags) { + $result = array(); + foreach ($tags as $tag) { + $result[] = '#'.$tag; + } + return $result; + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectStatusTransaction.php b/src/applications/project/xaction/PhabricatorProjectStatusTransaction.php new file mode 100644 index 0000000000..e5c8ead4f4 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectStatusTransaction.php @@ -0,0 +1,66 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + + if ($old == 0) { + return pht( + '%s archived this project.', + $this->renderAuthor()); + } else { + return pht( + '%s activated this project.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + + if ($old == 0) { + return pht( + '%s archived %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s activated %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getColor() { + $old = $this->getOldValue(); + + if ($old == 0) { + return 'red'; + } else { + return 'green'; + } + } + + public function getIcon() { + $old = $this->getOldValue(); + + if ($old == 0) { + return 'fa-ban'; + } else { + return 'fa-check'; + } + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectTransactionType.php b/src/applications/project/xaction/PhabricatorProjectTransactionType.php new file mode 100644 index 0000000000..9ce02dc450 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectTransactionType.php @@ -0,0 +1,4 @@ +getStatus() != $status) { $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setTransactionType( + ManiphestTaskStatusTransaction::TRANSACTIONTYPE) ->setMetadataValue('commitPHID', $commit->getPHID()) ->setNewValue($status); diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index b5aa399521..07f85407fe 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -592,6 +592,8 @@ abstract class PhabricatorApplicationTransactionEditor $xtype = $this->getModularTransactionType($type); if ($xtype) { + $xtype = clone $xtype; + $xtype->setStorage($xaction); return $xtype->applyExternalEffects($object, $xaction->getNewValue()); } @@ -1743,7 +1745,7 @@ abstract class PhabricatorApplicationTransactionEditor return array_values($result); } - protected function mergePHIDOrEdgeTransactions( + public function mergePHIDOrEdgeTransactions( PhabricatorApplicationTransaction $u, PhabricatorApplicationTransaction $v) { diff --git a/src/applications/transactions/storage/PhabricatorModularTransaction.php b/src/applications/transactions/storage/PhabricatorModularTransaction.php index 396478c98b..9f3c24b946 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransaction.php +++ b/src/applications/transactions/storage/PhabricatorModularTransaction.php @@ -160,7 +160,7 @@ abstract class PhabricatorModularTransaction return parent::attachViewer($viewer); } - final public function hasChangeDetails() { + /* final */ public function hasChangeDetails() { if ($this->getTransactionImplementation()->hasChangeDetailView()) { return true; } @@ -168,7 +168,7 @@ abstract class PhabricatorModularTransaction return parent::hasChangeDetails(); } - final public function renderChangeDetails(PhabricatorUser $viewer) { + /* final */ public function renderChangeDetails(PhabricatorUser $viewer) { $impl = $this->getTransactionImplementation(); $impl->setViewer($viewer); $view = $impl->newChangeDetailView(); diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index 8a56e8e8ce..128b5c7c19 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -208,6 +208,19 @@ abstract class PhabricatorModularTransactionType $value); } + final protected function renderValueList(array $values) { + $result = array(); + foreach ($values as $value) { + $result[] = $this->renderValue($value); + } + + if ($this->isTextMode()) { + return implode(', ', $result); + } + + return phutil_implode_html(', ', $result); + } + final protected function renderOldValue() { return $this->renderValue($this->getOldValue()); } diff --git a/src/applications/uiexample/examples/PHUITimelineExample.php b/src/applications/uiexample/examples/PHUITimelineExample.php index 9bedffd9eb..42c680ea7b 100644 --- a/src/applications/uiexample/examples/PHUITimelineExample.php +++ b/src/applications/uiexample/examples/PHUITimelineExample.php @@ -80,13 +80,35 @@ final class PHUITimelineExample extends PhabricatorUIExample { ->setTitle(pht('Minor Red Event')) ->setColor(PhabricatorTransactions::COLOR_RED); + // Pinboard!! + $pin1 = id(new PHUIPinboardItemView()) + ->setUser($user) + ->setHeader('user0.png') + ->setImageURI(celerity_get_resource_uri('/rsrc/image/people/user0.png')) + ->setURI(celerity_get_resource_uri('/rsrc/image/people/user0.png')) + ->setImageSize(280, 210); + + $pin2 = id(new PHUIPinboardItemView()) + ->setUser($user) + ->setHeader('user1.png') + ->setImageURI(celerity_get_resource_uri('/rsrc/image/people/user1.png')) + ->setURI(celerity_get_resource_uri('/rsrc/image/people/user1.png')) + ->setImageSize(280, 210); + + $pin3 = id(new PHUIPinboardItemView()) + ->setUser($user) + ->setHeader('user2.png') + ->setImageURI(celerity_get_resource_uri('/rsrc/image/people/user2.png')) + ->setURI(celerity_get_resource_uri('/rsrc/image/people/user1.png')) + ->setImageSize(280, 210); + $events[] = id(new PHUITimelineEventView()) ->setUserHandle($handle) - ->setIcon('fa-check') - ->setTitle(pht('Historically Important Action')) - ->setColor(PhabricatorTransactions::COLOR_BLACK) - ->setReallyMajorEvent(true); - + ->setIcon('fa-camera-retro') + ->setTitle(pht('Pinboard Image Event')) + ->addPinboardItem($pin1) + ->addPinboardItem($pin2) + ->addPinboardItem($pin3); $events[] = id(new PHUITimelineEventView()) ->setUserHandle($handle) @@ -102,12 +124,6 @@ final class PHUITimelineExample extends PhabricatorUIExample { ->appendChild(str_repeat(pht('Long Text Body').' ', 64)) ->setColor(PhabricatorTransactions::COLOR_ORANGE); - $events[] = id(new PHUITimelineEventView()) - ->setUserHandle($handle) - ->setTitle(str_repeat('LongTextEventNoSpaces', 1024)) - ->appendChild(str_repeat('LongTextNoSpaces', 1024)) - ->setColor(PhabricatorTransactions::COLOR_RED); - $colors = array( PhabricatorTransactions::COLOR_RED, PhabricatorTransactions::COLOR_ORANGE, diff --git a/src/infrastructure/diff/PhabricatorInlineCommentController.php b/src/infrastructure/diff/PhabricatorInlineCommentController.php index 1c92b167b0..d7f1a9dd3a 100644 --- a/src/infrastructure/diff/PhabricatorInlineCommentController.php +++ b/src/infrastructure/diff/PhabricatorInlineCommentController.php @@ -94,19 +94,6 @@ abstract class PhabricatorInlineCommentController $op = $this->getOperation(); switch ($op) { - case 'busy': - if ($request->isFormPost()) { - return new AphrontAjaxResponse(); - } - - return $this->newDialog() - ->setTitle(pht('Already Editing')) - ->appendParagraph( - pht( - 'You are already editing an inline comment. Finish editing '. - 'your current comment before adding new comments.')) - ->addCancelButton('/') - ->addSubmitButton(pht('Jump to Inline')); case 'hide': case 'show': if (!$request->validateCSRF()) { @@ -130,12 +117,14 @@ abstract class PhabricatorInlineCommentController $inline = $this->loadCommentForDone($this->getCommentID()); $is_draft_state = false; + $is_checked = false; switch ($inline->getFixedState()) { case PhabricatorInlineCommentInterface::STATE_DRAFT: $next_state = PhabricatorInlineCommentInterface::STATE_UNDONE; break; case PhabricatorInlineCommentInterface::STATE_UNDRAFT: $next_state = PhabricatorInlineCommentInterface::STATE_DONE; + $is_checked = true; break; case PhabricatorInlineCommentInterface::STATE_DONE: $next_state = PhabricatorInlineCommentInterface::STATE_UNDRAFT; @@ -145,6 +134,7 @@ abstract class PhabricatorInlineCommentController case PhabricatorInlineCommentInterface::STATE_UNDONE: $next_state = PhabricatorInlineCommentInterface::STATE_DRAFT; $is_draft_state = true; + $is_checked = true; break; } @@ -153,6 +143,7 @@ abstract class PhabricatorInlineCommentController return id(new AphrontAjaxResponse()) ->setContent( array( + 'isChecked' => $is_checked, 'draftState' => $is_draft_state, )); case 'delete': diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index f01bfbdb65..9fc1e1bded 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -99,6 +99,14 @@ final class PHUIDiffInlineCommentDetailView 'differential-inline-comment', ); + $is_fixed = false; + switch ($inline->getFixedState()) { + case PhabricatorInlineCommentInterface::STATE_DONE: + case PhabricatorInlineCommentInterface::STATE_DRAFT: + $is_fixed = true; + break; + } + $metadata = array( 'id' => $inline->getID(), 'phid' => $inline->getPHID(), @@ -109,6 +117,9 @@ final class PHUIDiffInlineCommentDetailView 'on_right' => $this->getIsOnRight(), 'original' => $inline->getContent(), 'replyToCommentPHID' => $inline->getReplyToCommentPHID(), + 'isDraft' => $inline->isDraft(), + 'isFixed' => $is_fixed, + 'isGhost' => $inline->getIsGhost(), ); $sigil = 'differential-inline-comment'; @@ -190,61 +201,31 @@ final class PHUIDiffInlineCommentDetailView } } - $nextprev = null; - if (!$this->preview) { - $nextprev = new PHUIButtonBarView(); - $nextprev->setBorderless(true); - $nextprev->addClass('inline-button-divider'); - - - $up = id(new PHUIButtonView()) - ->setTag('a') - ->setTooltip(pht('Previous')) - ->setIcon('fa-chevron-up') - ->addSigil('differential-inline-prev') - ->setMustCapture(true); - - $down = id(new PHUIButtonView()) - ->setTag('a') - ->setTooltip(pht('Next')) - ->setIcon('fa-chevron-down') - ->addSigil('differential-inline-next') - ->setMustCapture(true); - - if ($this->canHide()) { - $hide = id(new PHUIButtonView()) - ->setTag('a') - ->setTooltip(pht('Hide Comment')) - ->setIcon('fa-times') - ->addSigil('hide-inline') - ->setMustCapture(true); - - $nextprev->addButton($hide); - } - - $nextprev->addButton($up); - $nextprev->addButton($down); - - $action_buttons = array(); - if ($this->allowReply) { - if (!$is_synthetic) { - // NOTE: No product reason why you can't reply to these, but the reply - // mechanism currently sends the inline comment ID to the server, not - // file/line information, and synthetic comments don't have an inline - // comment ID. - - $action_buttons[] = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-reply') - ->setTooltip(pht('Reply')) - ->addSigil('differential-inline-reply') - ->setMustCapture(true); - } - } - } - $anchor_name = $this->getAnchorName(); + $action_buttons = array(); + + $can_reply = + (!$this->editable) && + (!$this->preview) && + ($this->allowReply) && + + // NOTE: No product reason why you can't reply to synthetic comments, + // but the reply mechanism currently sends the inline comment ID to the + // server, not file/line information, and synthetic comments don't have + // an inline comment ID. + (!$is_synthetic); + + + if ($can_reply) { + $action_buttons[] = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-reply') + ->setTooltip(pht('Reply')) + ->addSigil('differential-inline-reply') + ->setMustCapture(true); + } + if ($this->editable && !$this->preview) { $action_buttons[] = id(new PHUIButtonView()) ->setTag('a') @@ -280,6 +261,15 @@ final class PHUIDiffInlineCommentDetailView ->setMustCapture(true); } + if (!$this->preview && $this->canHide()) { + $action_buttons[] = id(new PHUIButtonView()) + ->setTag('a') + ->setTooltip(pht('Hide Comment')) + ->setIcon('fa-times') + ->addSigil('hide-inline') + ->setMustCapture(true); + } + $done_button = null; if (!$is_synthetic) { @@ -426,11 +416,9 @@ final class PHUIDiffInlineCommentDetailView 'class' => 'inline-head-right', ), array( - $anchor, $done_button, $links, $actions, - $nextprev, )); $markup = javelin_tag( @@ -441,16 +429,41 @@ final class PHUIDiffInlineCommentDetailView 'meta' => $metadata, ), array( - phutil_tag_div('differential-inline-comment-head grouped', array( - $group_left, - $group_right, - )), + javelin_tag( + 'div', + array( + 'class' => 'differential-inline-comment-head grouped', + 'sigil' => 'differential-inline-header', + ), + array( + $group_left, + $group_right, + )), phutil_tag_div( 'differential-inline-comment-content', phutil_tag_div('phabricator-remarkup', $content)), )); - return $markup; + $snippet = id(new PhutilUTF8StringTruncator()) + ->setMaximumGlyphs(96) + ->truncateString($inline->getContent()); + + $summary = phutil_tag( + 'div', + array( + 'class' => 'differential-inline-summary', + ), + array( + phutil_tag('strong', array(), pht('%s:', $author)), + ' ', + $snippet, + )); + + return array( + $anchor, + $markup, + $summary, + ); } private function canHide() { diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php index b0c0b4ccac..7c6b1c54b5 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php @@ -21,21 +21,28 @@ abstract class PHUIDiffInlineCommentRowScaffold extends AphrontView { } protected function getRowAttributes() { - // TODO: This is semantic information used by the JS when placing comments - // and using keyboard navigation; we should move it out of class names. - - $style = null; + $is_hidden = false; foreach ($this->getInlineViews() as $view) { if ($view->isHidden()) { - $style = 'display: none'; + $is_hidden = true; } } - return array( - 'class' => 'inline', + $classes = array(); + $classes[] = 'inline'; + if ($is_hidden) { + $classes[] = 'inline-hidden'; + } + + $result = array( + 'class' => implode(' ', $classes), 'sigil' => 'inline-row', - 'style' => $style, + 'meta' => array( + 'hidden' => $is_hidden, + ), ); + + return $result; } } diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php index c4bdd65bf8..4abdb00e0b 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php @@ -9,6 +9,10 @@ final class PHUIDiffInlineCommentUndoView extends PHUIDiffInlineCommentView { + public function isHideable() { + return false; + } + public function render() { $link = javelin_tag( 'a', diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php index b62160e232..e2c89e238c 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php @@ -21,4 +21,16 @@ abstract class PHUIDiffInlineCommentView extends AphrontView { return false; } + public function isHideable() { + return true; + } + + public function newHiddenIcon() { + if ($this->isHideable()) { + return new PHUIDiffRevealIconView(); + } else { + return null; + } + } + } diff --git a/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php b/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php index 708b70b360..53c2255dc8 100644 --- a/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php +++ b/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php @@ -22,9 +22,17 @@ final class PHUIDiffOneUpInlineCommentRowScaffold 'id' => $inline->getScaffoldCellID(), ); + if ($inline->getIsOnRight()) { + $left_hidden = null; + $right_hidden = $inline->newHiddenIcon(); + } else { + $left_hidden = $inline->newHiddenIcon(); + $right_hidden = null; + } + $cells = array( - phutil_tag('th', array()), - phutil_tag('th', array()), + phutil_tag('th', array(), $left_hidden), + phutil_tag('th', array(), $right_hidden), phutil_tag('td', $attrs, $inline), ); diff --git a/src/infrastructure/diff/view/PHUIDiffRevealIconView.php b/src/infrastructure/diff/view/PHUIDiffRevealIconView.php index 284b72b2be..8ca3eae2c1 100644 --- a/src/infrastructure/diff/view/PHUIDiffRevealIconView.php +++ b/src/infrastructure/diff/view/PHUIDiffRevealIconView.php @@ -8,7 +8,7 @@ final class PHUIDiffRevealIconView extends AphrontView { ->addSigil('has-tooltip') ->setMetadata( array( - 'tip' => pht('Show Hidden Comments'), + 'tip' => pht('Show Hidden Comment'), 'align' => 'E', 'size' => 275, )); @@ -17,8 +17,8 @@ final class PHUIDiffRevealIconView extends AphrontView { 'a', array( 'href' => '#', - 'class' => 'reveal-inlines', - 'sigil' => 'reveal-inlines', + 'class' => 'reveal-inline', + 'sigil' => 'reveal-inline', 'mustcapture' => true, ), $icon); diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php index e6ca6cc53d..47c8f633e7 100644 --- a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php @@ -103,22 +103,6 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { } } - $reveal_link = javelin_tag( - 'a', - array( - 'sigil' => 'differential-reveal-all', - 'mustcapture' => true, - 'class' => 'button differential-toc-reveal-all', - ), - pht('Show All Context')); - - $buttons = phutil_tag( - 'div', - array( - 'class' => 'differential-toc-buttons grouped', - ), - $reveal_link); - $table = id(new AphrontTableView($rows)) ->setRowClasses($rowc) ->setHeaders( @@ -185,8 +169,7 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { ->setHeader($header) ->setBackground($this->background) ->setTable($table) - ->appendChild($anchor) - ->appendChild($buttons); + ->appendChild($anchor); if ($this->infoView) { $box->setInfoView($this->infoView); diff --git a/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php b/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php index 4fac5088d1..81b0edaf49 100644 --- a/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php +++ b/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php @@ -27,9 +27,15 @@ final class PHUIDiffTwoUpInlineCommentRowScaffold if ($inline->getIsOnRight()) { $left_side = null; $right_side = $inline; + + $left_hidden = null; + $right_hidden = $inline->newHiddenIcon(); } else { $left_side = $inline; $right_side = null; + + $left_hidden = $inline->newHiddenIcon(); + $right_hidden = null; } } else { list($u, $v) = $inlines; @@ -48,6 +54,9 @@ final class PHUIDiffTwoUpInlineCommentRowScaffold $left_side = $v; $right_side = $u; } + + $left_hidden = null; + $right_hidden = null; } $left_attrs = array( @@ -62,9 +71,9 @@ final class PHUIDiffTwoUpInlineCommentRowScaffold ); $cells = array( - phutil_tag('th', array()), + phutil_tag('th', array(), $left_hidden), phutil_tag('td', $left_attrs, $left_side), - phutil_tag('th', array()), + phutil_tag('th', array(), $right_hidden), phutil_tag('td', $right_attrs, $right_side), ); diff --git a/src/infrastructure/env/PhabricatorConfigLocalSource.php b/src/infrastructure/env/PhabricatorConfigLocalSource.php index 3a2295eb5e..16dc43a9bc 100644 --- a/src/infrastructure/env/PhabricatorConfigLocalSource.php +++ b/src/infrastructure/env/PhabricatorConfigLocalSource.php @@ -21,17 +21,35 @@ final class PhabricatorConfigLocalSource extends PhabricatorConfigProxySource { private function loadConfig() { $path = $this->getConfigPath(); - if (@file_exists($path)) { - $data = @file_get_contents($path); - if ($data) { - $data = json_decode($data, true); - if (is_array($data)) { - return $data; - } - } + + if (!Filesystem::pathExists($path)) { + return array(); } - return array(); + try { + $data = Filesystem::readFile($path); + } catch (FilesystemException $ex) { + throw new PhutilProxyException( + pht( + 'Configuration file "%s" exists, but could not be read.', + $path), + $ex); + } + + try { + $result = phutil_json_decode($data); + } catch (PhutilJSONParserException $ex) { + throw new PhutilProxyException( + pht( + 'Configuration file "%s" exists and is readable, but the content '. + 'is not valid JSON. You may have edited this file manually and '. + 'introduced a syntax error by mistake. Correct the file syntax '. + 'to continue.', + $path), + $ex); + } + + return $result; } private function saveConfig() { diff --git a/src/view/form/PHUIInfoView.php b/src/view/form/PHUIInfoView.php index ca01ff294b..8ba74056b8 100644 --- a/src/view/form/PHUIInfoView.php +++ b/src/view/form/PHUIInfoView.php @@ -7,6 +7,7 @@ final class PHUIInfoView extends AphrontTagView { const SEVERITY_NOTICE = 'notice'; const SEVERITY_NODATA = 'nodata'; const SEVERITY_SUCCESS = 'success'; + const SEVERITY_PLAIN = 'plain'; private $title; private $errors; @@ -52,8 +53,14 @@ final class PHUIInfoView extends AphrontTagView { return $this; } - public function setIcon(PHUIIconView $icon) { - $this->icon = $icon; + public function setIcon($icon) { + if ($icon instanceof PHUIIconView) { + $this->icon = $icon; + } else { + $icon = id(new PHUIIconView()) + ->setIcon($icon); + } + return $this; } @@ -72,6 +79,7 @@ final class PHUIInfoView extends AphrontTagView { case self::SEVERITY_NOTICE: $icon = 'fa-info-circle'; break; + case self::SEVERITY_PLAIN: case self::SEVERITY_NODATA: return null; break; diff --git a/src/view/layout/PhabricatorActionListView.php b/src/view/layout/PhabricatorActionListView.php index faf30555eb..134c336735 100644 --- a/src/view/layout/PhabricatorActionListView.php +++ b/src/view/layout/PhabricatorActionListView.php @@ -16,6 +16,10 @@ final class PhabricatorActionListView extends AphrontTagView { } protected function getTagName() { + if (!$this->actions) { + return null; + } + return 'ul'; } diff --git a/src/view/phui/PHUIButtonView.php b/src/view/phui/PHUIButtonView.php index c4ee7a190c..ee6975357c 100644 --- a/src/view/phui/PHUIButtonView.php +++ b/src/view/phui/PHUIButtonView.php @@ -64,6 +64,10 @@ final class PHUIButtonView extends AphrontTagView { return $this; } + public function getColor() { + return $this->color; + } + public function setDisabled($disabled) { $this->disabled = $disabled; return $this; diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php index 0ae2c558d6..0cd8379b42 100644 --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -267,7 +267,9 @@ final class PHUIHeaderView extends AphrontTagView { if ($this->actionLinks) { $actions = array(); foreach ($this->actionLinks as $button) { - $button->setColor(PHUIButtonView::GREY); + if (!$button->getColor()) { + $button->setColor(PHUIButtonView::GREY); + } $button->addClass(PHUI::MARGIN_SMALL_LEFT); $button->addClass('phui-header-action-link'); $actions[] = $button; diff --git a/src/view/phui/PHUITimelineEventView.php b/src/view/phui/PHUITimelineEventView.php index 142ea1e87d..51a0ea5aae 100644 --- a/src/view/phui/PHUITimelineEventView.php +++ b/src/view/phui/PHUITimelineEventView.php @@ -28,6 +28,7 @@ final class PHUITimelineEventView extends AphrontView { private $hideCommentOptions = false; private $authorPHID; private $badges = array(); + private $pinboardItems = array(); public function setAuthorPHID($author_phid) { $this->authorPHID = $author_phid; @@ -190,6 +191,11 @@ final class PHUITimelineEventView extends AphrontView { return $this->hideCommentOptions; } + public function addPinboardItem(PHUIPinboardItemView $item) { + $this->pinboardItems[] = $item; + return $this; + } + public function setToken($token, $removed = false) { $this->token = $token; $this->tokenRemoved = $removed; @@ -441,12 +447,21 @@ final class PHUITimelineEventView extends AphrontView { ), $content); + // Image Events + $pinboard = null; + if ($this->pinboardItems) { + $pinboard = new PHUIPinboardView(); + foreach ($this->pinboardItems as $item) { + $pinboard->addItem($item); + } + } + $content = phutil_tag( 'div', array( 'class' => implode(' ', $content_classes), ), - array($image, $badges, $wedge, $content)); + array($image, $badges, $wedge, $content, $pinboard)); $outer_classes = $this->classes; $outer_classes[] = 'phui-timeline-shell'; diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 1bd7d48635..eee0e167f3 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -67,10 +67,6 @@ padding: 1px 4px; } -.device .differential-diff .inline > td { - padding: 4px; -} - .differential-diff td .zwsp { position: absolute; width: 0; @@ -113,10 +109,6 @@ color: {$darkgreytext}; } -.differential-diff th.selected { - background-color: {$sh-yellowbackground}; -} - .differential-changeset-immutable .differential-diff th { cursor: auto; } @@ -312,10 +304,11 @@ td.cov-I { top: 0; left: 0; box-sizing: border-box; + pointer-events: none; } .differential-diff .inline > td { - padding: 8px 12px; + padding: 0; } .differential-loading { @@ -332,6 +325,7 @@ td.cov-I { border: 1px solid {$blue}; text-align: center; background-color: {$lightblue}; + margin: 8px; } .differential-collapse-undo a { @@ -392,3 +386,53 @@ tr.differential-inline-loading { .differential-review-stage { position: relative; } + +.diff-banner { + position: fixed; + top: 0; + left: 0; + right: 0; + background: rgba(255, 255, 255, 0.95); + box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1); + border-bottom: 1px solid {$lightgreyborder}; + padding: 12px 18px; + vertical-align: middle; + font-weight: bold; + font-size: {$biggerfontsize}; +} + +.diff-banner .phui-icon-view { + margin-right: 4px; +} + +.diff-banner-path { + color: {$greytext}; +} + +.scroll-objective-list { + position: fixed; + right: 0; + width: 24px; + top: 48px; + bottom: 48px; + background: rgba(255, 255, 255, 0.50); + border-style: solid; + border-color: rgba(255, 255, 255, 0.95); + border-width: 1px 0 1px 1px; + box-shadow: -1px 0 2px rgba(255, 255, 255, 0.25); + overflow: hidden; +} + +.scroll-objective { + display: block; + position: absolute; + box-sizing: border-box; + cursor: pointer; + left: 8px; +} + +.scroll-objective .phui-icon-view { + text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.25); + display: block; + height: 14px; +} diff --git a/webroot/rsrc/css/application/differential/phui-inline-comment.css b/webroot/rsrc/css/application/differential/phui-inline-comment.css index f93b2c8d6c..146117b1cb 100644 --- a/webroot/rsrc/css/application/differential/phui-inline-comment.css +++ b/webroot/rsrc/css/application/differential/phui-inline-comment.css @@ -28,14 +28,17 @@ background: #fff; border: 1px solid {$sh-yellowborder}; font: {$basefont}; - margin: 0; - width: 100%; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; overflow: hidden; white-space: normal; border-radius: 3px; + margin: 8px 12px; +} + +.device .differential-inline-comment { + margin: 4px; } .inline-state-is-draft { @@ -61,7 +64,7 @@ /* Tighten up spacing on replies */ .differential-inline-comment.inline-comment-is-reply { - margin-top: -12px; + margin-top: 0; } .differential-inline-comment .inline-head-right { @@ -315,10 +318,10 @@ .differential-inline-undo { padding: 8px; + margin: 4px 12px; text-align: center; background: {$sh-yellowbackground}; border: 1px solid {$sh-yellowborder}; - margin: 4px 0; color: {$darkgreytext}; font: {$basefont}; font-size: {$normalfontsize}; @@ -374,17 +377,45 @@ /* - Hiding Inlines ------------------------------------------------------------ */ -.reveal-inlines { - float: left; - margin-left: 4px; +.reveal-inline { + color: {$lightbluetext}; + margin: 4px 0; + display: none; +} + +.inline-hidden .reveal-inline { + display: block; +} + +.inline-hidden .differential-inline-comment { + display: none; +} + +.differential-inline-summary { + background: {$lightgreybackground}; + padding: 2px 16px; + color: {$lightgreytext}; + display: none; + font: {$basefont}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.device .differential-inline-summary { + padding-left: 4px; + padding-right: 4px; +} + +.inline-hidden .differential-inline-summary { + display: block; +} + +.reveal-inline span.phui-icon-view { color: {$lightbluetext}; } -.reveal-inlines span.phui-icon-view { - color: {$lightbluetext}; -} - -.reveal-inlines:hover span.phui-icon-view { +.reveal-inline:hover span.phui-icon-view { color: {$darkbluetext}; } diff --git a/webroot/rsrc/css/core/z-index.css b/webroot/rsrc/css/core/z-index.css index bdab2e97ed..04b013386d 100644 --- a/webroot/rsrc/css/core/z-index.css +++ b/webroot/rsrc/css/core/z-index.css @@ -93,6 +93,14 @@ div.phui-calendar-day-event { z-index: 6; } +.diff-banner { + z-index: 6; +} + +.scroll-objective-list { + z-index: 6; +} + .conpherence-durable-column { z-index: 7; } diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-drag-ui.css b/webroot/rsrc/css/phui/object-item/phui-oi-drag-ui.css index caf9ff8dd0..5f24e2b983 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-drag-ui.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-drag-ui.css @@ -23,6 +23,10 @@ margin-top: 4px; } +.phui-oi-drag .phui-oi-name { + padding-left: 0; +} + .phui-oi-drag.phui-oi-with-image-icon .phui-oi-frame, .phui-oi-drag.phui-oi-with-image .phui-oi-frame, .phui-oi-drag .phui-oi-frame { @@ -57,3 +61,7 @@ .phui-oi-list-drag .drag-ghost { margin-top: 4px; } + +.phui-oi-list-drag .phui-object-icon-pane { + padding-right: 8px; +} diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css index 2f29001f9a..26b0781e8d 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css @@ -390,6 +390,17 @@ ul.phui-oi-icons { text-decoration: line-through; } +.phui-oi.phui-oi-disabled .phui-oi-image { + opacity: .8; + -webkit-filter: grayscale(100%); + filter: grayscale(100%); +} + +.phui-oi.phui-oi-disabled .phui-oi-attribute, +.phui-oi.phui-oi-disabled .phui-oi-attribute .phui-icon-view { + color: {$lightgreytext}; +} + /* - Effects ------------------------------------------------------------------- diff --git a/webroot/rsrc/css/phui/phui-curtain-view.css b/webroot/rsrc/css/phui/phui-curtain-view.css index dbc706994a..0d7d4f942f 100644 --- a/webroot/rsrc/css/phui/phui-curtain-view.css +++ b/webroot/rsrc/css/phui/phui-curtain-view.css @@ -7,12 +7,17 @@ margin: 0 4px; } +.phui-two-column-properties > .phui-curtain-panel:first-child { + padding-top: 6px; +} + .device .phui-curtain-panel { padding: 8px 0; margin: 0; } -.device-desktop .phui-curtain-panel { +.device-desktop .phui-curtain-panel + .phui-curtain-panel, +.device-desktop .phabricator-action-list-view + .phui-curtain-panel { border-top: 1px solid {$greybackground}; } diff --git a/webroot/rsrc/css/phui/phui-info-view.css b/webroot/rsrc/css/phui/phui-info-view.css index 1642822b77..105adf9ed4 100644 --- a/webroot/rsrc/css/phui/phui-info-view.css +++ b/webroot/rsrc/css/phui/phui-info-view.css @@ -11,6 +11,14 @@ border-radius: 3px; } +div.phui-info-view.phui-info-severity-plain { + background: {$lightgreybackground}; + color: {$bluetext}; + border: none; + padding: 8px 12px; + margin-bottom: 4px !important; +} + .phui-info-view.phui-info-view-flush { margin: 0 0 20px 0; } diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index c4f568411a..da6b5950c4 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -429,3 +429,9 @@ a.phui-timeline-menu .phui-icon-view { .phui-comment-preview-view { margin-bottom: 20px; } + +.phui-timeline-view .phui-pinboard-view { + margin: 8px 0 0 0; + padding: 0; + text-align: left; +} diff --git a/webroot/rsrc/externals/javelin/lib/DOM.js b/webroot/rsrc/externals/javelin/lib/DOM.js index 189d778ad5..e0e3d764e0 100644 --- a/webroot/rsrc/externals/javelin/lib/DOM.js +++ b/webroot/rsrc/externals/javelin/lib/DOM.js @@ -891,7 +891,7 @@ JX.install('DOM', { * it. * * @param Node Node to look above. - * @param string Tag name, like 'a' or 'textarea'. + * @param string Optional tag name, like 'a' or 'textarea'. * @param string Optionally, sigil which selected node must have. * @return Node Matching node. * @@ -911,7 +911,7 @@ JX.install('DOM', { if (!result) { break; } - if (JX.DOM.isType(result, tagname)) { + if (!tagname || JX.DOM.isType(result, tagname)) { if (!sigil || JX.Stratcom.hasSigil(result, sigil)) { break; } diff --git a/webroot/rsrc/js/application/differential/ChangesetViewManager.js b/webroot/rsrc/js/application/diff/DiffChangeset.js similarity index 54% rename from webroot/rsrc/js/application/differential/ChangesetViewManager.js rename to webroot/rsrc/js/application/diff/DiffChangeset.js index 0c23e18737..cd7f888a40 100644 --- a/webroot/rsrc/js/application/differential/ChangesetViewManager.js +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -1,5 +1,5 @@ /** - * @provides changeset-view-manager + * @provides phabricator-diff-changeset * @requires javelin-dom * javelin-util * javelin-stratcom @@ -8,15 +8,18 @@ * javelin-router * javelin-behavior-device * javelin-vector + * phabricator-diff-inline + * @javelin */ -JX.install('ChangesetViewManager', { +JX.install('DiffChangeset', { construct : function(node) { this._node = node; var data = this._getNodeData(); + this._renderURI = data.renderURI; this._ref = data.ref; this._whitespace = data.whitespace; @@ -24,6 +27,15 @@ JX.install('ChangesetViewManager', { this._highlight = data.highlight; this._encoding = data.encoding; this._loaded = data.loaded; + + this._leftID = data.left; + this._rightID = data.right; + + this._displayPath = JX.$H(data.displayPath); + this._objectiveName = data.objectiveName; + this._icon = data.icon; + + this._inlines = []; }, members: { @@ -40,6 +52,70 @@ JX.install('ChangesetViewManager', { _encoding: null, _undoTemplates: null, + _leftID: null, + _rightID: null, + + _inlines: null, + _visible: true, + + _undoNode: null, + _displayPath: null, + + _changesetList: null, + _objective: null, + _objectiveName: null, + _icon: null, + + getLeftChangesetID: function() { + return this._leftID; + }, + + getRightChangesetID: function() { + return this._rightID; + }, + + setChangesetList: function(list) { + this._changesetList = list; + + var objectives = list.getObjectives(); + this._objective = objectives.newObjective() + .setAnchor(this._node); + + this._updateObjective(); + + return this; + }, + + _updateObjective: function() { + this._objective + .setIcon(this.getIcon()) + .setColor(this.getColor()) + .setTooltip(this.getObjectiveName()); + }, + + getIcon: function() { + if (!this._visible) { + return 'fa-file-o'; + } + + return this._icon; + }, + + getColor: function() { + if (!this._visible) { + return 'grey'; + } + + return 'blue'; + }, + + getObjectiveName: function() { + return this._objectiveName; + }, + + getChangesetList: function() { + return this._changesetList; + }, /** * Has the content of this changeset been loaded? @@ -121,6 +197,7 @@ JX.install('ChangesetViewManager', { this._sequence++; var params = this._getViewParameters(); + var pht = this.getChangesetList().getTranslations(); var workflow = new JX.Workflow(this._renderURI, params) .setHandler(JX.bind(this, this._onresponse, this._sequence)); @@ -132,7 +209,7 @@ JX.install('ChangesetViewManager', { JX.$N( 'div', {className: 'differential-loading'}, - 'Loading...')); + pht('Loading...'))); return this; }, @@ -152,9 +229,10 @@ JX.install('ChangesetViewManager', { var params = this._getViewParameters(); params.range = range; + var pht = this.getChangesetList().getTranslations(); + var container = JX.DOM.scry(target, 'td')[0]; - // TODO: pht() - JX.DOM.setContent(container, 'Loading...'); + JX.DOM.setContent(container, pht('Loading...')); JX.DOM.alterClass(target, 'differential-show-more-loading', true); var workflow = new JX.Workflow(this._renderURI, params) @@ -174,6 +252,20 @@ JX.install('ChangesetViewManager', { return this; }, + loadAllContext: function() { + var nodes = JX.DOM.scry(this._node, 'tr', 'context-target'); + for (var ii = 0; ii < nodes.length; ii++) { + var show = JX.DOM.scry(nodes[ii], 'a', 'show-more'); + for (var jj = 0; jj < show.length; jj++) { + var data = JX.Stratcom.getData(show[jj]); + if (data.type != 'all') { + continue; + } + this.loadContext(data.range, nodes[ii], true); + } + } + }, + _startContentWorkflow: function(workflow) { var routable = workflow.getRoutable(); @@ -185,6 +277,9 @@ JX.install('ChangesetViewManager', { JX.Router.getInstance().queue(routable); }, + getDisplayPath: function() { + return this._displayPath; + }, /** * Receive a response to a context request. @@ -293,10 +388,105 @@ JX.install('ChangesetViewManager', { return this._highlight; }, + getSelectableItems: function() { + var items = []; + + items.push({ + type: 'file', + changeset: this, + target: this, + nodes: { + begin: this._node, + end: null + } + }); + + if (!this._visible) { + return items; + } + + var rows = JX.DOM.scry(this._node, 'tr'); + + var blocks = []; + var block; + var ii; + for (ii = 0; ii < rows.length; ii++) { + var type = this._getRowType(rows[ii]); + + if (!block || (block.type !== type)) { + block = { + type: type, + items: [] + }; + blocks.push(block); + } + + block.items.push(rows[ii]); + } + + for (ii = 0; ii < blocks.length; ii++) { + block = blocks[ii]; + + if (block.type == 'change') { + items.push({ + type: block.type, + changeset: this, + target: block.items[0], + nodes: { + begin: block.items[0], + end: block.items[block.items.length - 1] + } + }); + } + + if (block.type == 'comment') { + for (var jj = 0; jj < block.items.length; jj++) { + var inline = this.getInlineForRow(block.items[jj]); + + items.push({ + type: block.type, + changeset: this, + target: inline, + hidden: inline.isHidden(), + nodes: { + begin: block.items[jj], + end: block.items[jj] + } + }); + } + } + } + + return items; + }, + + _getRowType: function(row) { + // NOTE: Don't do "className.indexOf()" elsewhere. This is evil legacy + // magic. + + if (row.className.indexOf('inline') !== -1) { + return 'comment'; + } + + var cells = JX.DOM.scry(row, 'td'); + for (var ii = 0; ii < cells.length; ii++) { + if (cells[ii].className.indexOf('old') !== -1 || + cells[ii].className.indexOf('new') !== -1) { + return 'change'; + } + } + }, + _getNodeData: function() { return JX.Stratcom.getData(this._node); }, + getVectors: function() { + return { + pos: JX.$V(this._node), + dim: JX.Vector.getDim(this._node) + }; + }, _onresponse: function(sequence, response) { if (sequence != this._sequence) { @@ -331,6 +521,12 @@ JX.install('ChangesetViewManager', { var near_top = (old_pos.y <= sticky); var near_bot = ((old_pos.y + old_view.y) >= (old_dim.y - sticky)); + // If we have an anchor in the URL, never stick to the bottom of the + // page. See T11784 for discussion. + if (window.location.hash) { + near_bot = false; + } + var target_pos = JX.Vector.getPos(target); var target_dim = JX.Vector.getDim(target); var target_mid = (target_pos.y + (target_dim.y / 2)); @@ -373,6 +569,11 @@ JX.install('ChangesetViewManager', { } JX.Stratcom.invoke('differential-inline-comment-refresh'); + + this._objective.show(); + this._rebuildAllInlines(); + + JX.Stratcom.invoke('resize'); }, _getContentFrame: function() { @@ -381,15 +582,201 @@ JX.install('ChangesetViewManager', { _getRoutableKey: function() { return 'changeset-view.' + this._ref + '.' + this._sequence; - } + }, + getInlineForRow: function(node) { + var data = JX.Stratcom.getData(node); + + if (!data.inline) { + var inline = new JX.DiffInline() + .setChangeset(this) + .bindToRow(node); + + this._inlines.push(inline); + } + + return data.inline; + }, + + newInlineForRange: function(origin, target) { + var list = this.getChangesetList(); + + var src = list.getLineNumberFromHeader(origin); + var dst = list.getLineNumberFromHeader(target); + + var changeset_id = null; + var side = list.getDisplaySideFromHeader(origin); + if (side == 'right') { + changeset_id = this.getRightChangesetID(); + } else { + changeset_id = this.getLeftChangesetID(); + } + + var is_new = false; + if (side == 'right') { + is_new = true; + } else if (this.getRightChangesetID() != this.getLeftChangesetID()) { + is_new = true; + } + + var data = { + origin: origin, + target: target, + number: src, + length: dst - src, + changesetID: changeset_id, + displaySide: side, + isNewFile: is_new + }; + + var inline = new JX.DiffInline() + .setChangeset(this) + .bindToRange(data); + + this._inlines.push(inline); + + inline.create(); + + return inline; + }, + + newInlineReply: function(original, text) { + var inline = new JX.DiffInline() + .setChangeset(this) + .bindToReply(original); + + this._inlines.push(inline); + + inline.create(text); + + return inline; + }, + + getInlineByID: function(id) { + return this._queryInline('id', id); + }, + + getInlineByPHID: function(phid) { + return this._queryInline('phid', phid); + }, + + _queryInline: function(field, value) { + // First, look for the inline in the objects we've already built. + var inline = this._findInline(field, value); + if (inline) { + return inline; + } + + // If we haven't found a matching inline yet, rebuild all the inlines + // present in the document, then look again. + this._rebuildAllInlines(); + return this._findInline(field, value); + }, + + _findInline: function(field, value) { + for (var ii = 0; ii < this._inlines.length; ii++) { + var inline = this._inlines[ii]; + + var target; + switch (field) { + case 'id': + target = inline.getID(); + break; + case 'phid': + target = inline.getPHID(); + break; + } + + if (target == value) { + return inline; + } + } + + return null; + }, + + _rebuildAllInlines: function() { + var rows = JX.DOM.scry(this._node, 'tr'); + for (var ii = 0; ii < rows.length; ii++) { + var row = rows[ii]; + if (this._getRowType(row) != 'comment') { + continue; + } + + // As a side effect, this builds any missing inline objects and adds + // them to this Changeset's list of inlines. + this.getInlineForRow(row); + } + }, + + toggleVisibility: function() { + this._visible = !this._visible; + + var diff = JX.DOM.find(this._node, 'table', 'differential-diff'); + var undo = this._getUndoNode(); + + if (this._visible) { + JX.DOM.show(diff); + JX.DOM.remove(undo); + } else { + JX.DOM.hide(diff); + JX.DOM.appendContent(diff.parentNode, undo); + } + + this._updateObjective(); + for (var ii = 0; ii < this._inlines.length; ii++) { + this._inlines[ii].updateObjective(); + } + + JX.Stratcom.invoke('resize'); + }, + + isVisible: function() { + return this._visible; + }, + + _getUndoNode: function() { + if (!this._undoNode) { + var pht = this.getChangesetList().getTranslations(); + + var link_attributes = { + href: '#' + }; + + var undo_link = JX.$N('a', link_attributes, pht('Show Content')); + + var onundo = JX.bind(this, this._onundo); + JX.DOM.listen(undo_link, 'click', null, onundo); + + var node_attributes = { + className: 'differential-collapse-undo' + }; + + var node_content = [ + pht('This file content has been collapsed.'), + ' ', + undo_link + ]; + + var undo_node = JX.$N('div', node_attributes, node_content); + + this._undoNode = undo_node; + } + + return this._undoNode; + }, + + _onundo: function(e) { + e.kill(); + this.toggleVisibility(); + } }, statics: { getForNode: function(node) { var data = JX.Stratcom.getData(node); if (!data.changesetViewManager) { - data.changesetViewManager = new JX.ChangesetViewManager(node); + data.changesetViewManager = new JX.DiffChangeset(node); } return data.changesetViewManager; } diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js new file mode 100644 index 0000000000..e256372852 --- /dev/null +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -0,0 +1,1380 @@ +/** + * @provides phabricator-diff-changeset-list + * @requires javelin-install + * phabricator-scroll-objective-list + * @javelin + */ + +JX.install('DiffChangesetList', { + + construct: function() { + this._changesets = []; + this._objectives = new JX.ScrollObjectiveList(); + + var onload = JX.bind(this, this._ifawake, this._onload); + JX.Stratcom.listen('click', 'differential-load', onload); + + var onmore = JX.bind(this, this._ifawake, this._onmore); + JX.Stratcom.listen('click', 'show-more', onmore); + + var onmenu = JX.bind(this, this._ifawake, this._onmenu); + JX.Stratcom.listen('click', 'differential-view-options', onmenu); + + var onhide = JX.bind(this, this._ifawake, this._onhide); + JX.Stratcom.listen('click', 'hide-inline', onhide); + + var onreveal = JX.bind(this, this._ifawake, this._onreveal); + JX.Stratcom.listen('click', 'reveal-inline', onreveal); + + var onedit = JX.bind(this, this._ifawake, this._onaction, 'edit'); + JX.Stratcom.listen( + 'click', + ['differential-inline-comment', 'differential-inline-edit'], + onedit); + + var ondone = JX.bind(this, this._ifawake, this._onaction, 'done'); + JX.Stratcom.listen( + 'click', + ['differential-inline-comment', 'differential-inline-done'], + ondone); + + var ondelete = JX.bind(this, this._ifawake, this._onaction, 'delete'); + JX.Stratcom.listen( + 'click', + ['differential-inline-comment', 'differential-inline-delete'], + ondelete); + + var onreply = JX.bind(this, this._ifawake, this._onaction, 'reply'); + JX.Stratcom.listen( + 'click', + ['differential-inline-comment', 'differential-inline-reply'], + onreply); + + var onresize = JX.bind(this, this._ifawake, this._onresize); + JX.Stratcom.listen('resize', null, onresize); + + var onscroll = JX.bind(this, this._ifawake, this._onscroll); + JX.Stratcom.listen('scroll', null, onscroll); + + var onselect = JX.bind(this, this._ifawake, this._onselect); + JX.Stratcom.listen( + 'mousedown', + ['differential-inline-comment', 'differential-inline-header'], + onselect); + + var onhover = JX.bind(this, this._ifawake, this._onhover); + JX.Stratcom.listen( + ['mouseover', 'mouseout'], + 'differential-inline-comment', + onhover); + + var onrangedown = JX.bind(this, this._ifawake, this._onrangedown); + JX.Stratcom.listen( + ['touchstart', 'mousedown'], + ['differential-changeset', 'tag:th'], + onrangedown); + + var onrangemove = JX.bind(this, this._ifawake, this._onrangemove); + JX.Stratcom.listen( + ['mouseover', 'mouseout'], + ['differential-changeset', 'tag:th'], + onrangemove); + + var onrangetouchmove = JX.bind(this, this._ifawake, this._onrangetouchmove); + JX.Stratcom.listen( + 'touchmove', + null, + onrangetouchmove); + + var onrangeup = JX.bind(this, this._ifawake, this._onrangeup); + JX.Stratcom.listen( + ['touchend', 'mouseup'], + null, + onrangeup); + }, + + properties: { + translations: null, + inlineURI: null + }, + + members: { + _initialized: false, + _asleep: true, + _changesets: null, + _objectives: null, + + _cursorItem: null, + + _focusNode: null, + _focusStart: null, + _focusEnd: null, + + _hoverNode: null, + _hoverInline: null, + _hoverOrigin: null, + _hoverTarget: null, + + _rangeActive: false, + _rangeOrigin: null, + _rangeTarget: null, + + _bannerNode: null, + + sleep: function() { + this._asleep = true; + + this._redrawFocus(); + this._redrawSelection(); + this.resetHover(); + + this._objectives.hide(); + }, + + wake: function() { + this._asleep = false; + + this._redrawFocus(); + this._redrawSelection(); + + this._objectives.show(); + + if (this._initialized) { + return; + } + + this._initialized = true; + var pht = this.getTranslations(); + + var label; + + label = pht('Jump to next change.'); + this._installJumpKey('j', label, 1); + + label = pht('Jump to previous change.'); + this._installJumpKey('k', label, -1); + + label = pht('Jump to next file.'); + this._installJumpKey('J', label, 1, 'file'); + + label = pht('Jump to previous file.'); + this._installJumpKey('K', label, -1, 'file'); + + label = pht('Jump to next inline comment.'); + this._installJumpKey('n', label, 1, 'comment'); + + label = pht('Jump to previous inline comment.'); + this._installJumpKey('p', label, -1, 'comment'); + + label = pht('Jump to next inline comment, including hidden comments.'); + this._installJumpKey('N', label, 1, 'comment', true); + + label = pht( + 'Jump to previous inline comment, including hidden comments.'); + this._installJumpKey('P', label, -1, 'comment', true); + + label = pht('Hide or show the current file.'); + this._installKey('h', label, this._onkeytogglefile); + + label = pht('Jump to the table of contents.'); + this._installKey('t', label, this._ontoc); + + label = pht('Reply to selected inline comment or change.'); + this._installKey('r', label, JX.bind(this, this._onkeyreply, false)); + + label = pht('Reply and quote selected inline comment.'); + this._installKey('R', label, JX.bind(this, this._onkeyreply, true)); + + label = pht('Edit selected inline comment.'); + this._installKey('e', label, this._onkeyedit); + + label = pht('Mark or unmark selected inline comment as done.'); + this._installKey('w', label, this._onkeydone); + + label = pht('Hide or show inline comment.'); + this._installKey('q', label, this._onkeyhide); + }, + + isAsleep: function() { + return this._asleep; + }, + + getObjectives: function() { + return this._objectives; + }, + + newChangesetForNode: function(node) { + var changeset = JX.DiffChangeset.getForNode(node); + + this._changesets.push(changeset); + changeset.setChangesetList(this); + + return changeset; + }, + + getChangesetForNode: function(node) { + return JX.DiffChangeset.getForNode(node); + }, + + getInlineByID: function(id) { + var inline = null; + + for (var ii = 0; ii < this._changesets.length; ii++) { + inline = this._changesets[ii].getInlineByID(id); + if (inline) { + break; + } + } + + return inline; + }, + + _ifawake: function(f) { + // This function takes another function and only calls it if the + // changeset list is awake, so we basically just ignore events when we + // are asleep. This may move up the stack at some point as we do more + // with Quicksand/Sheets. + + if (this.isAsleep()) { + return; + } + + return f.apply(this, [].slice.call(arguments, 1)); + }, + + _onload: function(e) { + var data = e.getNodeData('differential-load'); + + // NOTE: We can trigger a load from either an explicit "Load" link on + // the changeset, or by clicking a link in the table of contents. If + // the event was a table of contents link, we let the anchor behavior + // run normally. + if (data.kill) { + e.kill(); + } + + var node = JX.$(data.id); + var changeset = this.getChangesetForNode(node); + + changeset.load(); + + // TODO: Move this into Changeset. + var routable = changeset.getRoutable(); + if (routable) { + routable.setPriority(2000); + } + }, + + _installKey: function(key, label, handler) { + handler = JX.bind(this, this._ifawake, handler); + + return new JX.KeyboardShortcut(key, label) + .setHandler(handler) + .register(); + }, + + _installJumpKey: function(key, label, delta, filter, show_hidden) { + filter = filter || null; + var handler = JX.bind(this, this._onjumpkey, delta, filter, show_hidden); + return this._installKey(key, label, handler); + }, + + _ontoc: function(manager) { + var toc = JX.$('toc'); + manager.scrollTo(toc); + }, + + getSelectedInline: function() { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'comment') { + return cursor.target; + } + } + + return null; + }, + + _onkeyreply: function(is_quote) { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'comment') { + var inline = cursor.target; + if (inline.canReply()) { + this.setFocus(null); + + var text; + if (is_quote) { + text = inline.getRawText(); + text = '> ' + text.replace(/\n/g, '\n> ') + '\n\n'; + } else { + text = ''; + } + + inline.reply(text); + return; + } + } + + // If the keyboard cursor is selecting a range of lines, we may have + // a mixture of old and new changes on the selected rows. It is not + // entirely unambiguous what the user means when they say they want + // to reply to this, but we use this logic: reply on the new file if + // there are any new lines. Otherwise (if there are only removed + // lines) reply on the old file. + + if (cursor.type == 'change') { + var origin = cursor.nodes.begin; + var target = cursor.nodes.end; + + // The "origin" and "target" are entire rows, but we need to find + // a range of "" nodes to actually create an inline, so go + // fishing. + + var old_list = []; + var new_list = []; + + var row = origin; + while (row) { + var header = row.firstChild; + while (header) { + if (JX.DOM.isType(header, 'th')) { + if (header.className.indexOf('old') !== -1) { + old_list.push(header); + } else if (header.className.indexOf('new') !== -1) { + new_list.push(header); + } + } + header = header.nextSibling; + } + + if (row == target) { + break; + } + + row = row.nextSibling; + } + + var use_list; + if (new_list.length) { + use_list = new_list; + } else { + use_list = old_list; + } + + var src = use_list[0]; + var dst = use_list[use_list.length - 1]; + + cursor.changeset.newInlineForRange(src, dst); + + this.setFocus(null); + return; + } + } + + var pht = this.getTranslations(); + this._warnUser(pht('You must select a comment or change to reply to.')); + }, + + _onkeyedit: function() { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'comment') { + var inline = cursor.target; + if (inline.canEdit()) { + this.setFocus(null); + + inline.edit(); + return; + } + } + } + + var pht = this.getTranslations(); + this._warnUser(pht('You must select a comment to edit.')); + }, + + _onkeydone: function() { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'comment') { + var inline = cursor.target; + if (inline.canDone()) { + this.setFocus(null); + + inline.toggleDone(); + return; + } + } + } + + var pht = this.getTranslations(); + this._warnUser(pht('You must select a comment to mark done.')); + }, + + _onkeytogglefile: function() { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'file') { + cursor.changeset.toggleVisibility(); + return; + } + } + + var pht = this.getTranslations(); + this._warnUser(pht('You must select a file to hide or show.')); + }, + + _onkeyhide: function() { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'comment') { + var inline = cursor.target; + if (inline.canHide()) { + this.setFocus(null); + + inline.setHidden(!inline.isHidden()); + return; + } + } + } + + var pht = this.getTranslations(); + this._warnUser(pht('You must select a comment to hide.')); + }, + + _warnUser: function(message) { + new JX.Notification() + .setContent(message) + .alterClassName('jx-notification-alert', true) + .setDuration(1000) + .show(); + }, + + _onjumpkey: function(delta, filter, show_hidden, manager) { + var state = this._getSelectionState(); + + var cursor = state.cursor; + var items = state.items; + + // If there's currently no selection and the user tries to go back, + // don't do anything. + if ((cursor === null) && (delta < 0)) { + return; + } + + while (true) { + if (cursor === null) { + cursor = 0; + } else { + cursor = cursor + delta; + } + + // If we've gone backward past the first change, bail out. + if (cursor < 0) { + return; + } + + // If we've gone forward off the end of the list, bail out. + if (cursor >= items.length) { + return; + } + + // If we're selecting things of a particular type (like only files) + // and the next item isn't of that type, move past it. + if (filter !== null) { + if (items[cursor].type !== filter) { + continue; + } + } + + // If the item is hidden, don't select it when iterating with jump + // keys. It can still potentially be selected in other ways. + if (!show_hidden) { + if (items[cursor].hidden) { + continue; + } + } + + // Otherwise, we've found a valid item to select. + break; + } + + this._setSelectionState(items[cursor], manager); + }, + + _getSelectionState: function() { + var items = this._getSelectableItems(); + + var cursor = null; + if (this._cursorItem !== null) { + for (var ii = 0; ii < items.length; ii++) { + var item = items[ii]; + if (this._cursorItem.target === item.target) { + cursor = ii; + break; + } + } + } + + return { + cursor: cursor, + items: items + }; + }, + + _setSelectionState: function(item, manager) { + this._cursorItem = item; + + this._redrawSelection(manager, true); + + return this; + }, + + _redrawSelection: function(manager, scroll) { + var cursor = this._cursorItem; + if (!cursor) { + this.setFocus(null); + return; + } + + this.setFocus(cursor.nodes.begin, cursor.nodes.end); + + if (manager && scroll) { + manager.scrollTo(cursor.nodes.begin); + } + + return this; + }, + + redrawCursor: function() { + // NOTE: This is setting the cursor to the current cursor. Usually, this + // would have no effect. + + // However, if the old cursor pointed at an inline and the inline has + // been edited so the rows have changed, this updates the cursor to point + // at the new inline with the proper rows for the current state, and + // redraws the reticle correctly. + + var state = this._getSelectionState(); + if (state.cursor !== null) { + this._setSelectionState(state.items[state.cursor]); + } + }, + + _getSelectableItems: function() { + var result = []; + + for (var ii = 0; ii < this._changesets.length; ii++) { + var items = this._changesets[ii].getSelectableItems(); + for (var jj = 0; jj < items.length; jj++) { + result.push(items[jj]); + } + } + + return result; + }, + + _onhover: function(e) { + if (e.getIsTouchEvent()) { + return; + } + + var inline; + if (e.getType() == 'mouseout') { + inline = null; + } else { + inline = this._getInlineForEvent(e); + } + + this._setHoverInline(inline); + }, + + _onmore: function(e) { + e.kill(); + + var node = e.getNode('differential-changeset'); + var changeset = this.getChangesetForNode(node); + + var data = e.getNodeData('show-more'); + var target = e.getNode('context-target'); + + changeset.loadContext(data.range, target); + }, + + _onmenu: function(e) { + var button = e.getNode('differential-view-options'); + + var data = JX.Stratcom.getData(button); + if (data.menu) { + // We've already built this menu, so we can let the menu itself handle + // the event. + return; + } + + e.prevent(); + + var pht = this.getTranslations(); + + var node = JX.DOM.findAbove( + button, + 'div', + 'differential-changeset'); + + var changeset = this.getChangesetForNode(node); + + var menu = new JX.PHUIXDropdownMenu(button); + var list = new JX.PHUIXActionListView(); + + var add_link = function(icon, name, href, local) { + if (!href) { + return; + } + + var link = new JX.PHUIXActionView() + .setIcon(icon) + .setName(name) + .setHref(href) + .setHandler(function(e) { + if (local) { + window.location.assign(href); + } else { + window.open(href); + } + menu.close(); + e.prevent(); + }); + + list.addItem(link); + return link; + }; + + var reveal_item = new JX.PHUIXActionView() + .setIcon('fa-eye'); + list.addItem(reveal_item); + + var visible_item = new JX.PHUIXActionView() + .setHandler(function(e) { + e.prevent(); + menu.close(); + + changeset.toggleVisibility(); + }); + list.addItem(visible_item); + + add_link('fa-file-text', pht('Browse in Diffusion'), data.diffusionURI); + add_link('fa-file-o', pht('View Standalone'), data.standaloneURI); + + var up_item = new JX.PHUIXActionView() + .setHandler(function(e) { + if (changeset.isLoaded()) { + var renderer = changeset.getRenderer(); + if (renderer == '1up') { + renderer = '2up'; + } else { + renderer = '1up'; + } + changeset.setRenderer(renderer); + } + changeset.reload(); + + e.prevent(); + menu.close(); + }); + list.addItem(up_item); + + var encoding_item = new JX.PHUIXActionView() + .setIcon('fa-font') + .setName(pht('Change Text Encoding...')) + .setHandler(function(e) { + var params = { + encoding: changeset.getEncoding() + }; + + new JX.Workflow('/services/encoding/', params) + .setHandler(function(r) { + changeset.setEncoding(r.encoding); + changeset.reload(); + }) + .start(); + + e.prevent(); + menu.close(); + }); + list.addItem(encoding_item); + + var highlight_item = new JX.PHUIXActionView() + .setIcon('fa-sun-o') + .setName(pht('Highlight As...')) + .setHandler(function(e) { + var params = { + highlight: changeset.getHighlight() + }; + + new JX.Workflow('/services/highlight/', params) + .setHandler(function(r) { + changeset.setHighlight(r.highlight); + changeset.reload(); + }) + .start(); + + e.prevent(); + menu.close(); + }); + list.addItem(highlight_item); + + add_link('fa-arrow-left', pht('Show Raw File (Left)'), data.leftURI); + add_link('fa-arrow-right', pht('Show Raw File (Right)'), data.rightURI); + add_link('fa-pencil', pht('Open in Editor'), data.editor, true); + add_link('fa-wrench', pht('Configure Editor'), data.editorConfigure); + + menu.setContent(list.getNode()); + + menu.listen('open', function() { + // When the user opens the menu, check if there are any "Show More" + // links in the changeset body. If there aren't, disable the "Show + // Entire File" menu item since it won't change anything. + + var nodes = JX.DOM.scry(JX.$(data.containerID), 'a', 'show-more'); + if (nodes.length) { + reveal_item + .setDisabled(false) + .setName(pht('Show All Context')) + .setIcon('fa-file-o') + .setHandler(function(e) { + changeset.loadAllContext(); + e.prevent(); + menu.close(); + }); + } else { + reveal_item + .setDisabled(true) + .setIcon('fa-file') + .setName(pht('All Context Shown')) + .setHandler(function(e) { e.prevent(); }); + } + + encoding_item.setDisabled(!changeset.isLoaded()); + highlight_item.setDisabled(!changeset.isLoaded()); + + if (changeset.isLoaded()) { + if (changeset.getRenderer() == '2up') { + up_item + .setIcon('fa-list-alt') + .setName(pht('View Unified')); + } else { + up_item + .setIcon('fa-files-o') + .setName(pht('View Side-by-Side')); + } + } else { + up_item + .setIcon('fa-refresh') + .setName(pht('Load Changes')); + } + + visible_item + .setDisabled(true) + .setIcon('fa-expand') + .setName(pht('Can\'t Toggle Unloaded File')); + var diffs = JX.DOM.scry( + JX.$(data.containerID), + 'table', + 'differential-diff'); + + if (diffs.length > 1) { + JX.$E( + 'More than one node with sigil "differential-diff" was found in "'+ + data.containerID+'."'); + } else if (diffs.length == 1) { + var diff = diffs[0]; + visible_item.setDisabled(false); + if (!changeset.isVisible()) { + visible_item + .setName(pht('Expand File')) + .setIcon('fa-expand'); + } else { + visible_item + .setName(pht('Collapse File')) + .setIcon('fa-compress'); + } + } else { + // Do nothing when there is no diff shown in the table. For example, + // the file is binary. + } + + }); + + data.menu = menu; + menu.open(); + }, + + _onhide: function(e) { + this._onhidereveal(e, true); + }, + + _onreveal: function(e) { + this._onhidereveal(e, false); + }, + + _onhidereveal: function(e, is_hide) { + e.kill(); + + var inline = this._getInlineForEvent(e); + + inline.setHidden(is_hide); + }, + + _onresize: function() { + this._redrawFocus(); + this._redrawSelection(); + this._redrawHover(); + + this._redrawBanner(); + }, + + _onscroll: function() { + this._redrawBanner(); + }, + + _onselect: function(e) { + // If the user clicked some element inside the header, like an action + // icon, ignore the event. They have to click the header element itself. + if (e.getTarget() !== e.getNode('differential-inline-header')) { + return; + } + + var inline = this._getInlineForEvent(e); + if (!inline) { + return; + } + + // The user definitely clicked an inline, so we're going to handle the + // event. + e.kill(); + + this.selectInline(inline); + }, + + selectInline: function(inline) { + var selection = this._getSelectionState(); + var item; + + // If the comment the user clicked is currently selected, deselect it. + // This makes it easy to undo things if you clicked by mistake. + if (selection.cursor !== null) { + item = selection.items[selection.cursor]; + if (item.target === inline) { + this._setSelectionState(null); + return; + } + } + + // Otherwise, select the item that the user clicked. This makes it + // easier to resume keyboard operations after using the mouse to do + // something else. + var items = selection.items; + for (var ii = 0; ii < items.length; ii++) { + item = items[ii]; + if (item.target === inline) { + this._setSelectionState(item); + } + } + }, + + _onaction: function(action, e) { + e.kill(); + + var inline = this._getInlineForEvent(e); + var is_ref = false; + + // If we don't have a natural inline object, the user may have clicked + // an action (like "Delete") inside a preview element at the bottom of + // the page. + + // If they did, try to find an associated normal inline to act on, and + // pretend they clicked that instead. This makes the overall state of + // the page more consistent. + + // However, there may be no normal inline (for example, because it is + // on a version of the diff which is not visible). In this case, we + // act by reference. + + if (inline === null) { + var data = e.getNodeData('differential-inline-comment'); + inline = this.getInlineByID(data.id); + if (inline) { + is_ref = true; + } else { + switch (action) { + case 'delete': + this._deleteInlineByID(data.id); + return; + } + } + } + + // TODO: For normal operations, highlight the inline range here. + + switch (action) { + case 'edit': + inline.edit(); + break; + case 'done': + inline.toggleDone(); + break; + case 'delete': + inline.delete(is_ref); + break; + case 'reply': + inline.reply(); + break; + } + }, + + redrawPreview: function() { + // TODO: This isn't the cleanest way to find the preview form, but + // rendering no longer has direct access to it. + var forms = JX.DOM.scry(document.body, 'form', 'transaction-append'); + if (forms.length) { + JX.DOM.invoke(forms[0], 'shouldRefresh'); + } + + // Clear the mouse hover reticle after a substantive edit: we don't get + // a "mouseout" event if the row vanished because of row being removed + // after an edit. + this.resetHover(); + }, + + setFocus: function(node, extended_node) { + this._focusStart = node; + this._focusEnd = extended_node; + this._redrawFocus(); + }, + + _redrawFocus: function() { + var node = this._focusStart; + var extended_node = this._focusEnd || node; + + var reticle = this._getFocusNode(); + if (!node || this.isAsleep()) { + JX.DOM.remove(reticle); + return; + } + + // Outset the reticle some pixels away from the element, so there's some + // space between the focused element and the outline. + var p = JX.Vector.getPos(node); + var s = JX.Vector.getAggregateScrollForNode(node); + + p.add(s).add(-4, -4).setPos(reticle); + // Compute the size we need to extend to the full extent of the focused + // nodes. + JX.Vector.getPos(extended_node) + .add(-p.x, -p.y) + .add(JX.Vector.getDim(extended_node)) + .add(8, 8) + .setDim(reticle); + + JX.DOM.getContentFrame().appendChild(reticle); + }, + + _getFocusNode: function() { + if (!this._focusNode) { + var node = JX.$N('div', {className : 'keyboard-focus-focus-reticle'}); + this._focusNode = node; + } + return this._focusNode; + }, + + _setHoverInline: function(inline) { + this._hoverInline = inline; + + if (inline) { + var changeset = inline.getChangeset(); + + var changeset_id; + var side = inline.getDisplaySide(); + if (side == 'right') { + changeset_id = changeset.getRightChangesetID(); + } else { + changeset_id = changeset.getLeftChangesetID(); + } + + var new_part; + if (inline.isNewFile()) { + new_part = 'N'; + } else { + new_part = 'O'; + } + + var prefix = 'C' + changeset_id + new_part + 'L'; + + var number = inline.getLineNumber(); + var length = inline.getLineLength(); + + try { + var origin = JX.$(prefix + number); + var target = JX.$(prefix + (number + length)); + + this._hoverOrigin = origin; + this._hoverTarget = target; + } catch (error) { + // There may not be any nodes present in the document. A case where + // this occurs is when you reply to a ghost inline which was made + // on lines near the bottom of "long.txt" in an earlier diff, and + // the file was later shortened so those lines no longer exist. For + // more details, see T11662. + + this._hoverOrigin = null; + this._hoverTarget = null; + } + } else { + this._hoverOrigin = null; + this._hoverTarget = null; + } + + this._redrawHover(); + }, + + _setHoverRange: function(origin, target) { + this._hoverOrigin = origin; + this._hoverTarget = target; + + this._redrawHover(); + }, + + resetHover: function() { + this._setHoverInline(null); + + this._hoverOrigin = null; + this._hoverTarget = null; + }, + + _redrawHover: function() { + var reticle = this._getHoverNode(); + if (!this._hoverOrigin || this.isAsleep()) { + JX.DOM.remove(reticle); + return; + } + + JX.DOM.getContentFrame().appendChild(reticle); + + var top = this._hoverOrigin; + var bot = this._hoverTarget; + if (JX.$V(top).y > JX.$V(bot).y) { + var tmp = top; + top = bot; + bot = tmp; + } + + // Find the leftmost cell that we're going to highlight: this is the next + // in the row. In 2up views, it should be directly adjacent. In + // 1up views, we may have to skip over the other line number column. + var l = top; + while (JX.DOM.isType(l, 'th')) { + l = l.nextSibling; + } + + // Find the rightmost cell that we're going to highlight: this is the + // farthest consecutive, adjacent in the row. Sometimes the left + // and right nodes are the same (left side of 2up view); sometimes we're + // going to highlight several nodes (copy + code + coverage). + var r = l; + while (r.nextSibling && JX.DOM.isType(r.nextSibling, 'td')) { + r = r.nextSibling; + } + + var pos = JX.$V(l) + .add(JX.Vector.getAggregateScrollForNode(l)); + + var dim = JX.$V(r) + .add(JX.Vector.getAggregateScrollForNode(r)) + .add(-pos.x, -pos.y) + .add(JX.Vector.getDim(r)); + + var bpos = JX.$V(bot) + .add(JX.Vector.getAggregateScrollForNode(bot)); + dim.y = (bpos.y - pos.y) + JX.Vector.getDim(bot).y; + + pos.setPos(reticle); + dim.setDim(reticle); + + JX.DOM.show(reticle); + }, + + _getHoverNode: function() { + if (!this._hoverNode) { + var attributes = { + className: 'differential-reticle' + }; + this._hoverNode = JX.$N('div', attributes); + } + + return this._hoverNode; + }, + + _deleteInlineByID: function(id) { + var uri = this.getInlineURI(); + var data = { + op: 'refdelete', + id: id + }; + + var handler = JX.bind(this, this.redrawPreview); + + new JX.Workflow(uri, data) + .setHandler(handler) + .start(); + }, + + _getInlineForEvent: function(e) { + var node = e.getNode('differential-changeset'); + if (!node) { + return null; + } + + var changeset = this.getChangesetForNode(node); + + var inline_row = e.getNode('inline-row'); + return changeset.getInlineForRow(inline_row); + }, + + getLineNumberFromHeader: function(th) { + try { + return parseInt(th.id.match(/^C\d+[ON]L(\d+)$/)[1], 10); + } catch (x) { + return null; + } + }, + + getDisplaySideFromHeader: function(th) { + return (th.parentNode.firstChild != th) ? 'right' : 'left'; + }, + + _onrangedown: function(e) { + // NOTE: We're allowing touch events through, including "touchstart". We + // need to kill the "touchstart" event so the page doesn't scroll. + if (e.isRightButton()) { + return; + } + + if (this._rangeActive) { + return; + } + + var target = e.getTarget(); + var number = this.getLineNumberFromHeader(target); + if (!number) { + return; + } + + e.kill(); + this._rangeActive = true; + + this._rangeOrigin = target; + this._rangeTarget = target; + + this._setHoverRange(this._rangeOrigin, this._rangeTarget); + }, + + _onrangemove: function(e) { + if (e.getIsTouchEvent()) { + return; + } + + var is_out = (e.getType() == 'mouseout'); + var target = e.getTarget(); + + this._updateRange(target, is_out); + }, + + _updateRange: function(target, is_out) { + // Don't update the range if this "" doesn't correspond to a line + // number. For instance, this may be a dead line number, like the empty + // line numbers on the left hand side of a newly added file. + var number = this.getLineNumberFromHeader(target); + if (!number) { + return; + } + + if (this._rangeActive) { + var origin = this._hoverOrigin; + + // Don't update the reticle if we're selecting a line range and the + // "" under the cursor is on the wrong side of the file. You can + // only leave inline comments on the left or right side of a file, not + // across lines on both sides. + var origin_side = this.getDisplaySideFromHeader(origin); + var target_side = this.getDisplaySideFromHeader(target); + if (origin_side != target_side) { + return; + } + + // Don't update the reticle if we're selecting a line range and the + // "" under the cursor corresponds to a different file. You can + // only leave inline comments on lines in a single file, not across + // multiple files. + var origin_table = JX.DOM.findAbove(origin, 'table'); + var target_table = JX.DOM.findAbove(target, 'table'); + if (origin_table != target_table) { + return; + } + } + + if (is_out) { + if (this._rangeActive) { + // If we're dragging a range, just leave the state as it is. This + // allows you to drag over something invalid while selecting a + // range without the range flickering or getting lost. + } else { + // Otherwise, clear the current range. + this.resetHover(); + } + return; + } + + if (this._rangeActive) { + this._rangeTarget = target; + } else { + this._rangeOrigin = target; + this._rangeTarget = target; + } + + this._setHoverRange(this._rangeOrigin, this._rangeTarget); + }, + + _onrangetouchmove: function(e) { + if (!this._rangeActive) { + return; + } + + // NOTE: The target of a "touchmove" event is bogus. Use dark magic to + // identify the actual target. Some day, this might move into the core + // libraries. If this doesn't work, just bail. + + var target; + try { + var raw_event = e.getRawEvent(); + var touch = raw_event.touches[0]; + target = document.elementFromPoint(touch.clientX, touch.clientY); + } catch (ex) { + return; + } + + if (!JX.DOM.isType(target, 'th')) { + return; + } + + this._updateRange(target, false); + }, + + _onrangeup: function(e) { + if (!this._rangeActive) { + return; + } + + e.kill(); + + var origin = this._rangeOrigin; + var target = this._rangeTarget; + + // If the user dragged a range from the bottom to the top, swap the node + // order around. + if (JX.$V(origin).y > JX.$V(target).y) { + var tmp = target; + target = origin; + origin = tmp; + } + + var node = JX.DOM.findAbove(origin, null, 'differential-changeset'); + var changeset = this.getChangesetForNode(node); + + changeset.newInlineForRange(origin, target); + + this._rangeActive = false; + this._rangeOrigin = null; + this._rangeTarget = null; + + this.resetHover(); + }, + + _redrawBanner: function() { + var node = this._getBannerNode(); + var changeset = this._getVisibleChangeset(); + + // Don't do anything if nothing has changed. This seems to avoid some + // flickering issues in Safari, at least. + if (this._bannerChangeset === changeset) { + return; + } + this._bannerChangeset = changeset; + + if (!changeset) { + JX.DOM.remove(node); + return; + } + + var icon = new JX.PHUIXIconView() + .setIcon(changeset.getIcon()) + .getNode(); + JX.DOM.setContent(node, [icon, ' ', changeset.getDisplayPath()]); + + document.body.appendChild(node); + }, + + _getBannerNode: function() { + if (!this._bannerNode) { + var attributes = { + className: 'diff-banner', + id: 'diff-banner' + }; + + this._bannerNode = JX.$N('div', attributes); + } + + return this._bannerNode; + }, + + _getVisibleChangeset: function() { + if (this.isAsleep()) { + return null; + } + + if (JX.Device.getDevice() != 'desktop') { + return null; + } + + // Never show the banner if we're very near the top of the page. + var margin = 480; + var s = JX.Vector.getScroll(); + if (s.y < margin) { + return null; + } + + var v = JX.Vector.getViewport(); + for (var ii = 0; ii < this._changesets.length; ii++) { + var changeset = this._changesets[ii]; + var c = changeset.getVectors(); + + // If the changeset starts above the upper half of the screen... + if (c.pos.y < (s.y + (v.y / 2))) { + // ...and ends below the lower half of the screen, this is the + // current visible changeset. + if ((c.pos.y + c.dim.y) > (s.y + (v.y / 2))) { + return changeset; + } + } + } + + return null; + } + } + +}); diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js new file mode 100644 index 0000000000..e66fb544c5 --- /dev/null +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -0,0 +1,735 @@ +/** + * @provides phabricator-diff-inline + * @requires javelin-dom + * @javelin + */ + +JX.install('DiffInline', { + + construct : function() { + }, + + members: { + _id: null, + _phid: null, + _changesetID: null, + _row: null, + _hidden: false, + _number: null, + _length: null, + _displaySide: null, + _isNewFile: null, + _undoRow: null, + _replyToCommentPHID: null, + _originalText: null, + + _isDeleted: false, + _isInvisible: false, + _isLoading: false, + + _changeset: null, + _objective: null, + + _isDraft: null, + _isFixed: null, + + bindToRow: function(row) { + this._row = row; + this._objective.setAnchor(this._row); + + var row_data = JX.Stratcom.getData(row); + row_data.inline = this; + this._hidden = row_data.hidden || false; + + // TODO: Get smarter about this once we do more editing, this is pretty + // hacky. + var comment = JX.DOM.find(row, 'div', 'differential-inline-comment'); + var data = JX.Stratcom.getData(comment); + + this._id = data.id; + this._phid = data.phid; + + // TODO: This is very, very, very, very, very, very, very hacky. + var td = comment.parentNode; + var th = td.previousSibling; + if (th.parentNode.firstChild != th) { + this._displaySide = 'right'; + } else { + this._displaySide = 'left'; + } + + this._number = parseInt(data.number, 10); + this._length = parseInt(data.length, 10); + this._originalText = data.original; + this._isNewFile = + (this.getDisplaySide() == 'right') || + (data.left != data.right); + + this._replyToCommentPHID = data.replyToCommentPHID; + + this._isDraft = data.isDraft; + this._isFixed = data.isFixed; + this._isGhost = data.isGhost; + + this._changesetID = data.changesetID; + + this.updateObjective(); + this.setInvisible(false); + + return this; + }, + + bindToRange: function(data) { + this._displaySide = data.displaySide; + this._number = parseInt(data.number, 10); + this._length = parseInt(data.length, 10); + this._isNewFile = data.isNewFile; + this._changesetID = data.changesetID; + + // Insert the comment after any other comments which already appear on + // the same row. + var parent_row = JX.DOM.findAbove(data.target, 'tr'); + var target_row = parent_row.nextSibling; + while (target_row && JX.Stratcom.hasSigil(target_row, 'inline-row')) { + target_row = target_row.nextSibling; + } + + var row = this._newRow(); + parent_row.parentNode.insertBefore(row, target_row); + + this.setInvisible(true); + + return this; + }, + + bindToReply: function(inline) { + this._displaySide = inline._displaySide; + this._number = inline._number; + this._length = inline._length; + this._isNewFile = inline._isNewFile; + this._changesetID = inline._changesetID; + + this._replyToCommentPHID = inline._phid; + + var changeset = this.getChangeset(); + + // We're going to figure out where in the document to position the new + // inline. Normally, it goes after any existing inline rows (so if + // several inlines reply to the same line, they appear in chronological + // order). + + // However: if inlines are threaded, we want to put the new inline in + // the right place in the thread. This might be somewhere in the middle, + // so we need to do a bit more work to figure it out. + + // To find the right place in the thread, we're going to look for any + // inline which is at or above the level of the comment we're replying + // to. This means we've reached a new fork of the thread, and should + // put our new inline before the comment we found. + var ancestor_map = {}; + var ancestor = inline; + var reply_phid; + while (ancestor) { + reply_phid = ancestor.getReplyToCommentPHID(); + if (!reply_phid) { + break; + } + ancestor_map[reply_phid] = true; + ancestor = changeset.getInlineByPHID(reply_phid); + } + + var parent_row = inline._row; + var target_row = parent_row.nextSibling; + while (target_row && JX.Stratcom.hasSigil(target_row, 'inline-row')) { + var target = changeset.getInlineForRow(target_row); + reply_phid = target.getReplyToCommentPHID(); + + // If we found an inline which is replying directly to some ancestor + // of this new comment, this is where the new rows go. + if (ancestor_map.hasOwnProperty(reply_phid)) { + break; + } + + target_row = target_row.nextSibling; + } + + var row = this._newRow(); + parent_row.parentNode.insertBefore(row, target_row); + + this.setInvisible(true); + + return this; + }, + + setChangeset: function(changeset) { + this._changeset = changeset; + + var objectives = changeset.getChangesetList().getObjectives(); + this._objective = objectives.newObjective() + .setCallback(JX.bind(this, this._onobjective)); + this.updateObjective(); + + return this; + }, + + getChangeset: function() { + return this._changeset; + }, + + _onobjective: function() { + this.getChangeset().getChangesetList().selectInline(this); + }, + + updateObjective: function() { + var objective = this._objective; + + if (this.isHidden() || this._isDeleted) { + objective.hide(); + return; + } + + var changeset = this.getChangeset(); + if (!changeset.isVisible()) { + objective.hide(); + return; + } + + var icon = 'fa-comment'; + var color = 'bluegrey'; + + if (this._isDraft) { + // This inline is an unsubmitted draft. + icon = 'fa-pencil'; + } else if (this._isFixed) { + // This inline has been marked done. + icon = 'fa-check'; + color = 'grey'; + } else if (this._isGhost) { + icon = 'fa-comment-o'; + color = 'grey'; + } + + objective + .setIcon(icon) + .setColor(color) + .show(); + }, + + canReply: function() { + if (!this._hasAction('reply')) { + return false; + } + + return true; + }, + + canEdit: function() { + if (!this._hasAction('edit')) { + return false; + } + + return true; + }, + + canDone: function() { + if (!JX.DOM.scry(this._row, 'input', 'differential-inline-done').length) { + return false; + } + + return true; + }, + + canHide: function() { + if (!JX.DOM.scry(this._row, 'a', 'hide-inline').length) { + return false; + } + + return true; + }, + + getRawText: function() { + return this._originalText; + }, + + _hasAction: function(action) { + var nodes = JX.DOM.scry(this._row, 'a', 'differential-inline-' + action); + return (nodes.length > 0); + }, + + _newRow: function() { + var attributes = { + sigil: 'inline-row' + }; + + var row = JX.$N('tr', attributes); + + JX.Stratcom.getData(row).inline = this; + this._row = row; + this._objective.setAnchor(this._row); + + this._id = null; + this._phid = null; + this._hidden = false; + + this._originalText = null; + + return row; + }, + + setHidden: function(hidden) { + this._hidden = hidden; + + JX.DOM.alterClass(this._row, 'inline-hidden', this._hidden); + + var op; + if (hidden) { + op = 'hide'; + } else { + op = 'show'; + } + + var inline_uri = this._getInlineURI(); + var comment_id = this._id; + + new JX.Workflow(inline_uri, {op: op, ids: comment_id}) + .setHandler(JX.bag) + .start(); + + this._didUpdate(true); + }, + + isHidden: function() { + return this._hidden; + }, + + toggleDone: function() { + var uri = this._getInlineURI(); + var data = { + op: 'done', + id: this._id + }; + + var ondone = JX.bind(this, this._ondone); + + new JX.Workflow(uri, data) + .setHandler(ondone) + .start(); + }, + + _ondone: function(response) { + var checkbox = JX.DOM.find( + this._row, + 'input', + 'differential-inline-done'); + + checkbox.checked = (response.isChecked ? 'checked' : null); + + var comment = JX.DOM.findAbove( + checkbox, + 'div', + 'differential-inline-comment'); + + JX.DOM.alterClass(comment, 'inline-is-done', response.isChecked); + + // NOTE: This is marking the inline as having an unsubmitted checkmark, + // as opposed to a submitted checkmark. This is different from the + // top-level "draft" state of unsubmitted comments. + JX.DOM.alterClass(comment, 'inline-state-is-draft', response.draftState); + + this._isFixed = response.isChecked; + + this._didUpdate(); + }, + + create: function(text) { + var uri = this._getInlineURI(); + var handler = JX.bind(this, this._oncreateresponse); + var data = this._newRequestData('new', text); + + this.setLoading(true); + + new JX.Request(uri, handler) + .setData(data) + .send(); + }, + + reply: function(text) { + var changeset = this.getChangeset(); + return changeset.newInlineReply(this, text); + }, + + edit: function(text) { + var uri = this._getInlineURI(); + var handler = JX.bind(this, this._oneditresponse); + var data = this._newRequestData('edit', text || null); + + this.setLoading(true); + + new JX.Request(uri, handler) + .setData(data) + .send(); + }, + + delete: function(is_ref) { + var uri = this._getInlineURI(); + var handler = JX.bind(this, this._ondeleteresponse); + + // NOTE: This may be a direct delete (the user clicked on the inline + // itself) or a "refdelete" (the user clicked somewhere else, like the + // preview, but the inline is present on the page). + + // For a "refdelete", we prompt the user to confirm that they want to + // delete the comment, because they can not undo deletions from the + // preview. We could jump the user to the inline instead, but this would + // be somewhat disruptive and make deleting several comments more + // difficult. + + var op; + if (is_ref) { + op = 'refdelete'; + } else { + op = 'delete'; + } + + var data = this._newRequestData(op); + + this.setLoading(true); + + new JX.Workflow(uri, data) + .setHandler(handler) + .start(); + }, + + getDisplaySide: function() { + return this._displaySide; + }, + + getLineNumber: function() { + return this._number; + }, + + getLineLength: function() { + return this._length; + }, + + isNewFile: function() { + return this._isNewFile; + }, + + getID: function() { + return this._id; + }, + + getPHID: function() { + return this._phid; + }, + + getChangesetID: function() { + return this._changesetID; + }, + + getReplyToCommentPHID: function() { + return this._replyToCommentPHID; + }, + + setDeleted: function(deleted) { + this._isDeleted = deleted; + this._redraw(); + return this; + }, + + setInvisible: function(invisible) { + this._isInvisible = invisible; + this._redraw(); + return this; + }, + + setLoading: function(loading) { + this._isLoading = loading; + this._redraw(); + return this; + }, + + _newRequestData: function(operation, text) { + return { + op: operation, + id: this._id, + on_right: ((this.getDisplaySide() == 'right') ? 1 : 0), + renderer: this.getChangeset().getRenderer(), + number: this.getLineNumber(), + length: this.getLineLength(), + is_new: this.isNewFile(), + changesetID: this.getChangesetID(), + replyToCommentPHID: this.getReplyToCommentPHID() || '', + text: text || '' + }; + }, + + _oneditresponse: function(response) { + var rows = JX.$H(response).getNode(); + + this._drawEditRows(rows); + + this.setLoading(false); + this.setInvisible(true); + }, + + _oncreateresponse: function(response) { + var rows = JX.$H(response).getNode(); + + this._drawEditRows(rows); + }, + + _ondeleteresponse: function() { + this._drawUndeleteRows(); + + this.setLoading(false); + this.setDeleted(true); + + this._didUpdate(); + }, + + _drawUndeleteRows: function() { + return this._drawUndoRows('undelete', this._row); + }, + + _drawUneditRows: function(text) { + return this._drawUndoRows('unedit', null, text); + }, + + _drawUndoRows: function(mode, cursor, text) { + var templates = this.getChangeset().getUndoTemplates(); + + var template; + if (this.getDisplaySide() == 'right') { + template = templates.r; + } else { + template = templates.l; + } + template = JX.$H(template).getNode(); + + this._undoRow = this._drawRows(template, cursor, mode, text); + }, + + _drawEditRows: function(rows) { + return this._drawRows(rows, null, 'edit'); + }, + + _drawRows: function(rows, cursor, type, text) { + var first_row = JX.DOM.scry(rows, 'tr')[0]; + var first_meta; + var row = first_row; + var anchor = cursor || this._row; + cursor = cursor || this._row.nextSibling; + + var next_row; + while (row) { + // Grab this first, since it's going to change once we insert the row + // into the document. + next_row = row.nextSibling; + + // Bind edit and undo rows to this DiffInline object so that + // interactions like hovering work properly. + JX.Stratcom.getData(row).inline = this; + + anchor.parentNode.insertBefore(row, cursor); + cursor = row; + + var row_meta = { + node: row, + type: type, + text: text || null, + listeners: [] + }; + + if (!first_meta) { + first_meta = row_meta; + } + + if (type == 'edit') { + row_meta.listeners.push( + JX.DOM.listen( + row, + ['submit', 'didSyntheticSubmit'], + 'inline-edit-form', + JX.bind(this, this._onsubmit, row_meta))); + + row_meta.listeners.push( + JX.DOM.listen( + row, + 'click', + 'inline-edit-cancel', + JX.bind(this, this._oncancel, row_meta))); + } else { + row_meta.listeners.push( + JX.DOM.listen( + row, + 'click', + 'differential-inline-comment-undo', + JX.bind(this, this._onundo, row_meta))); + } + + // If the row has a textarea, focus it. This allows the user to start + // typing a comment immediately after a "new", "edit", or "reply" + // action. + var textareas = JX.DOM.scry( + row, + 'textarea', + 'differential-inline-comment-edit-textarea'); + if (textareas.length) { + var area = textareas[0]; + area.focus(); + + var length = area.value.length; + JX.TextAreaUtils.setSelectionRange(area, length, length); + } + + row = next_row; + } + + JX.Stratcom.invoke('resize'); + + return first_meta; + }, + + _onsubmit: function(row, e) { + e.kill(); + + var handler = JX.bind(this, this._onsubmitresponse, row); + + this.setLoading(true); + + JX.Workflow.newFromForm(e.getTarget()) + .setHandler(handler) + .start(); + }, + + _onundo: function(row, e) { + e.kill(); + + this._removeRow(row); + + if (row.type == 'undelete') { + var uri = this._getInlineURI(); + var data = this._newRequestData('undelete'); + var handler = JX.bind(this, this._onundelete); + + this.setDeleted(false); + this.setLoading(true); + + new JX.Request(uri, handler) + .setData(data) + .send(); + } + + if (row.type == 'unedit') { + if (this.getID()) { + this.edit(row.text); + } else { + this.create(row.text); + } + } + }, + + _onundelete: function() { + this.setLoading(false); + this._didUpdate(); + }, + + _oncancel: function(row, e) { + e.kill(); + + var text = this._readText(row.node); + if (text && text.length && (text != this._originalText)) { + this._drawUneditRows(text); + } + + this._removeRow(row); + + this.setInvisible(false); + + this._didUpdate(true); + }, + + _readText: function(row) { + var textarea; + try { + textarea = JX.DOM.find( + row, + 'textarea', + 'differential-inline-comment-edit-textarea'); + } catch (ex) { + return null; + } + + return textarea.value; + }, + + _onsubmitresponse: function(row, response) { + this._removeRow(row); + + this.setLoading(false); + this.setInvisible(false); + + this._onupdate(response); + }, + + _onupdate: function(response) { + var new_row; + if (response.markup) { + new_row = this._drawEditRows(JX.$H(response.markup).getNode()).node; + } + + // TODO: Save the old row so the action it's undo-able if it was a + // delete. + var remove_old = true; + if (remove_old) { + JX.DOM.remove(this._row); + } + + this.bindToRow(new_row); + + this._didUpdate(); + }, + + _didUpdate: function(local_only) { + // After making changes to inline comments, refresh the transaction + // preview at the bottom of the page. + if (!local_only) { + this.getChangeset().getChangesetList().redrawPreview(); + } + + this.updateObjective(); + + this.getChangeset().getChangesetList().redrawCursor(); + this.getChangeset().getChangesetList().resetHover(); + + // Emit a resize event so that UI elements like the keyboard focus + // reticle can redraw properly. + JX.Stratcom.invoke('resize'); + }, + + _redraw: function() { + var is_invisible = (this._isInvisible || this._isDeleted); + var is_loading = (this._isLoading); + + var row = this._row; + JX.DOM.alterClass(row, 'differential-inline-hidden', is_invisible); + JX.DOM.alterClass(row, 'differential-inline-loading', is_loading); + }, + + _removeRow: function(row) { + JX.DOM.remove(row.node); + for (var ii = 0; ii < row.listeners.length; ii++) { + row.listeners[ii].remove(); + } + }, + + _getInlineURI: function() { + var changeset = this.getChangeset(); + var list = changeset.getChangesetList(); + return list.getInlineURI(); + } + } + +}); diff --git a/webroot/rsrc/js/application/diff/ScrollObjective.js b/webroot/rsrc/js/application/diff/ScrollObjective.js new file mode 100644 index 0000000000..4e6fa55d6a --- /dev/null +++ b/webroot/rsrc/js/application/diff/ScrollObjective.js @@ -0,0 +1,121 @@ +/** + * @provides phabricator-scroll-objective + * @requires javelin-dom + * javelin-util + * javelin-stratcom + * javelin-install + * javelin-workflow + * @javelin + */ + + +JX.install('ScrollObjective', { + + construct : function() { + var node = this.getNode(); + + var onclick = JX.bind(this, this._onclick); + JX.DOM.listen(node, 'click', null, onclick); + }, + + members: { + _list: null, + + _node: null, + _anchor: null, + + _visible: false, + _callback: false, + + getNode: function() { + if (!this._node) { + var attributes = { + className: 'scroll-objective' + }; + + var content = this._getIconObject().getNode(); + + var node = JX.$N('div', attributes, content); + + this._node = node; + } + + return this._node; + }, + + setCallback: function(callback) { + this._callback = callback; + return this; + }, + + setObjectiveList: function(list) { + this._list = list; + return this; + }, + + _getIconObject: function() { + if (!this._iconObject) { + this._iconObject = new JX.PHUIXIconView(); + } + return this._iconObject; + }, + + _onclick: function(e) { + (this._callback && this._callback(e)); + + if (e.getPrevented()) { + return; + } + + e.kill(); + + // This is magic to account for the banner, and should probably be made + // less hard-coded. + var buffer = 48; + + JX.DOM.scrollToPosition(null, JX.$V(this.getAnchor()).y - buffer); + }, + + setAnchor: function(node) { + this._anchor = node; + return this; + }, + + getAnchor: function() { + return this._anchor; + }, + + setIcon: function(icon) { + this._getIconObject().setIcon(icon); + return this; + }, + + setColor: function(color) { + this._getIconObject().setColor(color); + return this; + }, + + setTooltip: function(tip) { + var node = this._getIconObject().getNode(); + JX.Stratcom.addSigil(node, 'has-tooltip'); + JX.Stratcom.getData(node).tip = tip; + JX.Stratcom.getData(node).align = 'W'; + return this; + }, + + show: function() { + this._visible = true; + return this; + }, + + hide: function() { + this._visible = false; + }, + + isVisible: function() { + return this._visible; + } + + } + +}); diff --git a/webroot/rsrc/js/application/diff/ScrollObjectiveList.js b/webroot/rsrc/js/application/diff/ScrollObjectiveList.js new file mode 100644 index 0000000000..f5beb751e6 --- /dev/null +++ b/webroot/rsrc/js/application/diff/ScrollObjectiveList.js @@ -0,0 +1,139 @@ +/** + * @provides phabricator-scroll-objective-list + * @requires javelin-dom + * javelin-util + * javelin-stratcom + * javelin-install + * javelin-workflow + * phabricator-scroll-objective + * @javelin + */ + + +JX.install('ScrollObjectiveList', { + + construct : function() { + this._objectives = []; + + var onresize = JX.bind(this, this._dirty); + JX.Stratcom.listen('resize', null, onresize); + }, + + members: { + _objectives: null, + _visible: false, + _trigger: null, + + newObjective: function() { + var objective = new JX.ScrollObjective() + .setObjectiveList(this); + + this._objectives.push(objective); + this._getNode().appendChild(objective.getNode()); + + this._dirty(); + + return objective; + }, + + show: function() { + this._visible = true; + this._dirty(); + return this; + }, + + hide: function() { + this._visible = false; + this._dirty(); + return this; + }, + + _getNode: function() { + if (!this._node) { + var node = new JX.$N('div', {className: 'scroll-objective-list'}); + this._node = node; + } + return this._node; + }, + + _dirty: function() { + if (this._trigger !== null) { + return; + } + + this._trigger = setTimeout(JX.bind(this, this._redraw), 0); + }, + + _redraw: function() { + this._trigger = null; + + var node = this._getNode(); + + var is_visible = + (this._visible) && + (JX.Device.getDevice() == 'desktop') && + (this._objectives.length); + + if (!is_visible) { + JX.DOM.remove(node); + return; + } + + document.body.appendChild(node); + + var d = JX.Vector.getDocument(); + + var list_dimensions = JX.Vector.getDim(node); + var icon_height = 16; + var list_y = (list_dimensions.y - icon_height); + + var ii; + var offset; + + // First, build a list of all the items we're going to show. + var items = []; + for (ii = 0; ii < this._objectives.length; ii++) { + var objective = this._objectives[ii]; + var objective_node = objective.getNode(); + + var anchor = objective.getAnchor(); + if (!anchor || !objective.isVisible()) { + JX.DOM.remove(objective_node); + continue; + } + + offset = (JX.$V(anchor).y / d.y) * (list_y); + + items.push({ + offset: offset, + node: objective_node + }); + } + + // Now, sort it from top to bottom. + items.sort(function(u, v) { + return u.offset - v.offset; + }); + + // Lay out the items in the objective list, leaving a minimum amount + // of space between them so they do not overlap. + var min = null; + for (ii = 0; ii < items.length; ii++) { + var item = items[ii]; + + offset = item.offset; + + if (min !== null) { + offset = Math.max(offset, min); + } + min = offset + 15; + + item.node.style.top = offset + 'px'; + node.appendChild(item.node); + } + + } + + } + +}); diff --git a/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js b/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js deleted file mode 100644 index 7e6015d969..0000000000 --- a/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js +++ /dev/null @@ -1,382 +0,0 @@ -/** - * @provides differential-inline-comment-editor - * @requires javelin-dom - * javelin-util - * javelin-stratcom - * javelin-install - * javelin-request - * javelin-workflow - */ - -JX.install('DifferentialInlineCommentEditor', { - - construct : function(uri) { - this._uri = uri; - }, - - events : ['done'], - - members : { - _uri : null, - _undoText : null, - _completed: false, - _skipOverInlineCommentRows : function(node) { - // TODO: Move this semantic information out of class names. - while (node && node.className.indexOf('inline') !== -1) { - node = node.nextSibling; - } - return node; - }, - _buildRequestData : function() { - return { - op : this.getOperation(), - on_right : this.getOnRight(), - id : this.getID(), - number : this.getLineNumber(), - is_new : (this.getIsNew() ? 1 : 0), - length : this.getLength(), - changesetID : this.getChangesetID(), - text : this.getText() || '', - renderer: this.getRenderer(), - replyToCommentPHID: this.getReplyToCommentPHID() || '', - }; - }, - _draw : function(content, exact_row) { - var row = this.getRow(); - var table = this.getTable(); - var target = exact_row ? row : this._skipOverInlineCommentRows(row); - - function copyRows(dst, src, before) { - var rows = JX.DOM.scry(src, 'tr'); - for (var ii = 0; ii < rows.length; ii++) { - - // Find the table this belongs to. If it's a sub-table, like a - // table in an inline comment, don't copy it. - if (JX.DOM.findAbove(rows[ii], 'table') !== src) { - continue; - } - - if (before) { - dst.insertBefore(rows[ii], before); - } else { - dst.appendChild(rows[ii]); - } - } - return rows; - } - - return copyRows(table, content, target); - }, - _removeUndoLink : function() { - var rows = JX.DifferentialInlineCommentEditor._undoRows; - if (rows) { - for (var ii = 0; ii < rows.length; ii++) { - JX.DOM.remove(rows[ii]); - } - } - JX.DifferentialInlineCommentEditor._undoRows = []; - }, - _undo : function() { - this._removeUndoLink(); - - if (this._undoText) { - this.setText(this._undoText); - } else { - this.setOperation('undelete'); - } - - this.start(); - }, - _registerUndoListener : function() { - if (!JX.DifferentialInlineCommentEditor._activeEditor) { - JX.Stratcom.listen( - 'click', - 'differential-inline-comment-undo', - function(e) { - JX.DifferentialInlineCommentEditor._activeEditor._undo(); - e.kill(); - }); - } - JX.DifferentialInlineCommentEditor._activeEditor = this; - }, - _setRowState : function(state) { - var is_hidden = (state == 'hidden'); - var is_loading = (state == 'loading'); - var row = this.getRow(); - JX.DOM.alterClass(row, 'differential-inline-hidden', is_hidden); - JX.DOM.alterClass(row, 'differential-inline-loading', is_loading); - }, - _didContinueWorkflow : function(response) { - var drawn = this._draw(JX.$H(response).getNode()); - - var op = this.getOperation(); - if (op == 'edit') { - this._setRowState('hidden'); - } - - JX.DOM.find( - drawn[0], - 'textarea', - 'differential-inline-comment-edit-textarea').focus(); - - var oncancel = JX.bind(this, function(e) { - e.kill(); - - this._didCancelWorkflow(); - - if (op == 'edit') { - this._setRowState('visible'); - } - - JX.DOM.remove(drawn[0]); - }); - JX.DOM.listen(drawn[0], 'click', 'inline-edit-cancel', oncancel); - - var onsubmit = JX.bind(this, function(e) { - e.kill(); - - JX.Workflow.newFromForm(e.getTarget()) - .setHandler(JX.bind(this, function(response) { - JX.DOM.remove(drawn[0]); - if (op == 'edit') { - this._setRowState('visible'); - } - this._didCompleteWorkflow(response); - })) - .start(); - - JX.DOM.alterClass(drawn[0], 'differential-inline-loading', true); - }); - JX.DOM.listen( - drawn[0], - ['submit', 'didSyntheticSubmit'], - 'inline-edit-form', - onsubmit); - }, - - - _didCompleteWorkflow : function(response) { - var op = this.getOperation(); - - // We don't get any markup back if the user deletes a comment, or saves - // an empty comment (which effects a delete). - if (response.markup) { - this._draw(JX.$H(response.markup).getNode()); - } - - if (op == 'delete' || op == 'refdelete') { - this._undoText = null; - this._drawUndo(); - } else { - this._removeUndoLink(); - } - - // These operations remove the old row (edit adds a new row first). - var remove_old = (op == 'edit' || op == 'delete' || op == 'refdelete'); - if (remove_old) { - this._setRowState('hidden'); - } - - if (op == 'undelete') { - this._setRowState('visible'); - } - - this._completed = true; - - this._didUpdate(); - this.invoke('done'); - }, - - - _didCancelWorkflow : function() { - this.invoke('done'); - - switch (this.getOperation()) { - case 'delete': - case 'refdelete': - if (!this._completed) { - this._setRowState('visible'); - } - return; - case 'undelete': - return; - } - - var textarea; - try { - textarea = JX.DOM.find( - document.body, // TODO: use getDialogRootNode() when available - 'textarea', - 'differential-inline-comment-edit-textarea'); - } catch (ex) { - // The close handler is called whenever the dialog closes, even if the - // user closed it by completing the workflow with "Save". The - // JX.Workflow API should probably be refined to allow programmatic - // distinction of close caused by 'cancel' vs 'submit'. Testing for - // presence of the textarea serves as a proxy for detecting a 'cancel'. - return; - } - - var text = textarea.value; - - // If the user hasn't edited the text (i.e., no change from original for - // 'edit' or no text at all), don't offer them an undo. - if (text == this.getOriginalText() || text === '') { - return; - } - - // Save the text so we can 'undo' back to it. - this._undoText = text; - - this._drawUndo(); - }, - - _drawUndo: function() { - var templates = this.getTemplates(); - var template = this.getOnRight() ? templates.r : templates.l; - template = JX.$H(template).getNode(); - - // NOTE: Operation order matters here; we can't remove anything until - // after we draw the new rows because _draw uses the old rows to figure - // out where to place the comment. - - // We use 'exact_row' to put the "undo" text directly above the affected - // comment. - var exact_row = true; - var rows = this._draw(template, exact_row); - - this._removeUndoLink(); - - JX.DifferentialInlineCommentEditor._undoRows = rows; - }, - - _onBusyWorkflow: function() { - // If the user clicks the "Jump to Inline" button, scroll to the row - // being edited. - JX.DOM.scrollTo(this.getRow()); - }, - - start : function() { - var op = this.getOperation(); - - // The user is already editing a comment, we're going to give them an - // error message. - if (op == 'busy') { - var onbusy = JX.bind(this, this._onBusyWorkflow); - - new JX.Workflow(this._uri, {op: op}) - .setHandler(onbusy) - .start(); - - return this; - } - - this._registerUndoListener(); - var data = this._buildRequestData(); - - if (op == 'delete' || op == 'refdelete' || op == 'undelete') { - this._setRowState('loading'); - - var oncomplete = JX.bind(this, this._didCompleteWorkflow); - var oncancel = JX.bind(this, this._didCancelWorkflow); - - new JX.Workflow(this._uri, data) - .setHandler(oncomplete) - .setCloseHandler(oncancel) - .start(); - } else { - var handler = JX.bind(this, this._didContinueWorkflow); - - if (op == 'edit') { - this._setRowState('loading'); - } - - new JX.Request(this._uri, handler) - .setData(data) - .send(); - } - - return this; - }, - - deleteByID: function(id) { - var data = { - op: 'refdelete', - id: id - }; - - new JX.Workflow(this._uri, data) - .setHandler(JX.bind(this, function() { - this._didUpdate(); - })) - .start(); - }, - - toggleCheckbox: function(id, checkbox) { - var data = { - op: 'done', - id: id - }; - - new JX.Workflow(this._uri, data) - .setHandler(JX.bind(this, function(r) { - checkbox.checked = !checkbox.checked; - - var comment = JX.DOM.findAbove( - checkbox, - 'div', - 'differential-inline-comment'); - JX.DOM.alterClass(comment, 'inline-is-done', !!checkbox.checked); - JX.DOM.alterClass(comment, 'inline-state-is-draft', r.draftState); - - this._didUpdate(); - })) - .start(); - }, - - _didUpdate: function() { - // After making changes to inline comments, refresh the transaction - // preview at the bottom of the page. - - // TODO: This isn't the cleanest way to find the preview form, but - // rendering no longer has direct access to it. - var forms = JX.DOM.scry(document.body, 'form', 'transaction-append'); - if (forms.length) { - JX.DOM.invoke(forms[0], 'shouldRefresh'); - } - } - - }, - - statics : { - /** - * Global refernece to the 'undo' rows currently rendered in the document. - */ - _undoRows : null, - - /** - * Global listener for the 'undo' click associated with the currently - * displayed 'undo' link. When an editor is start()ed, it becomes the active - * editor. - */ - _activeEditor : null - }, - - properties : { - operation : null, - row : null, - table : null, - onRight : null, - ID : null, - lineNumber : null, - changesetID : null, - length : null, - isNew : null, - text : null, - templates : null, - originalText : null, - renderer: null, - replyToCommentPHID: null - } - -}); diff --git a/webroot/rsrc/js/application/differential/behavior-comment-jump.js b/webroot/rsrc/js/application/differential/behavior-comment-jump.js deleted file mode 100644 index 53d43fd05e..0000000000 --- a/webroot/rsrc/js/application/differential/behavior-comment-jump.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @provides javelin-behavior-differential-comment-jump - * @requires javelin-behavior - * javelin-stratcom - * javelin-dom - */ - -JX.behavior('differential-comment-jump', function() { - function handle_jump(offset) { - return function(e) { - var parent = JX.$('differential-review-stage'); - var clicked = e.getNode('differential-inline-comment'); - var inlines = JX.DOM.scry(parent, 'div', 'differential-inline-comment'); - var jumpto = null; - - for (var ii = 0; ii < inlines.length; ii++) { - if (inlines[ii] == clicked) { - jumpto = inlines[(ii + offset + inlines.length) % inlines.length]; - break; - } - } - JX.Stratcom.invoke('differential-toggle-file-request', null, { - element: jumpto - }); - JX.DOM.scrollTo(jumpto); - e.kill(); - }; - } - - JX.Stratcom.listen('click', 'differential-inline-prev', handle_jump(-1)); - JX.Stratcom.listen('click', 'differential-inline-next', handle_jump(+1)); -}); diff --git a/webroot/rsrc/js/application/differential/behavior-comment-preview.js b/webroot/rsrc/js/application/differential/behavior-comment-preview.js index 283e6ea12c..beb9f9a5d9 100644 --- a/webroot/rsrc/js/application/differential/behavior-comment-preview.js +++ b/webroot/rsrc/js/application/differential/behavior-comment-preview.js @@ -74,6 +74,8 @@ JX.behavior('differential-feedback-preview', function(config) { }); updateLinks(); + + JX.Stratcom.invoke('resize'); }) .setTimeout(5000) .send(); diff --git a/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js b/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js deleted file mode 100644 index 1905e3a433..0000000000 --- a/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js +++ /dev/null @@ -1,254 +0,0 @@ -/** - * @provides javelin-behavior-differential-dropdown-menus - * @requires javelin-behavior - * javelin-dom - * javelin-util - * javelin-stratcom - * javelin-workflow - * phuix-dropdown-menu - * phuix-action-list-view - * phuix-action-view - * phabricator-phtize - * changeset-view-manager - */ - -JX.behavior('differential-dropdown-menus', function(config) { - var pht = JX.phtize(config.pht); - - function show_more(container) { - var view = JX.ChangesetViewManager.getForNode(container); - - var nodes = JX.DOM.scry(container, 'tr', 'context-target'); - for (var ii = 0; ii < nodes.length; ii++) { - var show = JX.DOM.scry(nodes[ii], 'a', 'show-more'); - for (var jj = 0; jj < show.length; jj++) { - var data = JX.Stratcom.getData(show[jj]); - if (data.type != 'all') { - continue; - } - view.loadContext(data.range, nodes[ii], true); - } - } - } - - JX.Stratcom.listen( - 'click', - 'differential-reveal-all', - function(e) { - var containers = JX.DOM.scry( - JX.$('differential-review-stage'), - 'div', - 'differential-changeset'); - for (var i=0; i < containers.length; i++) { - show_more(containers[i]); - } - e.kill(); - }); - - var buildmenu = function(e) { - var button = e.getNode('differential-view-options'); - var data = JX.Stratcom.getData(button); - if (data.menu) { - return; - } - - e.prevent(); - - var changeset = JX.DOM.findAbove( - button, - 'div', - 'differential-changeset'); - - var view = JX.ChangesetViewManager.getForNode(changeset); - var menu = new JX.PHUIXDropdownMenu(button); - var list = new JX.PHUIXActionListView(); - - var add_link = function(icon, name, href, local) { - if (!href) { - return; - } - - var link = new JX.PHUIXActionView() - .setIcon(icon) - .setName(name) - .setHref(href) - .setHandler(function(e) { - if (local) { - window.location.assign(href); - } else { - window.open(href); - } - menu.close(); - e.prevent(); - }); - - list.addItem(link); - return link; - }; - - var reveal_item = new JX.PHUIXActionView() - .setIcon('fa-eye'); - list.addItem(reveal_item); - - var visible_item = new JX.PHUIXActionView() - .setHandler(function(e) { - var diff = JX.DOM.scry( - JX.$(data.containerID), - 'table', - 'differential-diff'); - - JX.Stratcom.invoke('differential-toggle-file', null, {diff: diff}); - e.prevent(); - menu.close(); - }); - list.addItem(visible_item); - - add_link('fa-file-text', pht('Browse in Diffusion'), data.diffusionURI); - add_link('fa-file-o', pht('View Standalone'), data.standaloneURI); - - var up_item = new JX.PHUIXActionView() - .setHandler(function(e) { - if (view.isLoaded()) { - var renderer = view.getRenderer(); - if (renderer == '1up') { - renderer = '2up'; - } else { - renderer = '1up'; - } - view.setRenderer(renderer); - } - view.reload(); - - e.prevent(); - menu.close(); - }); - list.addItem(up_item); - - var encoding_item = new JX.PHUIXActionView() - .setIcon('fa-font') - .setName(pht('Change Text Encoding...')) - .setHandler(function(e) { - var params = { - encoding: view.getEncoding() - }; - - new JX.Workflow('/services/encoding/', params) - .setHandler(function(r) { - view.setEncoding(r.encoding); - view.reload(); - }) - .start(); - - e.prevent(); - menu.close(); - }); - list.addItem(encoding_item); - - var highlight_item = new JX.PHUIXActionView() - .setIcon('fa-sun-o') - .setName(pht('Highlight As...')) - .setHandler(function(e) { - var params = { - highlight: view.getHighlight() - }; - - new JX.Workflow('/services/highlight/', params) - .setHandler(function(r) { - view.setHighlight(r.highlight); - view.reload(); - }) - .start(); - - e.prevent(); - menu.close(); - }); - list.addItem(highlight_item); - - add_link('fa-arrow-left', pht('Show Raw File (Left)'), data.leftURI); - add_link('fa-arrow-right', pht('Show Raw File (Right)'), data.rightURI); - add_link('fa-pencil', pht('Open in Editor'), data.editor, true); - add_link('fa-wrench', pht('Configure Editor'), data.editorConfigure); - - menu.setContent(list.getNode()); - - menu.listen('open', function() { - // When the user opens the menu, check if there are any "Show More" - // links in the changeset body. If there aren't, disable the "Show - // Entire File" menu item since it won't change anything. - - var nodes = JX.DOM.scry(JX.$(data.containerID), 'a', 'show-more'); - if (nodes.length) { - reveal_item - .setDisabled(false) - .setName(pht('Show All Context')) - .setIcon('fa-file-o') - .setHandler(function(e) { - show_more(JX.$(data.containerID)); - e.prevent(); - menu.close(); - }); - } else { - reveal_item - .setDisabled(true) - .setIcon('fa-file') - .setName(pht('All Context Shown')) - .setHandler(function(e) { e.prevent(); }); - } - - encoding_item.setDisabled(!view.isLoaded()); - highlight_item.setDisabled(!view.isLoaded()); - - if (view.isLoaded()) { - if (view.getRenderer() == '2up') { - up_item - .setIcon('fa-list-alt') - .setName(pht('View Unified')); - } else { - up_item - .setIcon('fa-files-o') - .setName(pht('View Side-by-Side')); - } - } else { - up_item - .setIcon('fa-refresh') - .setName(pht('Load Changes')); - } - - visible_item - .setDisabled(true) - .setIcon('fa-expand') - .setName(pht('Can\'t Toggle Unloaded File')); - var diffs = JX.DOM.scry( - JX.$(data.containerID), - 'table', - 'differential-diff'); - - if (diffs.length > 1) { - JX.$E( - 'More than one node with sigil "differential-diff" was found in "'+ - data.containerID+'."'); - } else if (diffs.length == 1) { - var diff = diffs[0]; - visible_item.setDisabled(false); - if (JX.Stratcom.getData(diff).hidden) { - visible_item - .setName(pht('Expand File')) - .setIcon('fa-expand'); - } else { - visible_item - .setName(pht('Collapse File')) - .setIcon('fa-compress'); - } - } else { - // Do nothing when there is no diff shown in the table. For example, - // the file is binary. - } - - }); - - data.menu = menu; - menu.open(); - }; - - JX.Stratcom.listen('click', 'differential-view-options', buildmenu); -}); diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js deleted file mode 100644 index 032b8cec68..0000000000 --- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js +++ /dev/null @@ -1,511 +0,0 @@ -/** - * @provides javelin-behavior-differential-edit-inline-comments - * @requires javelin-behavior - * javelin-stratcom - * javelin-dom - * javelin-util - * javelin-vector - * differential-inline-comment-editor - */ - -JX.behavior('differential-edit-inline-comments', function(config) { - - var selecting = false; - var reticle = JX.$N('div', {className: 'differential-reticle'}); - var old_cells = []; - JX.DOM.hide(reticle); - - var origin = null; - var target = null; - var root = null; - var changeset = null; - - var editor = null; - - function updateReticleForComment(e) { - root = e.getNode('differential-changeset'); - if (!root) { - return; - } - - var data = e.getNodeData('differential-inline-comment'); - var change = e.getNodeData('differential-changeset'); - - var id_part = data.on_right ? change.right : change.left; - var new_part = data.isNewFile ? 'N' : 'O'; - var prefix = 'C' + id_part + new_part + 'L'; - - origin = JX.$(prefix + data.number); - target = JX.$(prefix + (parseInt(data.number, 10) + - parseInt(data.length, 10))); - - updateReticle(); - } - - function updateReticle() { - JX.DOM.getContentFrame().appendChild(reticle); - - var top = origin; - var bot = target; - if (JX.$V(top).y > JX.$V(bot).y) { - var tmp = top; - top = bot; - bot = tmp; - } - - // Find the leftmost cell that we're going to highlight: this is the next - // in the row. In 2up views, it should be directly adjacent. In - // 1up views, we may have to skip over the other line number column. - var l = top; - while (JX.DOM.isType(l, 'th')) { - l = l.nextSibling; - } - - // Find the rightmost cell that we're going to highlight: this is the - // farthest consecutive, adjacent in the row. Sometimes the left - // and right nodes are the same (left side of 2up view); sometimes we're - // going to highlight several nodes (copy + code + coverage). - var r = l; - while (r.nextSibling && JX.DOM.isType(r.nextSibling, 'td')) { - r = r.nextSibling; - } - - var pos = JX.$V(l) - .add(JX.Vector.getAggregateScrollForNode(l)); - - var dim = JX.$V(r) - .add(JX.Vector.getAggregateScrollForNode(r)) - .add(-pos.x, -pos.y) - .add(JX.Vector.getDim(r)); - - var bpos = JX.$V(bot) - .add(JX.Vector.getAggregateScrollForNode(bot)); - dim.y = (bpos.y - pos.y) + JX.Vector.getDim(bot).y; - - pos.setPos(reticle); - dim.setDim(reticle); - - JX.DOM.show(reticle); - - // Find all the cells in the same row position between the top and bottom - // cell, so we can highlight them. - var seq = 0; - var row = top.parentNode; - for (seq = 0; seq < row.childNodes.length; seq++) { - if (row.childNodes[seq] == top) { - break; - } - } - - var cells = []; - while (true) { - cells.push(row.childNodes[seq]); - if (row.childNodes[seq] == bot) { - break; - } - row = row.nextSibling; - } - - setSelectedCells(cells); - } - - function setSelectedCells(new_cells) { - updateSelectedCellsClass(old_cells, false); - updateSelectedCellsClass(new_cells, true); - old_cells = new_cells; - } - - function updateSelectedCellsClass(cells, selected) { - for (var ii = 0; ii < cells.length; ii++) { - JX.DOM.alterClass(cells[ii], 'selected', selected); - } - } - - function hideReticle() { - JX.DOM.hide(reticle); - setSelectedCells([]); - } - - JX.DifferentialInlineCommentEditor.listen('done', function() { - selecting = false; - editor = false; - hideReticle(); - set_link_state(false); - }); - - function isOnRight(node) { - return node.parentNode.firstChild != node; - } - - function isNewFile(node) { - var data = JX.Stratcom.getData(root); - return isOnRight(node) || (data.left != data.right); - } - - function getRowNumber(th_node) { - try { - return parseInt(th_node.id.match(/^C\d+[ON]L(\d+)$/)[1], 10); - } catch (x) { - return undefined; - } - } - - var set_link_state = function(active) { - JX.DOM.alterClass(JX.$(config.stage), 'inline-editor-active', active); - }; - - JX.Stratcom.listen( - 'mousedown', - ['differential-changeset', 'tag:th'], - function(e) { - if (e.isRightButton() || - getRowNumber(e.getTarget()) === undefined) { - return; - } - - if (editor) { - new JX.DifferentialInlineCommentEditor(config.uri) - .setOperation('busy') - .setRow(editor.getRow().previousSibling) - .start(); - return; - } - - if (selecting) { - return; - } - - selecting = true; - root = e.getNode('differential-changeset'); - - origin = target = e.getTarget(); - - var data = e.getNodeData('differential-changeset'); - if (isOnRight(target)) { - changeset = data.right; - } else { - changeset = data.left; - } - - updateReticle(); - - e.kill(); - }); - - JX.Stratcom.listen( - ['mouseover', 'mouseout'], - ['differential-changeset', 'tag:th'], - function(e) { - if (e.getIsTouchEvent()) { - return; - } - - if (editor) { - // Don't update the reticle if we're editing a comment, since this - // would be distracting and we want to keep the lines corresponding - // to the comment highlighted during the edit. - return; - } - - if (getRowNumber(e.getTarget()) === undefined) { - // Don't update the reticle if this "" doesn't correspond to a - // line number. For instance, this may be a dead line number, like the - // empty line numbers on the left hand side of a newly added file. - return; - } - - if (selecting) { - if (isOnRight(e.getTarget()) != isOnRight(origin)) { - // Don't update the reticle if we're selecting a line range and the - // "" under the cursor is on the wrong side of the file. You - // can only leave inline comments on the left or right side of a - // file, not across lines on both sides. - return; - } - - if (e.getNode('differential-changeset') !== root) { - // Don't update the reticle if we're selecting a line range and - // the "" under the cursor corresponds to a different file. - // You can only leave inline comments on lines in a single file, - // not across multiple files. - return; - } - } - - if (e.getType() == 'mouseout') { - if (selecting) { - // Don't hide the reticle if we're selecting, since we want to - // keep showing the line range that will be used if the mouse is - // released. - return; - } - hideReticle(); - } else { - target = e.getTarget(); - if (!selecting) { - // If we're just hovering the mouse and not selecting a line range, - // set the origin to the current row so we highlight it. - origin = target; - } - - updateReticle(); - } - }); - - JX.Stratcom.listen( - 'mouseup', - null, - function(e) { - if (editor || !selecting) { - return; - } - - var o = getRowNumber(origin); - var t = getRowNumber(target); - - var insert; - var len; - if (t < o) { - len = (o - t); - o = t; - insert = origin.parentNode; - } else { - len = (t - o); - insert = target.parentNode; - } - - var view = JX.ChangesetViewManager.getForNode(root); - - editor = new JX.DifferentialInlineCommentEditor(config.uri) - .setTemplates(view.getUndoTemplates()) - .setOperation('new') - .setChangesetID(changeset) - .setLineNumber(o) - .setLength(len) - .setIsNew(isNewFile(target) ? 1 : 0) - .setOnRight(isOnRight(target) ? 1 : 0) - .setRow(insert.nextSibling) - .setTable(insert.parentNode) - .setRenderer(view.getRenderer()) - .start(); - - set_link_state(true); - - e.kill(); - }); - - JX.Stratcom.listen( - ['mouseover', 'mouseout'], - 'differential-inline-comment', - function(e) { - if (e.getIsTouchEvent()) { - return; - } - - if (e.getType() == 'mouseout') { - hideReticle(); - } else { - updateReticleForComment(e); - } - }); - - var action_handler = function(op, e) { - e.kill(); - - if (editor) { - return; - } - - var node = e.getNode('differential-inline-comment'); - - // If we're on a touch device, we didn't highlight the affected lines - // earlier because we can't use hover events to mutate the document. - // Highlight them now. - updateReticleForComment(e); - - handle_inline_action(node, op); - }; - - var handle_inline_action = function(node, op) { - var data = JX.Stratcom.getData(node); - - // If you click an action in the preview at the bottom of the page, we - // find the corresponding node and simulate clicking that, if it's - // present on the page. This gives the editor a more consistent view - // of the document. - if (JX.Stratcom.hasSigil(node, 'differential-inline-comment-preview')) { - var nodes = JX.DOM.scry( - JX.DOM.getContentFrame(), - 'div', - 'differential-inline-comment'); - - var found = false; - var node_data; - for (var ii = 0; ii < nodes.length; ++ii) { - if (nodes[ii] == node) { - // Don't match the preview itself. - continue; - } - node_data = JX.Stratcom.getData(nodes[ii]); - if (node_data.id == data.id) { - node = nodes[ii]; - data = node_data; - found = true; - break; - } - } - - if (!found) { - switch (op) { - case 'delete': - new JX.DifferentialInlineCommentEditor(config.uri) - .deleteByID(data.id); - return; - } - } - - if (op == 'delete') { - op = 'refdelete'; - } - } - - if (op == 'done') { - var checkbox = JX.DOM.find(node, 'input', 'differential-inline-done'); - new JX.DifferentialInlineCommentEditor(config.uri) - .toggleCheckbox(data.id, checkbox); - return; - } - - var original = data.original; - var reply_phid = null; - if (op == 'reply') { - // If the user hit "reply", the original text is empty (a new reply), not - // the text of the comment they're replying to. - original = ''; - reply_phid = data.phid; - } - - var row = JX.DOM.findAbove(node, 'tr'); - var changeset_root = JX.DOM.findAbove( - node, - 'div', - 'differential-changeset'); - var view = JX.ChangesetViewManager.getForNode(changeset_root); - - editor = new JX.DifferentialInlineCommentEditor(config.uri) - .setTemplates(view.getUndoTemplates()) - .setOperation(op) - .setID(data.id) - .setChangesetID(data.changesetID) - .setLineNumber(data.number) - .setLength(data.length) - .setOnRight(data.on_right) - .setOriginalText(original) - .setRow(row) - .setTable(row.parentNode) - .setReplyToCommentPHID(reply_phid) - .setRenderer(view.getRenderer()) - .start(); - - set_link_state(true); - }; - - for (var op in {'edit': 1, 'delete': 1, 'reply': 1, 'done': 1}) { - JX.Stratcom.listen( - 'click', - ['differential-inline-comment', 'differential-inline-' + op], - JX.bind(null, action_handler, op)); - } - - JX.Stratcom.listen( - 'differential-inline-action', - null, - function(e) { - var data = e.getData(); - handle_inline_action(data.node, data.op); - }); - - // Respond to the user clicking the "Hide Inline" button on an inline - // comment. - JX.Stratcom.listen('click', 'hide-inline', function(e) { - e.kill(); - - var row = e.getNode('inline-row'); - JX.DOM.hide(row); - - var prev = row.previousSibling; - while (prev && JX.Stratcom.hasSigil(prev, 'inline-row')) { - prev = prev.previousSibling; - } - - if (!prev) { - return; - } - - var comment = e.getNodeData('differential-inline-comment'); - - var slots = []; - for (var ii = 0; ii < prev.childNodes.length; ii++) { - if (JX.DOM.isType(prev.childNodes[ii], 'th')) { - slots.push(prev.childNodes[ii]); - } - } - - // Select the right-hand side if the comment is on the right. - var slot = (comment.on_right && slots[1]) || slots[0]; - - var reveal = JX.DOM.scry(slot, 'a', 'reveal-inlines')[0]; - if (!reveal) { - reveal = JX.$N( - 'a', - { - className: 'reveal-inlines', - sigil: 'reveal-inlines' - }, - JX.$H(config.revealIcon)); - - JX.DOM.prependContent(slot, reveal); - } - - new JX.Workflow(config.uri, {op: 'hide', ids: comment.id}) - .setHandler(JX.bag) - .start(); - }); - - JX.Stratcom.listen('click', 'reveal-inlines', function(e) { - e.kill(); - - var row = e.getNode('tag:tr'); - var next = row.nextSibling; - - var ids = []; - var ii; - - // Show any hidden inline comment rows directly below this one. - while (next && JX.Stratcom.hasSigil(next, 'inline-row')) { - JX.DOM.show(next); - - var comments = JX.DOM.scry(next, 'div', 'differential-inline-comment'); - for (ii = 0; ii < comments.length; ii++) { - var id = JX.Stratcom.getData(comments[ii]).id; - if (id) { - ids.push(id); - } - } - - next = next.nextSibling; - } - - // Remove any "reveal" icons on the row. - var reveals = JX.DOM.scry(row, 'a', 'reveal-inlines'); - for (ii = 0; ii < reveals.length; ii++) { - JX.DOM.remove(reveals[ii]); - } - - new JX.Workflow(config.uri, {op: 'show', ids: ids.join(',')}) - .setHandler(JX.bag) - .start(); - }); - - -}); diff --git a/webroot/rsrc/js/application/differential/behavior-keyboard-nav.js b/webroot/rsrc/js/application/differential/behavior-keyboard-nav.js deleted file mode 100644 index f48df4d3dc..0000000000 --- a/webroot/rsrc/js/application/differential/behavior-keyboard-nav.js +++ /dev/null @@ -1,264 +0,0 @@ -/** - * @provides javelin-behavior-differential-keyboard-navigation - * @requires javelin-behavior - * javelin-dom - * javelin-stratcom - * phabricator-keyboard-shortcut - */ - -JX.behavior('differential-keyboard-navigation', function(config) { - - var cursor = -1; - var changesets; - - var selection_begin = null; - var selection_end = null; - - var refreshFocus = function() {}; - - function init() { - if (changesets) { - return; - } - changesets = JX.DOM.scry(document.body, 'div', 'differential-changeset'); - } - - function getBlocks(cursor) { - // TODO: This might not be terribly fast; we can't currently memoize it - // because it can change as ajax requests come in (e.g., content loads). - - var rows = JX.DOM.scry(changesets[cursor], 'tr'); - var blocks = [[changesets[cursor], changesets[cursor]]]; - var start = null; - var type; - var ii; - - // Don't show code blocks inside a collapsed file. - var diff = JX.DOM.scry(changesets[cursor], 'table', 'differential-diff'); - if (diff.length == 1 && JX.Stratcom.getData(diff[0]).hidden) { - return blocks; - } - - function push() { - if (start) { - blocks.push([start, rows[ii - 1]]); - } - start = null; - } - - for (ii = 0; ii < rows.length; ii++) { - type = getRowType(rows[ii]); - if (type == 'comment') { - // If we see these types of rows, make a block for each one. - push(); - } - if (!type) { - push(); - } else if (type && !start) { - start = rows[ii]; - } - } - push(); - - return blocks; - } - - function getRowType(row) { - // NOTE: Being somewhat over-general here to allow other types of objects - // to be easily focused in the future (inline comments, 'show more..'). - - if (row.className.indexOf('inline') !== -1) { - return 'comment'; - } - - if (row.className.indexOf('differential-changeset') !== -1) { - return 'file'; - } - - var cells = JX.DOM.scry(row, 'td'); - - for (var ii = 0; ii < cells.length; ii++) { - // NOTE: The semantic use of classnames here is for performance; don't - // emulate this elsewhere since it's super terrible. - if (cells[ii].className.indexOf('old') !== -1 || - cells[ii].className.indexOf('new') !== -1) { - return 'change'; - } - } - - return null; - } - - function jump(manager, delta, jump_to_type) { - init(); - - if (cursor < 0) { - if (delta < 0) { - // If the user goes "back" without a selection, just reject the action. - return; - } else { - cursor = 0; - } - } - - while (true) { - var blocks = getBlocks(cursor); - var focus; - if (delta < 0) { - focus = blocks.length; - } else { - focus = -1; - } - - for (var ii = 0; ii < blocks.length; ii++) { - if (blocks[ii][0] == selection_begin) { - focus = ii; - break; - } - } - - while (true) { - focus += delta; - - if (blocks[focus]) { - var row_type = getRowType(blocks[focus][0]); - if (jump_to_type && row_type != jump_to_type) { - continue; - } - - selection_begin = blocks[focus][0]; - selection_end = blocks[focus][1]; - - manager.scrollTo(selection_begin); - - refreshFocus = function() { - manager.focusOn(selection_begin, selection_end); - }; - - refreshFocus(); - - return; - } else { - var adjusted = (cursor + delta); - if (adjusted < 0 || adjusted >= changesets.length) { - // Stop cursor movement when the user reaches either end. - return; - } - cursor = adjusted; - - // Break the inner loop and go to the next file. - break; - } - } - } - - } - - // When inline comments are updated, wipe out our cache of blocks since - // comments may have been added or deleted. - JX.Stratcom.listen( - null, - 'differential-inline-comment-update', - function() { - changesets = null; - }); - // Same thing when a file is hidden or shown; don't want to highlight - // invisible code. - JX.Stratcom.listen( - 'differential-toggle-file-toggled', - null, - function() { - changesets = null; - init(); - refreshFocus(); - }); - - new JX.KeyboardShortcut('j', 'Jump to next change.') - .setHandler(function(manager) { - jump(manager, 1); - }) - .register(); - - new JX.KeyboardShortcut('k', 'Jump to previous change.') - .setHandler(function(manager) { - jump(manager, -1); - }) - .register(); - - new JX.KeyboardShortcut('J', 'Jump to next file.') - .setHandler(function(manager) { - jump(manager, 1, 'file'); - }) - .register(); - - new JX.KeyboardShortcut('K', 'Jump to previous file.') - .setHandler(function(manager) { - jump(manager, -1, 'file'); - }) - .register(); - - new JX.KeyboardShortcut('n', 'Jump to next inline comment.') - .setHandler(function(manager) { - jump(manager, 1, 'comment'); - }) - .register(); - - new JX.KeyboardShortcut('p', 'Jump to previous inline comment.') - .setHandler(function(manager) { - jump(manager, -1, 'comment'); - }) - .register(); - - - new JX.KeyboardShortcut('t', 'Jump to the table of contents.') - .setHandler(function(manager) { - var toc = JX.$('toc'); - manager.scrollTo(toc); - }) - .register(); - - new JX.KeyboardShortcut( - 'h', - 'Collapse or expand the file display (after jump).') - .setHandler(function() { - if (!changesets || !changesets[cursor]) { - return; - } - JX.Stratcom.invoke('differential-toggle-file', null, { - diff: JX.DOM.scry(changesets[cursor], 'table', 'differential-diff') - }); - }) - .register(); - - - function inline_op(node, op) { - // nothing selected - if (!node) { - return; - } - if (!JX.DOM.scry(node, 'a', 'differential-inline-' + op)) { - // No link for this operation, e.g. editing a comment you can't edit. - return; - } - - var data = { - node: JX.DOM.find(node, 'div', 'differential-inline-comment'), - op: op - }; - - JX.Stratcom.invoke('differential-inline-action', null, data); - } - - new JX.KeyboardShortcut('r', 'Reply to selected inline comment.') - .setHandler(function() { - inline_op(selection_begin, 'reply'); - }) - .register(); - - new JX.KeyboardShortcut('e', 'Edit selected inline comment.') - .setHandler(function() { - inline_op(selection_begin, 'edit'); - }) - .register(); - -}); diff --git a/webroot/rsrc/js/application/differential/behavior-populate.js b/webroot/rsrc/js/application/differential/behavior-populate.js index 6d0aabb213..e5b0d5039d 100644 --- a/webroot/rsrc/js/application/differential/behavior-populate.js +++ b/webroot/rsrc/js/application/differential/behavior-populate.js @@ -4,52 +4,80 @@ * javelin-dom * javelin-stratcom * phabricator-tooltip - * changeset-view-manager + * phabricator-diff-changeset-list + * phabricator-diff-changeset + * @javelin */ -JX.behavior('differential-populate', function(config) { +JX.behavior('differential-populate', function(config, statics) { + + // When we perform a Quicksand navigation, deactivate the changeset lists on + // the current page and activate the changeset lists on the new page. + var onredraw = function(page_id) { + // If the current page is already active, we don't need to do anything. + if (statics.pageID === page_id) { + return; + } + + var ii; + + // Put the old lists to sleep. + var old_lists = get_lists(statics.pageID); + for (ii = 0; ii < old_lists.length; ii++) { + old_lists[ii].sleep(); + } + statics.pageID = null; + + // Awaken the new lists, if they exist. + if (statics.pages.hasOwnProperty(page_id)) { + var new_lists = get_lists(page_id); + for (ii = 0; ii < new_lists.length; ii++) { + new_lists[ii].wake(); + } + + statics.pageID = page_id; + } + }; + + // Get changeset lists on the current page. + var get_lists = function(page_id) { + if (page_id === null) { + return []; + } + + return statics.pages[page_id] || []; + }; + + if (!statics.installed) { + statics.installed = true; + statics.pages = {}; + statics.pageID = null; + + JX.Stratcom.listen('quicksand-redraw', null, function(e) { + onredraw(e.getData().newResponseID); + }); + } + + var changeset_list = new JX.DiffChangesetList() + .setTranslations(JX.phtize(config.pht)) + .setInlineURI(config.inlineURI); + + // Install and activate the current page. + var page_id = JX.Quicksand.getCurrentPageID(); + statics.pages[page_id] = [changeset_list]; + onredraw(page_id); + + for (var ii = 0; ii < config.changesetViewIDs.length; ii++) { var id = config.changesetViewIDs[ii]; - var view = JX.ChangesetViewManager.getForNode(JX.$(id)); - if (view.shouldAutoload()) { - view.setStabilize(true).load(); + var node = JX.$(id); + var changeset = changeset_list.newChangesetForNode(node); + if (changeset.shouldAutoload()) { + changeset.setStabilize(true).load(); } } - JX.Stratcom.listen( - 'click', - 'differential-load', - function(e) { - var meta = e.getNodeData('differential-load'); - var changeset = JX.$(meta.id); - var view = JX.ChangesetViewManager.getForNode(changeset); - - view.load(); - var routable = view.getRoutable(); - if (routable) { - routable.setPriority(2000); - } - - if (meta.kill) { - e.kill(); - } - }); - - JX.Stratcom.listen( - 'click', - 'show-more', - function(e) { - e.kill(); - - var changeset = e.getNode('differential-changeset'); - var view = JX.ChangesetViewManager.getForNode(changeset); - var data = e.getNodeData('show-more'); - var target = e.getNode('context-target'); - - view.loadContext(data.range, target); - }); - var highlighted = null; var highlight_class = null; diff --git a/webroot/rsrc/js/application/differential/behavior-toggle-files.js b/webroot/rsrc/js/application/differential/behavior-toggle-files.js deleted file mode 100644 index 296b6f91b2..0000000000 --- a/webroot/rsrc/js/application/differential/behavior-toggle-files.js +++ /dev/null @@ -1,128 +0,0 @@ -/** - * @provides javelin-behavior-differential-toggle-files - * @requires javelin-behavior - * javelin-dom - * javelin-stratcom - * phabricator-phtize - */ - -JX.behavior('differential-toggle-files', function(config) { - var pht = JX.phtize(config.pht); - - JX.Stratcom.listen( - 'differential-toggle-file', - null, - function(e) { - if (e.getData().diff.length != 1) { - return; - } - - var diff = e.getData().diff[0], - data = JX.Stratcom.getData(diff); - if (data.hidden) { - data.hidden = false; - JX.DOM.show(diff); - JX.DOM.remove(data.undo); - data.undo = null; - } else { - data.hidden = true; - data.undo = render_collapse_undo(); - JX.DOM.hide(diff); - JX.DOM.listen( - data.undo, - 'click', - 'differential-collapse-undo', - function(e) { - e.kill(); - data.hidden = false; - JX.DOM.show(diff); - JX.DOM.remove(data.undo); - data.undo = null; - }); - JX.DOM.appendContent(diff.parentNode, data.undo); - } - JX.Stratcom.invoke('differential-toggle-file-toggled'); - }); - - JX.Stratcom.listen( - 'differential-toggle-file-request', - null, - function(e) { - var elt = e.getData().element; - while (elt !== document.body) { - if (JX.Stratcom.hasSigil(elt, 'differential-changeset')) { - var diffs = JX.DOM.scry(elt, 'table', 'differential-diff'); - var invoked = false; - for (var i = 0; i < diffs.length; ++i) { - if (JX.Stratcom.getData(diffs[i]).hidden) { - JX.Stratcom.invoke('differential-toggle-file', null, { - diff: [ diffs[i] ] - }); - invoked = true; - } - } - if (!invoked) { - e.prevent(); - } - return; - } - elt = elt.parentNode; - } - e.prevent(); - }); - - JX.Stratcom.listen( - 'click', - 'tag:a', - function(e) { - var link = e.getNode('tag:a'); - var id = link.getAttribute('href'); - if (!id || !id.match(/^#.+/)) { - return; - } - var raw = e.getRawEvent(); - if (raw.altKey || raw.ctrlKey || raw.metaKey || raw.shiftKey) { - return; - } - // The target may have either a matching name or a matching id. - var target; - try { - target = JX.$(id.substr(1)); - } catch(err) { - var named = document.getElementsByName(id.substr(1)); - for (var i = 0; i < named.length; ++i) { - if (named[i].tagName.toLowerCase() == 'a') { - if (target) { - return; - } - target = named[i]; - } - } - if (!target) { - return; - } - } - var event = JX.Stratcom.invoke('differential-toggle-file-request', null, { - element: target - }); - if (!event.getPrevented()) { - // This event is processed after the hash has changed, so it doesn't - // automatically jump there like we want. - JX.DOM.scrollTo(target); - } - }); - - var render_collapse_undo = function() { - var link = JX.$N( - 'a', - {href: '#', sigil: 'differential-collapse-undo'}, - pht('undo')); - - return JX.$N( - 'div', - {className: 'differential-collapse-undo', - sigil: 'differential-collapse-undo-div'}, - [pht('collapsed'), ' ', link]); - }; - -}); diff --git a/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js b/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js index b4dcd2975b..ab4bf768d8 100644 --- a/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js +++ b/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js @@ -60,6 +60,7 @@ JX.behavior('phabricator-show-older-transactions', function(config) { var show_older = function(swap, r) { JX.DOM.replace(swap, JX.$H(r.timeline).getFragment()); + JX.Stratcom.invoke('resize'); }; var load_hidden_hash_callback = function(swap, r) { diff --git a/webroot/rsrc/js/core/KeyboardShortcutManager.js b/webroot/rsrc/js/core/KeyboardShortcutManager.js index c9a1fbe64e..281c12a8f3 100644 --- a/webroot/rsrc/js/core/KeyboardShortcutManager.js +++ b/webroot/rsrc/js/core/KeyboardShortcutManager.js @@ -54,7 +54,6 @@ JX.install('KeyboardShortcutManager', { members : { _shortcuts : null, - _focusReticle : null, /** * Instead of calling this directly, you should call @@ -83,48 +82,6 @@ JX.install('KeyboardShortcutManager', { JX.DOM.scrollToPosition(0, node_position.y + scroll_distance.y - 60); }, - /** - * Move the keyboard shortcut focus to an element. - * - * @param Node Node to focus, or pass null to clear the focus. - * @param Node To focus multiple nodes (like rows in a table), specify the - * top-left node as the first parameter and the bottom-right - * node as the focus extension. - * @return void - */ - focusOn : function(node, extended_node) { - this._clearReticle(); - - if (!node) { - return; - } - - var r = JX.$N('div', {className : 'keyboard-focus-focus-reticle'}); - - extended_node = extended_node || node; - - // Outset the reticle some pixels away from the element, so there's some - // space between the focused element and the outline. - var p = JX.Vector.getPos(node); - var s = JX.Vector.getAggregateScrollForNode(node); - - p.add(s).add(-4, -4).setPos(r); - // Compute the size we need to extend to the full extent of the focused - // nodes. - JX.Vector.getPos(extended_node) - .add(-p.x, -p.y) - .add(JX.Vector.getDim(extended_node)) - .add(8, 8) - .setDim(r); - JX.DOM.getContentFrame().appendChild(r); - - this._focusReticle = r; - }, - - _clearReticle : function() { - this._focusReticle && JX.DOM.remove(this._focusReticle); - this._focusReticle = null; - }, _onkeypress : function(e) { if (!(this._getKey(e) in JX.KeyboardShortcutManager._downkeys)) { this._onkeyhit(e); diff --git a/webroot/rsrc/js/core/behavior-phabricator-nav.js b/webroot/rsrc/js/core/behavior-phabricator-nav.js index ca7fc0d14a..e37680abf0 100644 --- a/webroot/rsrc/js/core/behavior-phabricator-nav.js +++ b/webroot/rsrc/js/core/behavior-phabricator-nav.js @@ -113,6 +113,10 @@ JX.behavior('phabricator-nav', function(config) { new JX.Request('/settings/adjust/', JX.bag) .setData({ key : 'nav-collapsed', value : (collapsed ? 1 : 0) }) .send(); + + // Invoke a resize event so page elements can redraw if they need to. One + // example is the selection reticles in Differential. + JX.Stratcom.invoke('resize'); }); @@ -126,8 +130,19 @@ JX.behavior('phabricator-nav', function(config) { return; } + // When the buoyant header is visible, move the menu down below it. This + // is a bit of a hack. + var banner_height = 0; + try { + var banner = JX.$('diff-banner'); + banner_height = JX.Vector.getDim(banner).y; + } catch (error) { + // Ignore if there's no banner on the page. + } + local.style.top = Math.max( 0, + banner_height, JX.$V(content).y - Math.max(0, JX.Vector.getScroll().y)) + 'px'; }