From bfc1ccfdf1107bee576e697494019e4e575a05e4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 5 May 2014 10:57:23 -0700 Subject: [PATCH] Move all comment management junk into a dropdown menu Summary: man I sure hate Javascript I removed the ajax-edit and ajax-remove interactions, becuase they were prohibitively complex to get working given that the entire menu has to change too. Instead, the page just reloads. This works perfectly fine in practice. If we want to restore these in the future, we should have the server re-render the entire transaction group or something. I think very little is lost here, though. Test Plan: - Took all the actions. - Used existing dropdown menus. {F150196} Reviewers: chad, btrahan Reviewed By: btrahan Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D8966 --- resources/celerity/map.php | 291 ++++++++++-------- ...cationTransactionCommentEditController.php | 5 +- ...tionTransactionCommentRemoveController.php | 5 +- .../PhabricatorApplicationTransactionView.php | 3 - src/view/layout/PhabricatorActionView.php | 12 + src/view/phui/PHUITimelineEventView.php | 188 +++++++---- webroot/rsrc/css/core/core.css | 10 + webroot/rsrc/css/phui/phui-button.css | 10 +- webroot/rsrc/css/phui/phui-timeline-view.css | 49 +++ .../transactions/behavior-transaction-list.js | 65 ++-- .../behavior-phui-timeline-dropdown-menu.js | 34 ++ webroot/rsrc/js/phuix/PHUIXDropdownMenu.js | 31 +- 12 files changed, 450 insertions(+), 253 deletions(-) create mode 100644 webroot/rsrc/js/phui/behavior-phui-timeline-dropdown-menu.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 5831134655..0f8d52f77d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,14 +7,14 @@ return array( 'names' => array( - 'core.pkg.css' => 'afe6e16d', - 'core.pkg.js' => 'c415c382', + 'core.pkg.css' => '4279f4bd', + 'core.pkg.js' => 'f6616bcf', 'darkconsole.pkg.js' => 'ca8671ce', 'differential.pkg.css' => '4b8686e3', - 'differential.pkg.js' => '11a5b750', + 'differential.pkg.js' => 'a2f45b5f', 'diffusion.pkg.css' => '3783278d', 'diffusion.pkg.js' => '5b4010f4', - 'javelin.pkg.js' => '9f6d38c7', + 'javelin.pkg.js' => 'c57fd32c', 'maniphest.pkg.css' => 'f1887d71', 'maniphest.pkg.js' => '2fe8af22', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', @@ -49,7 +49,7 @@ return array( 'rsrc/css/application/conpherence/message-pane.css' => 'e46b612c', 'rsrc/css/application/conpherence/notification.css' => '403cf598', 'rsrc/css/application/conpherence/update.css' => '1099a660', - 'rsrc/css/application/conpherence/widget-pane.css' => '87b12e0c', + 'rsrc/css/application/conpherence/widget-pane.css' => 'bf275a6c', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', 'rsrc/css/application/countdown/timer.css' => '86b7b0a0', 'rsrc/css/application/diff/inline-comment-summary.css' => '8cfd34e8', @@ -105,17 +105,17 @@ return array( 'rsrc/css/application/subscriptions/subscribers-list.css' => '5bb30c78', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', - 'rsrc/css/core/core.css' => '7dff07c3', - 'rsrc/css/core/remarkup.css' => '0ec9ea61', + 'rsrc/css/core/core.css' => 'ef1b7892', + 'rsrc/css/core/remarkup.css' => '80c3a48c', 'rsrc/css/core/syntax.css' => '3c18c1cb', - 'rsrc/css/core/z-index.css' => '7e4989ed', + 'rsrc/css/core/z-index.css' => 'efb673ac', 'rsrc/css/diviner/diviner-shared.css' => '38813222', 'rsrc/css/font/font-awesome.css' => '62bc244d', 'rsrc/css/font/font-glyphicons-halflings.css' => 'c4c1c6b6', 'rsrc/css/font/font-source-sans-pro.css' => '91d53463', 'rsrc/css/font/phui-font-icon-base.css' => 'cd92ff25', 'rsrc/css/layout/phabricator-action-header-view.css' => 'c14dfc57', - 'rsrc/css/layout/phabricator-action-list-view.css' => '81383e25', + 'rsrc/css/layout/phabricator-action-list-view.css' => '6f7ef696', 'rsrc/css/layout/phabricator-crumbs-view.css' => '0222cbe0', 'rsrc/css/layout/phabricator-filetree-view.css' => 'a8c86ace', 'rsrc/css/layout/phabricator-hovercard-view.css' => '46a13cf0', @@ -126,7 +126,7 @@ return array( 'rsrc/css/phui/calendar/phui-calendar-month.css' => 'a92e47d2', 'rsrc/css/phui/calendar/phui-calendar.css' => '5e1ad989', 'rsrc/css/phui/phui-box.css' => '7b3a2eed', - 'rsrc/css/phui/phui-button.css' => '653ac588', + 'rsrc/css/phui/phui-button.css' => '3dbdbf0d', 'rsrc/css/phui/phui-document.css' => '3b078dc0', 'rsrc/css/phui/phui-feed-story.css' => '3a59c2cf', 'rsrc/css/phui/phui-fontkit.css' => 'de84aa4a', @@ -145,7 +145,7 @@ return array( 'rsrc/css/phui/phui-status.css' => '2f562399', 'rsrc/css/phui/phui-tag-view.css' => '295d81c4', 'rsrc/css/phui/phui-text.css' => '23e9b4b7', - 'rsrc/css/phui/phui-timeline-view.css' => '4a39d766', + 'rsrc/css/phui/phui-timeline-view.css' => '15ff2a9f', 'rsrc/css/phui/phui-workboard-view.css' => '84f2c272', 'rsrc/css/phui/phui-workpanel-view.css' => '97b69459', 'rsrc/css/sprite-actions.css' => '969ad0e5', @@ -163,7 +163,7 @@ return array( 'rsrc/css/sprite-minicons.css' => 'df4f76fe', 'rsrc/css/sprite-payments.css' => 'cc085d44', 'rsrc/css/sprite-projects.css' => '7578fa56', - 'rsrc/css/sprite-status.css' => '8bce1c97', + 'rsrc/css/sprite-status.css' => '25d7f92f', 'rsrc/css/sprite-tokens.css' => '1706b943', 'rsrc/externals/font/fontawesome/fontawesome-webfont.eot' => 'b676fe4f', 'rsrc/externals/font/fontawesome/fontawesome-webfont.ttf' => 'af66fc5c', @@ -200,7 +200,7 @@ return array( 'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => '7a94d6a5', 'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '5426001c', 'rsrc/externals/javelin/lib/Cookie.js' => '6b3dcf44', - 'rsrc/externals/javelin/lib/DOM.js' => '32a4d380', + 'rsrc/externals/javelin/lib/DOM.js' => '07d99a3d', 'rsrc/externals/javelin/lib/History.js' => 'c60f4327', 'rsrc/externals/javelin/lib/JSON.js' => '08e56a4e', 'rsrc/externals/javelin/lib/Mask.js' => 'b9f26029', @@ -333,8 +333,8 @@ return array( 'rsrc/image/sprite-payments.png' => 'd8576309', 'rsrc/image/sprite-projects-X2.png' => '218fdc8b', 'rsrc/image/sprite-projects.png' => '631ff9a7', - 'rsrc/image/sprite-status-X2.png' => '82445ee0', - 'rsrc/image/sprite-status.png' => '926a896a', + 'rsrc/image/sprite-status-X2.png' => '6cb4b401', + 'rsrc/image/sprite-status.png' => '97eb1562', 'rsrc/image/sprite-tokens-X2.png' => 'b4776580', 'rsrc/image/sprite-tokens.png' => '25b75533', 'rsrc/image/texture/card-gradient.png' => '815f26e8', @@ -354,7 +354,7 @@ return array( 'rsrc/js/application/config/behavior-reorder-fields.js' => '938aed89', 'rsrc/js/application/conpherence/behavior-menu.js' => '7ee23816', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '53f6f2dd', - 'rsrc/js/application/conpherence/behavior-widget-pane.js' => 'd8ef8659', + 'rsrc/js/application/conpherence/behavior-widget-pane.js' => '40b1ff90', 'rsrc/js/application/countdown/timer.js' => '889c96f3', 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '4398eabb', 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => 'f2441746', @@ -362,7 +362,7 @@ return array( 'rsrc/js/application/differential/behavior-comment-jump.js' => '71755c79', 'rsrc/js/application/differential/behavior-comment-preview.js' => '127f2018', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '5f004630', + 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '7f93ef26', 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '00861799', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '173ce7e7', 'rsrc/js/application/differential/behavior-populate.js' => 'ce0c217a', @@ -402,7 +402,7 @@ return array( 'rsrc/js/application/phortune/behavior-stripe-payment-form.js' => '1693a296', 'rsrc/js/application/phortune/behavior-test-payment-form.js' => 'b3e5ee60', 'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef', - 'rsrc/js/application/policy/behavior-policy-control.js' => 'c01153ea', + 'rsrc/js/application/policy/behavior-policy-control.js' => 'bc99b0f2', 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '263aeb8c', 'rsrc/js/application/ponder/behavior-votebox.js' => '327dbe61', 'rsrc/js/application/projects/behavior-project-boards.js' => 'd8e135db', @@ -414,7 +414,7 @@ return array( 'rsrc/js/application/search/behavior-reorder-queries.js' => '37871df4', 'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => 'a51fdb2e', 'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => '9084a36f', - 'rsrc/js/application/transactions/behavior-transaction-list.js' => '925c9bab', + 'rsrc/js/application/transactions/behavior-transaction-list.js' => 'cf656c84', 'rsrc/js/application/uiexample/JavelinViewExample.js' => 'd4a14807', 'rsrc/js/application/uiexample/ReactorButtonExample.js' => '44524435', 'rsrc/js/application/uiexample/ReactorCheckboxExample.js' => '7ba325ee', @@ -431,8 +431,6 @@ return array( 'rsrc/js/core/Busy.js' => '6453c869', 'rsrc/js/core/DragAndDropFileUpload.js' => 'ae6abfba', 'rsrc/js/core/DraggableList.js' => '1681c4d4', - 'rsrc/js/core/DropdownMenu.js' => 'fb342e18', - 'rsrc/js/core/DropdownMenuItem.js' => '0f386ef4', 'rsrc/js/core/FileUpload.js' => 'a4ae61bf', 'rsrc/js/core/Hovercard.js' => '4f344388', 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', @@ -481,6 +479,10 @@ return array( 'rsrc/js/core/behavior-workflow.js' => 'fee00761', 'rsrc/js/core/phtize.js' => 'd254d646', 'rsrc/js/phui/behavior-phui-object-box-tabs.js' => 'a3e2244e', + 'rsrc/js/phui/behavior-phui-timeline-dropdown-menu.js' => '4d94d9c3', + 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', + 'rsrc/js/phuix/PHUIXActionView.js' => '19a0b148', + 'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca', 'rsrc/swf/aphlict.swf' => 'abac967d', ), 'symbols' => @@ -506,7 +508,7 @@ return array( 'conpherence-message-pane-css' => 'e46b612c', 'conpherence-notification-css' => '403cf598', 'conpherence-update-css' => '1099a660', - 'conpherence-widget-pane-css' => '87b12e0c', + 'conpherence-widget-pane-css' => 'bf275a6c', 'differential-changeset-view-css' => '1570a1ff', 'differential-core-view-css' => '7ac3cabc', 'differential-inline-comment-editor' => 'f2441746', @@ -544,7 +546,7 @@ return array( 'javelin-behavior-config-reorder-fields' => '938aed89', 'javelin-behavior-conpherence-menu' => '7ee23816', 'javelin-behavior-conpherence-pontificate' => '53f6f2dd', - 'javelin-behavior-conpherence-widget-pane' => 'd8ef8659', + 'javelin-behavior-conpherence-widget-pane' => '40b1ff90', 'javelin-behavior-countdown-timer' => '889c96f3', 'javelin-behavior-dark-console' => 'e9fdb5e5', 'javelin-behavior-dashboard-async-panel' => '4398eabb', @@ -552,7 +554,7 @@ return array( 'javelin-behavior-differential-add-reviewers-and-ccs' => '533a187b', 'javelin-behavior-differential-comment-jump' => '71755c79', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', - 'javelin-behavior-differential-dropdown-menus' => '5f004630', + 'javelin-behavior-differential-dropdown-menus' => '7f93ef26', 'javelin-behavior-differential-edit-inline-comments' => '00861799', 'javelin-behavior-differential-feedback-preview' => '127f2018', 'javelin-behavior-differential-keyboard-navigation' => '173ce7e7', @@ -609,13 +611,14 @@ return array( 'javelin-behavior-phabricator-show-all-transactions' => '7c273581', 'javelin-behavior-phabricator-tooltips' => '48db4145', 'javelin-behavior-phabricator-transaction-comment-form' => '9084a36f', - 'javelin-behavior-phabricator-transaction-list' => '925c9bab', + 'javelin-behavior-phabricator-transaction-list' => 'cf656c84', 'javelin-behavior-phabricator-watch-anchor' => '06e05112', 'javelin-behavior-phame-post-preview' => '61d927ec', 'javelin-behavior-pholio-mock-edit' => '1e1e8bb0', 'javelin-behavior-pholio-mock-view' => '28497740', 'javelin-behavior-phui-object-box-tabs' => 'a3e2244e', - 'javelin-behavior-policy-control' => 'c01153ea', + 'javelin-behavior-phui-timeline-dropdown-menu' => '4d94d9c3', + 'javelin-behavior-policy-control' => 'bc99b0f2', 'javelin-behavior-policy-rule-editor' => '263aeb8c', 'javelin-behavior-ponder-votebox' => '327dbe61', 'javelin-behavior-project-boards' => 'd8e135db', @@ -636,7 +639,7 @@ return array( 'javelin-behavior-workflow' => 'fee00761', 'javelin-color' => '7e41274a', 'javelin-cookie' => '6b3dcf44', - 'javelin-dom' => '32a4d380', + 'javelin-dom' => '07d99a3d', 'javelin-dynval' => 'f6555212', 'javelin-event' => '79473b62', 'javelin-fx' => '54b612ba', @@ -681,17 +684,16 @@ return array( 'path-typeahead' => 'f7fc67ec', 'people-profile-css' => 'ba7b2762', 'phabricator-action-header-view-css' => 'c14dfc57', - 'phabricator-action-list-view-css' => '81383e25', + 'phabricator-action-list-view-css' => '6f7ef696', 'phabricator-application-launch-view-css' => 'd290ba21', 'phabricator-busy' => '6453c869', 'phabricator-chatlog-css' => '852140ff', 'phabricator-content-source-view-css' => '4b8b05d4', - 'phabricator-core-css' => '7dff07c3', + 'phabricator-core-css' => 'ef1b7892', 'phabricator-countdown-css' => '86b7b0a0', 'phabricator-crumbs-view-css' => '0222cbe0', 'phabricator-drag-and-drop-file-upload' => 'ae6abfba', 'phabricator-draggable-list' => '1681c4d4', - 'phabricator-dropdown-menu' => 'fb342e18', 'phabricator-fatal-config-template-css' => '25d446d6', 'phabricator-feed-css' => '0d17c209', 'phabricator-file-upload' => 'a4ae61bf', @@ -703,7 +705,6 @@ return array( 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => 'ad7a69ca', 'phabricator-main-menu-view' => '0a599177', - 'phabricator-menu-item' => '0f386ef4', 'phabricator-nav-view-css' => '80e60fc1', 'phabricator-notification' => '0c6946e7', 'phabricator-notification-css' => 'ef2c9b34', @@ -713,7 +714,7 @@ return array( 'phabricator-prefab' => '0326e5d0', 'phabricator-profile-css' => '33e6f703', 'phabricator-project-tag-css' => '095c9404', - 'phabricator-remarkup-css' => '0ec9ea61', + 'phabricator-remarkup-css' => '80c3a48c', 'phabricator-search-results-css' => 'f240504c', 'phabricator-settings-css' => 'ea8f5915', 'phabricator-shaped-request' => 'dfa181a4', @@ -735,7 +736,7 @@ return array( 'phabricator-uiexample-reactor-select' => '189e4fe3', 'phabricator-uiexample-reactor-sendclass' => 'bf97561d', 'phabricator-uiexample-reactor-sendproperties' => '551add57', - 'phabricator-zindex-css' => '7e4989ed', + 'phabricator-zindex-css' => 'efb673ac', 'phame-css' => '19ecc703', 'pholio-css' => '2fa97dbe', 'pholio-edit-css' => 'b9e59b6d', @@ -745,7 +746,7 @@ return array( 'phrequent-css' => 'ffc185ad', 'phriction-document-css' => '7d7f0071', 'phui-box-css' => '7b3a2eed', - 'phui-button-css' => '653ac588', + 'phui-button-css' => '3dbdbf0d', 'phui-calendar-css' => '5e1ad989', 'phui-calendar-day-css' => 'de035c8a', 'phui-calendar-list-css' => 'c1d0ca59', @@ -769,9 +770,12 @@ return array( 'phui-status-list-view-css' => '2f562399', 'phui-tag-view-css' => '295d81c4', 'phui-text-css' => '23e9b4b7', - 'phui-timeline-view-css' => '4a39d766', + 'phui-timeline-view-css' => '15ff2a9f', 'phui-workboard-view-css' => '84f2c272', 'phui-workpanel-view-css' => '97b69459', + 'phuix-action-list-view' => 'b5c256b8', + 'phuix-action-view' => '19a0b148', + 'phuix-dropdown-menu' => 'bd4c8dca', 'policy-css' => '957ea14c', 'policy-edit-css' => '05cca26a', 'policy-transaction-detail-css' => '82100a43', @@ -802,7 +806,7 @@ return array( 'sprite-minicons-css' => 'df4f76fe', 'sprite-payments-css' => 'cc085d44', 'sprite-projects-css' => '7578fa56', - 'sprite-status-css' => '8bce1c97', + 'sprite-status-css' => '25d7f92f', 'sprite-tokens-css' => '1706b943', 'subscribers-list-css' => '5bb30c78', 'syntax-highlighting-css' => '3c18c1cb', @@ -863,6 +867,14 @@ return array( 2 => 'javelin-dom', 3 => 'javelin-vector', ), + '07d99a3d' => + array( + 0 => 'javelin-magical-init', + 1 => 'javelin-install', + 2 => 'javelin-util', + 3 => 'javelin-vector', + 4 => 'javelin-stratcom', + ), '08e56a4e' => array( 0 => 'javelin-install', @@ -887,11 +899,6 @@ return array( 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), - '0f386ef4' => - array( - 0 => 'javelin-install', - 1 => 'javelin-dom', - ), '0f764c35' => array( 0 => 'javelin-install', @@ -934,6 +941,12 @@ return array( 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), + '19a0b148' => + array( + 0 => 'javelin-install', + 1 => 'javelin-dom', + 2 => 'javelin-util', + ), '1ae869f2' => array( 0 => 'javelin-install', @@ -1031,14 +1044,6 @@ return array( 3 => 'javelin-stratcom', 4 => 'javelin-request', ), - '32a4d380' => - array( - 0 => 'javelin-magical-init', - 1 => 'javelin-install', - 2 => 'javelin-util', - 3 => 'javelin-vector', - 4 => 'javelin-stratcom', - ), '356de121' => array( 0 => 'javelin-util', @@ -1075,6 +1080,19 @@ return array( 1 => 'javelin-dom', 2 => 'phortune-credit-card-form', ), + '40b1ff90' => + array( + 0 => 'javelin-behavior', + 1 => 'javelin-dom', + 2 => 'javelin-stratcom', + 3 => 'javelin-workflow', + 4 => 'javelin-util', + 5 => 'phabricator-notification', + 6 => 'javelin-behavior-device', + 7 => 'phuix-dropdown-menu', + 8 => 'phuix-action-list-view', + 9 => 'phuix-action-view', + ), '4398eabb' => array( 0 => 'javelin-behavior', @@ -1115,6 +1133,13 @@ return array( 2 => 'phabricator-drag-and-drop-file-upload', 3 => 'phabricator-textareautils', ), + '4d94d9c3' => + array( + 0 => 'javelin-behavior', + 1 => 'javelin-stratcom', + 2 => 'javelin-dom', + 3 => 'phuix-dropdown-menu', + ), '4e37e4de' => array( 0 => 'javelin-install', @@ -1187,16 +1212,6 @@ return array( 3 => 'javelin-stratcom', 4 => 'javelin-vector', ), - '5f004630' => - array( - 0 => 'javelin-behavior', - 1 => 'javelin-dom', - 2 => 'javelin-util', - 3 => 'javelin-stratcom', - 4 => 'phabricator-dropdown-menu', - 5 => 'phabricator-menu-item', - 6 => 'phabricator-phtize', - ), '5f850b5c' => array( 0 => 'javelin-install', @@ -1208,6 +1223,13 @@ return array( 2 => 'javelin-util', 3 => 'phabricator-shaped-request', ), + '62e18640' => + array( + 0 => 'javelin-install', + 1 => 'javelin-util', + 2 => 'javelin-dom', + 3 => 'javelin-typeahead-normalizer', + ), '6453c869' => array( 0 => 'javelin-install', @@ -1241,13 +1263,6 @@ return array( 0 => 'javelin-behavior', 1 => 'javelin-dom', ), - '62e18640' => - array( - 0 => 'javelin-install', - 1 => 'javelin-util', - 2 => 'javelin-dom', - 3 => 'javelin-typeahead-normalizer', - ), '75903ee1' => array( 0 => 'javelin-behavior', @@ -1313,6 +1328,17 @@ return array( 0 => 'javelin-behavior', 1 => 'javelin-history', ), + '7f93ef26' => + array( + 0 => 'javelin-behavior', + 1 => 'javelin-dom', + 2 => 'javelin-util', + 3 => 'javelin-stratcom', + 4 => 'phuix-dropdown-menu', + 5 => 'phuix-action-list-view', + 6 => 'phuix-action-view', + 7 => 'phabricator-phtize', + ), '82f568cd' => array( 0 => 'javelin-install', @@ -1402,15 +1428,6 @@ return array( 4 => 'javelin-request', 5 => 'phabricator-shaped-request', ), - '925c9bab' => - array( - 0 => 'javelin-behavior', - 1 => 'javelin-stratcom', - 2 => 'javelin-workflow', - 3 => 'javelin-dom', - 4 => 'javelin-fx', - 5 => 'javelin-util', - ), '938aed89' => array( 0 => 'javelin-behavior', @@ -1567,6 +1584,11 @@ return array( 6 => 'javelin-request', 7 => 'javelin-util', ), + 'b5c256b8' => + array( + 0 => 'javelin-install', + 1 => 'javelin-dom', + ), 'b657bdf8' => array( 0 => 'javelin-behavior', @@ -1601,6 +1623,24 @@ return array( 2 => 'javelin-dom', 3 => 'javelin-history', ), + 'bc99b0f2' => + array( + 0 => 'javelin-behavior', + 1 => 'javelin-dom', + 2 => 'javelin-util', + 3 => 'phuix-dropdown-menu', + 4 => 'phuix-action-list-view', + 5 => 'phuix-action-view', + 6 => 'javelin-workflow', + ), + 'bd4c8dca' => + array( + 0 => 'javelin-install', + 1 => 'javelin-util', + 2 => 'javelin-dom', + 3 => 'javelin-vector', + 4 => 'javelin-stratcom', + ), 'bdaf4d04' => array( 0 => 'javelin-behavior', @@ -1621,15 +1661,6 @@ return array( 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), - 'c01153ea' => - array( - 0 => 'javelin-behavior', - 1 => 'javelin-dom', - 2 => 'javelin-util', - 3 => 'phabricator-dropdown-menu', - 4 => 'phabricator-menu-item', - 5 => 'javelin-workflow', - ), 'c021950a' => array( 0 => 'javelin-behavior', @@ -1724,6 +1755,16 @@ return array( 6 => 'javelin-vector', 7 => 'phabricator-tooltip', ), + 'cf656c84' => + array( + 0 => 'javelin-behavior', + 1 => 'javelin-stratcom', + 2 => 'javelin-workflow', + 3 => 'javelin-dom', + 4 => 'javelin-fx', + 5 => 'javelin-uri', + 6 => 'phabricator-textareautils', + ), 'cf76cfd5' => array( 0 => 'javelin-behavior', @@ -1787,18 +1828,6 @@ return array( 4 => 'javelin-workflow', 5 => 'phabricator-draggable-list', ), - 'd8ef8659' => - array( - 0 => 'javelin-behavior', - 1 => 'javelin-dom', - 2 => 'javelin-stratcom', - 3 => 'javelin-workflow', - 4 => 'javelin-util', - 5 => 'phabricator-notification', - 6 => 'javelin-behavior-device', - 7 => 'phabricator-dropdown-menu', - 8 => 'phabricator-menu-item', - ), 'd9a9b862' => array( 0 => 'javelin-install', @@ -1935,15 +1964,6 @@ return array( 4 => 'javelin-stratcom', 5 => 'phabricator-shaped-request', ), - 'fb342e18' => - array( - 0 => 'javelin-install', - 1 => 'javelin-util', - 2 => 'javelin-dom', - 3 => 'javelin-vector', - 4 => 'javelin-stratcom', - 5 => 'phabricator-menu-item', - ), 'fbbce3bf' => array( 0 => 'phabricator-busy', @@ -2084,36 +2104,37 @@ return array( 6 => 'javelin-behavior-refresh-csrf', 7 => 'javelin-behavior-phabricator-watch-anchor', 8 => 'javelin-behavior-phabricator-autofocus', - 9 => 'phabricator-menu-item', - 10 => 'phabricator-dropdown-menu', - 11 => 'phabricator-phtize', - 12 => 'javelin-behavior-phabricator-oncopy', - 13 => 'phabricator-tooltip', - 14 => 'javelin-behavior-phabricator-tooltips', - 15 => 'phabricator-prefab', - 16 => 'javelin-behavior-device', - 17 => 'javelin-behavior-toggle-class', - 18 => 'javelin-behavior-lightbox-attachments', - 19 => 'phabricator-busy', - 20 => 'javelin-aphlict', - 21 => 'phabricator-notification', - 22 => 'javelin-behavior-aphlict-listen', - 23 => 'javelin-behavior-phabricator-search-typeahead', - 24 => 'javelin-behavior-konami', - 25 => 'javelin-behavior-aphlict-dropdown', - 26 => 'javelin-behavior-history-install', - 27 => 'javelin-behavior-phabricator-gesture', - 28 => 'javelin-behavior-phabricator-active-nav', - 29 => 'javelin-behavior-phabricator-nav', - 30 => 'javelin-behavior-phabricator-remarkup-assist', - 31 => 'phabricator-textareautils', - 32 => 'phabricator-file-upload', - 33 => 'javelin-behavior-global-drag-and-drop', - 34 => 'javelin-behavior-phabricator-reveal-content', - 35 => 'phabricator-hovercard', - 36 => 'javelin-behavior-phabricator-hovercards', - 37 => 'javelin-color', - 38 => 'javelin-fx', + 9 => 'phuix-dropdown-menu', + 10 => 'phuix-action-list-view', + 11 => 'phuix-action-view', + 12 => 'phabricator-phtize', + 13 => 'javelin-behavior-phabricator-oncopy', + 14 => 'phabricator-tooltip', + 15 => 'javelin-behavior-phabricator-tooltips', + 16 => 'phabricator-prefab', + 17 => 'javelin-behavior-device', + 18 => 'javelin-behavior-toggle-class', + 19 => 'javelin-behavior-lightbox-attachments', + 20 => 'phabricator-busy', + 21 => 'javelin-aphlict', + 22 => 'phabricator-notification', + 23 => 'javelin-behavior-aphlict-listen', + 24 => 'javelin-behavior-phabricator-search-typeahead', + 25 => 'javelin-behavior-konami', + 26 => 'javelin-behavior-aphlict-dropdown', + 27 => 'javelin-behavior-history-install', + 28 => 'javelin-behavior-phabricator-gesture', + 29 => 'javelin-behavior-phabricator-active-nav', + 30 => 'javelin-behavior-phabricator-nav', + 31 => 'javelin-behavior-phabricator-remarkup-assist', + 32 => 'phabricator-textareautils', + 33 => 'phabricator-file-upload', + 34 => 'javelin-behavior-global-drag-and-drop', + 35 => 'javelin-behavior-phabricator-reveal-content', + 36 => 'phabricator-hovercard', + 37 => 'javelin-behavior-phabricator-hovercards', + 38 => 'javelin-color', + 39 => 'javelin-fx', ), 'darkconsole.pkg.js' => array( diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentEditController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentEditController.php index 7ed3685ae4..0b4d0b615e 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentEditController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentEditController.php @@ -54,10 +54,7 @@ final class PhabricatorApplicationTransactionCommentEditController ->applyEdit($xaction, $comment); if ($request->isAjax()) { - return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($user) - ->setTransactions(array($xaction)) - ->setAnchorOffset($request->getStr('anchor')); + return id(new AphrontAjaxResponse())->setContent(array()); } else { return id(new AphrontReloadResponse())->setURI($obj_handle->getURI()); } diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRemoveController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRemoveController.php index fb9c6f481d..a4feff433d 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRemoveController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRemoveController.php @@ -47,10 +47,7 @@ final class PhabricatorApplicationTransactionCommentRemoveController ->applyEdit($xaction, $comment); if ($request->isAjax()) { - return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($viewer) - ->setTransactions(array($xaction)) - ->setAnchorOffset($request->getStr('anchor')); + return id(new AphrontAjaxResponse())->setContent(array()); } else { return id(new AphrontReloadResponse())->setURI($obj_handle->getURI()); } diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php index 0dd9358b36..64d9cd4c11 100644 --- a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php +++ b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php @@ -169,9 +169,6 @@ class PhabricatorApplicationTransactionView extends AphrontView { 'listID' => $list_id, 'objectPHID' => $this->getObjectPHID(), 'nextAnchor' => $this->anchorOffset + count($events), - 'historyLink' => '/transactions/history/', - 'historyLinkText' => pht('Edited'), - 'linkDelimiter' => PHUITimelineEventView::DELIMITER, )); } diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php index 7548ebe183..5f879ba0a0 100644 --- a/src/view/layout/PhabricatorActionView.php +++ b/src/view/layout/PhabricatorActionView.php @@ -12,6 +12,16 @@ final class PhabricatorActionView extends AphrontView { private $download; private $objectURI; private $sigils = array(); + private $metadata; + + public function setMetadata($metadata) { + $this->metadata = $metadata; + return $this; + } + + public function getMetadata() { + return $this->metadata; + } public function setObjectURI($object_uri) { $this->objectURI = $object_uri; @@ -138,6 +148,7 @@ final class PhabricatorActionView extends AphrontView { 'action' => $this->getHref(), 'method' => 'POST', 'sigil' => $sigils, + 'meta' => $this->metadata, ), $item); } else { @@ -147,6 +158,7 @@ final class PhabricatorActionView extends AphrontView { 'href' => $this->getHref(), 'class' => 'phabricator-action-view-item', 'sigil' => $sigils, + 'meta' => $this->metadata, ), $this->name); } diff --git a/src/view/phui/PHUITimelineEventView.php b/src/view/phui/PHUITimelineEventView.php index e2c7371592..c1b30fcffa 100644 --- a/src/view/phui/PHUITimelineEventView.php +++ b/src/view/phui/PHUITimelineEventView.php @@ -163,7 +163,7 @@ final class PHUITimelineEventView extends AphrontView { return $this; } - protected function renderEventTitle($is_first_event, $force_icon) { + protected function renderEventTitle($is_first_event, $force_icon, $has_menu) { $title = $this->title; if (($title === null) && !$this->hasChildren()) { $title = ''; @@ -181,7 +181,6 @@ final class PHUITimelineEventView extends AphrontView { $extra = javelin_tag( 'span', array( - 'sigil' => 'timeline-extra', 'class' => 'phui-timeline-extra', ), phutil_implode_html( @@ -205,6 +204,10 @@ final class PHUITimelineEventView extends AphrontView { $title_classes[] = 'phui-timeline-title-with-icon'; } + if ($has_menu) { + $title_classes[] = 'phui-timeline-title-with-menu'; + } + if ($this->icon) { $fill_classes = array(); $fill_classes[] = 'phui-timeline-icon-fill'; @@ -260,11 +263,71 @@ final class PHUITimelineEventView extends AphrontView { $events = array_select_keys($events, $icon_keys) + $events; $force_icon = (bool)$icon_keys; + $menu = null; + $items = array(); + $has_menu = false; + if (!$this->getIsPreview()) { + foreach ($this->getEventGroup() as $event) { + $items[] = $event->getMenuItems($this->anchor); + if ($event->hasChildren()) { + $has_menu = true; + } + } + $items = array_mergev($items); + } + + if ($items || $has_menu) { + $icon = id(new PHUIIconView()) + ->setIconFont('fa-cog'); + $aural = javelin_tag( + 'span', + array( + 'aural' => true, + ), + pht('Comment Actions')); + + if ($items) { + $sigil = 'phui-timeline-menu'; + Javelin::initBehavior('phui-timeline-dropdown-menu'); + } else { + $sigil = null; + } + + $action_list = id(new PhabricatorActionListView()) + ->setUser($this->getUser()); + foreach ($items as $item) { + $action_list->addAction($item); + } + + $menu = javelin_tag( + $items ? 'a' : 'span', + array( + 'href' => '#', + 'class' => 'phui-timeline-menu', + 'sigil' => $sigil, + 'aria-haspopup' => 'true', + 'aria-expanded' => 'false', + 'meta' => array( + 'items' => hsprintf('%s', $action_list), + ), + ), + array( + $aural, + $icon, + )); + + $has_menu = true; + } + $group_titles = array(); + $group_items = array(); $group_children = array(); $is_first_event = true; foreach ($events as $event) { - $group_titles[] = $event->renderEventTitle($is_first_event, $force_icon); + $group_titles[] = $event->renderEventTitle( + $is_first_event, + $force_icon, + $has_menu); $is_first_event = false; if ($event->hasChildren()) { $group_children[] = $event->renderChildren(); @@ -303,6 +366,7 @@ final class PHUITimelineEventView extends AphrontView { ), array( $group_titles, + $menu, phutil_tag( 'div', array( @@ -375,61 +439,9 @@ final class PHUITimelineEventView extends AphrontView { if ($this->getIsPreview()) { $extra[] = pht('PREVIEW'); } else { - $xaction_phid = $this->getTransactionPHID(); - - if ($this->getQuoteTargetID()) { - - $ref = null; - if ($this->getQuoteRef()) { - $ref = $this->getQuoteRef(); - if ($this->anchor) { - $ref = $ref.'#'.$this->anchor; - } - } - - $extra[] = javelin_tag( - 'a', - array( - 'href' => '#', - 'sigil' => 'transaction-quote', - 'mustcapture' => true, - 'meta' => array( - 'targetID' => $this->getQuoteTargetID(), - 'uri' => '/transactions/quote/'.$xaction_phid.'/', - 'ref' => $ref, - ), - ), - pht('Quote')); - } if ($this->getIsEdited()) { - $extra[] = javelin_tag( - 'a', - array( - 'href' => '/transactions/history/'.$xaction_phid.'/', - 'sigil' => 'workflow transaction-edit-history', - ), - pht('Edited')); - } - - if ($this->getIsEditable()) { - $extra[] = javelin_tag( - 'a', - array( - 'href' => '/transactions/edit/'.$xaction_phid.'/', - 'sigil' => 'workflow transaction-edit', - ), - pht('Edit')); - } - - if ($this->getIsRemovable()) { - $extra[] = javelin_tag( - 'a', - array( - 'href' => '/transactions/remove/'.$xaction_phid.'/', - 'sigil' => 'workflow transaction-remove', - ), - pht('Remove')); + $extra[] = pht('Edited'); } if ($is_first_extra) { @@ -476,9 +488,73 @@ final class PHUITimelineEventView extends AphrontView { $extra[] = $date; } } + } return $extra; } + private function getMenuItems($anchor) { + $xaction_phid = $this->getTransactionPHID(); + + $items = array(); + if ($this->getQuoteTargetID()) { + + $ref = null; + if ($this->getQuoteRef()) { + $ref = $this->getQuoteRef(); + if ($anchor) { + $ref = $ref.'#'.$anchor; + } + } + + $items[] = id(new PhabricatorActionView()) + ->setIcon('comment') + ->setHref('#') + ->setName(pht('Quote')) + ->addSigil('transaction-quote') + ->setMetadata( + array( + 'targetID' => $this->getQuoteTargetID(), + 'uri' => '/transactions/quote/'.$xaction_phid.'/', + 'ref' => $ref, + )); + } + + if ($this->getIsEditable()) { + $items[] = id(new PhabricatorActionView()) + ->setIcon('edit') + ->setHref('/transactions/edit/'.$xaction_phid.'/') + ->setName(pht('Edit Comment')) + ->addSigil('transaction-edit') + ->setMetadata( + array( + 'anchor' => $anchor, + )); + } + + if ($this->getIsRemovable()) { + $items[] = id(new PhabricatorActionView()) + ->setIcon('delete') + ->setHref('/transactions/remove/'.$xaction_phid.'/') + ->setName(pht('Remove Comment')) + ->addSigil('transaction-remove') + ->setMetadata( + array( + 'anchor' => $anchor, + )); + + } + + if ($this->getIsEdited()) { + $items[] = id(new PhabricatorActionView()) + ->setIcon('transcript') + ->setHref('/transactions/history/'.$xaction_phid.'/') + ->setName(pht('View Edit History')) + ->setWorkflow(true); + } + + return $items; + } + } diff --git a/webroot/rsrc/css/core/core.css b/webroot/rsrc/css/core/core.css index 58f75c1486..114752e5f4 100644 --- a/webroot/rsrc/css/core/core.css +++ b/webroot/rsrc/css/core/core.css @@ -127,6 +127,13 @@ hr { .aural-only { position: absolute !important; clip: rect(1px, 1px, 1px, 1px); + + /* NOTE: Without this, Safari sometimes lays these elements out at normal + size. An example is the label on the comment action menu on timelines. */ + + width: 0; + height: 0; + overflow: hidden; } .visual-only { @@ -135,6 +142,9 @@ hr { .audible .aural-only { clip: auto; + width: auto; + height: auto; + overflow: auto; background: #006699; color: #ffffff; } diff --git a/webroot/rsrc/css/phui/phui-button.css b/webroot/rsrc/css/phui/phui-button.css index 7a5a048be1..b02875685a 100644 --- a/webroot/rsrc/css/phui/phui-button.css +++ b/webroot/rsrc/css/phui/phui-button.css @@ -98,12 +98,12 @@ body a.button:active { button.grey:active, a.grey:active, button.grey_active, -a.button.dropdown-open { +a.button.phuix-dropdown-open { background-color: #7d7d7d; box-shadow: inset 0 0 4px rgba(0,0,0,.2); } -a.dropdown-open { +a.phuix-dropdown-open { color: {$greytext}; } @@ -177,6 +177,12 @@ button.link:hover { border-bottom-color: {$greyborder}; } +.phuix-dropdown-menu a:focus { + /* We automatically focus links in dropdown menus for assistive devices, but + this is distracting for visual user agents. */ + outline: none; +} + a.policy-control { width: 240px; text-align: left; diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index 7b27fcf829..d536f978e9 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -125,6 +125,10 @@ padding-left: 38px; } +.phui-timeline-title-with-menu { + padding-right: 36px; +} + .phui-timeline-view .phui-icon-view.phui-timeline-token { vertical-align: middle; margin-right: 4px; @@ -316,3 +320,48 @@ font-weight: normal; color: {$bluetext}; } + +.phui-timeline-comment-actions .phui-icon-view { + width: 16px; + height: 16px; + font-size: 16px; + text-align: center; + overflow: hidden; +} + +.phui-timeline-menu { + position: absolute; + right: 3px; + top: 4px; + width: 28px; + height: 22px; + text-align: center; + line-height: 22px; + font-size: 15px; + border-left: 1px solid {$lightblueborder}; +} + +.phui-timeline-menu:focus { + outline: none; +} + +.phui-timeline-menu .phui-icon-view { + color: {$lightgreytext}; +} + +a.phui-timeline-menu .phui-icon-view { + color: {$bluetext}; +} + +.device-desktop a.phui-timeline-menu:hover .phui-icon-view { + color: {$darkgreytext}; +} + +.phui-timeline-menu.phuix-dropdown-open { + background: {$blue}; +} + +.phui-timeline-menu.phuix-dropdown-open .phui-icon-view, +.device-desktop a.phuix-dropdown-open:hover .phui-icon-view { + color: #ffffff; +} diff --git a/webroot/rsrc/js/application/transactions/behavior-transaction-list.js b/webroot/rsrc/js/application/transactions/behavior-transaction-list.js index abed702c14..8c56da6a74 100644 --- a/webroot/rsrc/js/application/transactions/behavior-transaction-list.js +++ b/webroot/rsrc/js/application/transactions/behavior-transaction-list.js @@ -5,7 +5,7 @@ * javelin-workflow * javelin-dom * javelin-fx - * javelin-util + * javelin-uri * phabricator-textareautils */ @@ -64,64 +64,37 @@ JX.behavior('phabricator-transaction-list', function(config) { } } - function edittransaction(transaction, response) { - // NOTE: this is for 1 transaction only - for (var phid in response.xactions) { - var new_node = JX.$H(response.xactions[phid]).getFragment().firstChild; - var new_comment = JX.DOM.find(new_node, 'span', 'transaction-comment'); - var old_comments = JX.DOM.scry( - transaction, - 'span', - 'transaction-comment'); - var old_comment = old_comments[0]; - JX.DOM.replace(old_comment, new_comment); - var edit_history = JX.DOM.scry( - transaction, - 'a', - 'transaction-edit-history'); - if (!edit_history.length) { - var transaction_phid = JX.Stratcom.getData(new_comment).phid; - var history_link = JX.$N( - 'a', - { sigil : 'transaction-edit-history', - href : config.historyLink + transaction_phid + '/' }, - config.historyLinkText); - JX.Stratcom.addSigil(history_link, 'workflow'); - var timeline_extra = JX.DOM.find(transaction, 'span', 'timeline-extra'); - var old_content = JX.$H(timeline_extra.innerHTML); - JX.DOM.setContent( - timeline_extra, - [history_link, config.linkDelimiter, old_content]); - } - new JX.FX(transaction).setDuration(500).start({opacity: [0, 1]}); - } - } - - JX.DOM.listen( - list, + JX.Stratcom.listen( 'click', - ['transaction-edit', 'transaction-remove'], + [['transaction-edit'], ['transaction-remove']], function(e) { if (!e.isNormalClick()) { return; } - var transaction = e.getNode('transaction'); + e.prevent(); - JX.Workflow.newFromLink(e.getTarget()) - .setData({anchor: e.getNodeData('transaction').anchor}) - .setHandler(JX.bind(null, edittransaction, transaction)) + var anchor = e.getNodeData('tag:a').anchor; + var uri = JX.$U(window.location).setFragment(anchor); + + JX.Workflow.newFromLink(e.getNode('tag:a')) + .setHandler(function() { + // In most cases, `uri` is on the same page (just at a new anchor), + // so we have to call reload() explicitly to get the browser to + // refresh the page. It would be nice to just issue a server-side + // redirect instead, but there isn't currently an easy way to do + // that without complexity and/or a semi-open redirect. + uri.go(); + window.location.reload(); + }) .start(); - - e.kill(); }); - JX.DOM.listen( - list, + JX.Stratcom.listen( 'click', 'transaction-quote', function(e) { - e.kill(); + e.prevent(); var data = e.getNodeData('transaction-quote'); new JX.Workflow(data.uri) diff --git a/webroot/rsrc/js/phui/behavior-phui-timeline-dropdown-menu.js b/webroot/rsrc/js/phui/behavior-phui-timeline-dropdown-menu.js new file mode 100644 index 0000000000..2c3e3ded9b --- /dev/null +++ b/webroot/rsrc/js/phui/behavior-phui-timeline-dropdown-menu.js @@ -0,0 +1,34 @@ +/** + * @provides javelin-behavior-phui-timeline-dropdown-menu + * @requires javelin-behavior + * javelin-stratcom + * javelin-dom + * phuix-dropdown-menu + */ + +JX.behavior('phui-timeline-dropdown-menu', function() { + + JX.Stratcom.listen('click', 'phui-timeline-menu', function(e) { + var data = e.getNodeData('phui-timeline-menu'); + if (data.menu) { + return; + } + + e.kill(); + + var list = JX.$H(data.items).getFragment().firstChild; + + var icon = e.getNode('phui-timeline-menu'); + data.menu = new JX.PHUIXDropdownMenu(icon); + data.menu.setContent(list); + data.menu.open(); + + JX.DOM.listen(list, 'click', 'tag:a', function(e) { + if (!e.isNormalClick()) { + return; + } + data.menu.close(); + }); + }); + +}); diff --git a/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js b/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js index aa9fe4b224..3c93af21d2 100644 --- a/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js +++ b/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js @@ -38,6 +38,8 @@ JX.install('PHUIXDropdownMenu', { JX.bind(this, this._adjustposition)); JX.Stratcom.listen('phuix.dropdown.open', null, JX.bind(this, this.close)); + + JX.Stratcom.listen('keydown', null, JX.bind(this, this._onkey)); }, events: ['open'], @@ -93,9 +95,6 @@ JX.install('PHUIXDropdownMenu', { var menu = JX.$N('div', attrs); - this._node.setAttribute('aria-haspopup', 'true'); - this._node.setAttribute('aria-expanded', 'false'); - this._menu = menu; } @@ -143,6 +142,12 @@ JX.install('PHUIXDropdownMenu', { JX.DOM.alterClass(this._node, 'phuix-dropdown-open', true); this._node.setAttribute('aria-expanded', 'true'); + + // Try to highlight the first link in the menu for assistive technologies. + var links = JX.DOM.scry(this._menu, 'a'); + if (links[0]) { + JX.DOM.focus(links[0]); + } }, _hide : function() { @@ -176,6 +181,26 @@ JX.install('PHUIXDropdownMenu', { v = v.add(this.getOffsetX(), this.getOffsetY()); v.setPos(this._menu); + }, + + _onkey: function(e) { + // When the user presses escape with a menu open, close the menu and + // refocus the button which activates the menu. In particular, this makes + // popups more usable with assistive technologies. + + if (!this._open) { + return; + } + + if (e.getSpecialKey() != 'esc') { + return; + } + + this.close(); + JX.DOM.focus(this._node); + + e.prevent(); } + } });