@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Move inline comment actions into a dropdown menu

Summary: Ref T11401. Ref T13513. This paves the way for more comment actions, particularly an edit-after-submit action.

Test Plan: Took all actions from menus, via mouse and via keyboard (where applicable).

Maniphest Tasks: T13513, T11401

Differential Revision: https://secure.phabricator.com/D21244

+205 -119
+13 -13
resources/celerity/map.php
··· 13 13 'core.pkg.js' => '1e667bcb', 14 14 'dark-console.pkg.js' => '187792c2', 15 15 'differential.pkg.css' => 'd71d4531', 16 - 'differential.pkg.js' => '39781f05', 16 + 'differential.pkg.js' => '21616a78', 17 17 'diffusion.pkg.css' => '42c75c37', 18 18 'diffusion.pkg.js' => 'a98c0bf7', 19 19 'maniphest.pkg.css' => '35995d6d', ··· 380 380 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '1e413dc9', 381 381 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '0116d3e8', 382 382 'rsrc/js/application/diff/DiffChangeset.js' => '20715b98', 383 - 'rsrc/js/application/diff/DiffChangesetList.js' => '564cbd20', 384 - 'rsrc/js/application/diff/DiffInline.js' => 'a0ef0b54', 383 + 'rsrc/js/application/diff/DiffChangesetList.js' => '40d6c41c', 384 + 'rsrc/js/application/diff/DiffInline.js' => '15de2478', 385 385 'rsrc/js/application/diff/DiffPathView.js' => '8207abf9', 386 386 'rsrc/js/application/diff/DiffTreeView.js' => '5d83623b', 387 387 'rsrc/js/application/differential/behavior-diff-radios.js' => '925fe8cd', ··· 775 775 'phabricator-darkmessage' => '26cd4b73', 776 776 'phabricator-dashboard-css' => '5a205b9d', 777 777 'phabricator-diff-changeset' => '20715b98', 778 - 'phabricator-diff-changeset-list' => '564cbd20', 779 - 'phabricator-diff-inline' => 'a0ef0b54', 778 + 'phabricator-diff-changeset-list' => '40d6c41c', 779 + 'phabricator-diff-inline' => '15de2478', 780 780 'phabricator-diff-path-view' => '8207abf9', 781 781 'phabricator-diff-tree-view' => '5d83623b', 782 782 'phabricator-drag-and-drop-file-upload' => '4370900d', ··· 1033 1033 'javelin-dom', 1034 1034 'javelin-stratcom', 1035 1035 'javelin-util', 1036 + ), 1037 + '15de2478' => array( 1038 + 'javelin-dom', 1036 1039 ), 1037 1040 '1a844c06' => array( 1038 1041 'javelin-install', ··· 1259 1262 'javelin-behavior', 1260 1263 'javelin-uri', 1261 1264 ), 1265 + '40d6c41c' => array( 1266 + 'javelin-install', 1267 + 'phuix-button-view', 1268 + 'phabricator-diff-tree-view', 1269 + ), 1262 1270 '42c44e8b' => array( 1263 1271 'javelin-behavior', 1264 1272 'javelin-workflow', ··· 1417 1425 'javelin-behavior', 1418 1426 'javelin-stratcom', 1419 1427 'javelin-dom', 1420 - ), 1421 - '564cbd20' => array( 1422 - 'javelin-install', 1423 - 'phuix-button-view', 1424 - 'phabricator-diff-tree-view', 1425 1428 ), 1426 1429 '5793d835' => array( 1427 1430 'javelin-install', ··· 1832 1835 'javelin-workflow', 1833 1836 'javelin-util', 1834 1837 'phabricator-keyboard-shortcut', 1835 - ), 1836 - 'a0ef0b54' => array( 1837 - 'javelin-dom', 1838 1838 ), 1839 1839 'a17b84f1' => array( 1840 1840 'javelin-behavior',
+67 -56
src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php
··· 88 88 $is_synthetic = true; 89 89 } 90 90 91 + $is_preview = $this->preview; 92 + 91 93 $metadata = $this->getInlineCommentMetadata(); 92 94 93 95 $sigil = 'differential-inline-comment'; 94 - if ($this->preview) { 96 + if ($is_preview) { 95 97 $sigil = $sigil.' differential-inline-comment-preview'; 96 98 } 97 99 ··· 146 148 $classes[] = 'inline-comment-ghost'; 147 149 } 148 150 149 - // I think this is unused 150 - if ($inline->getHasReplies()) { 151 - $classes[] = 'inline-comment-has-reply'; 152 - } 153 - 154 151 if ($inline->getReplyToCommentPHID()) { 155 152 $classes[] = 'inline-comment-is-reply'; 156 153 } ··· 167 164 $anchor_name = $this->getAnchorName(); 168 165 169 166 $action_buttons = array(); 170 - 171 - $can_reply = 172 - (!$this->editable) && 173 - (!$this->preview) && 174 - ($this->allowReply) && 175 - 176 - // NOTE: No product reason why you can't reply to synthetic comments, 177 - // but the reply mechanism currently sends the inline comment ID to the 178 - // server, not file/line information, and synthetic comments don't have 179 - // an inline comment ID. 180 - (!$is_synthetic); 181 - 182 - 183 - if ($can_reply) { 184 - $action_buttons[] = id(new PHUIButtonView()) 185 - ->setTag('a') 186 - ->setIcon('fa-reply') 187 - ->setTooltip(pht('Reply')) 188 - ->addSigil('differential-inline-reply') 189 - ->setMustCapture(true) 190 - ->setAuralLabel(pht('Reply')); 191 - } 167 + $menu_items = array(); 192 168 193 - if ($this->editable && !$this->preview) { 194 - $action_buttons[] = id(new PHUIButtonView()) 195 - ->setTag('a') 196 - ->setIcon('fa-pencil') 197 - ->setTooltip(pht('Edit')) 198 - ->addSigil('differential-inline-edit') 199 - ->setMustCapture(true) 200 - ->setAuralLabel(pht('Edit')); 169 + if ($this->editable && !$is_preview) { 170 + $menu_items[] = array( 171 + 'label' => pht('Edit Comment'), 172 + 'icon' => 'fa-pencil', 173 + 'action' => 'edit', 174 + 'key' => 'e', 175 + ); 201 176 202 - $action_buttons[] = id(new PHUIButtonView()) 203 - ->setTag('a') 204 - ->setIcon('fa-trash-o') 205 - ->setTooltip(pht('Delete')) 206 - ->addSigil('differential-inline-delete') 207 - ->setMustCapture(true) 208 - ->setAuralLabel(pht('Delete')); 209 - 210 - } else if ($this->preview) { 177 + $menu_items[] = array( 178 + 'label' => pht('Delete Comment'), 179 + 'icon' => 'fa-trash-o', 180 + 'action' => 'delete', 181 + ); 182 + } else if ($is_preview) { 211 183 $links[] = javelin_tag( 212 184 'a', 213 185 array( ··· 228 200 ->setAuralLabel(pht('Delete')); 229 201 } 230 202 231 - if (!$this->preview && $this->canHide()) { 232 - $action_buttons[] = id(new PHUIButtonView()) 233 - ->setTag('a') 234 - ->setTooltip(pht('Collapse')) 235 - ->setIcon('fa-times') 236 - ->addSigil('hide-inline') 237 - ->setMustCapture(true) 238 - ->setAuralLabel(pht('Collapse')); 203 + if (!$is_preview && $this->canHide()) { 204 + $menu_items[] = array( 205 + 'label' => pht('Collapse'), 206 + 'icon' => 'fa-times', 207 + 'action' => 'collapse', 208 + 'key' => 'q', 209 + ); 210 + } 211 + 212 + $can_reply = 213 + (!$this->editable) && 214 + (!$is_preview) && 215 + ($this->allowReply) && 216 + 217 + // NOTE: No product reason why you can't reply to synthetic comments, 218 + // but the reply mechanism currently sends the inline comment ID to the 219 + // server, not file/line information, and synthetic comments don't have 220 + // an inline comment ID. 221 + (!$is_synthetic); 222 + 223 + if ($can_reply) { 224 + $menu_items[] = array( 225 + 'label' => pht('Reply to Comment'), 226 + 'icon' => 'fa-reply', 227 + 'action' => 'reply', 228 + 'key' => 'r', 229 + ); 230 + 231 + $menu_items[] = array( 232 + 'label' => pht('Quote Comment'), 233 + 'icon' => 'fa-quote-left', 234 + 'action' => 'quote', 235 + 'key' => 'R', 236 + ); 239 237 } 240 238 241 239 $done_button = null; ··· 283 281 $classes[] = 'inline-state-is-draft'; 284 282 } 285 283 286 - if ($mark_done && !$this->preview) { 284 + if ($mark_done && !$is_preview) { 287 285 $done_input = javelin_tag( 288 286 'input', 289 287 array( ··· 327 325 $inline, 328 326 PhabricatorInlineComment::MARKUP_FIELD_BODY); 329 327 330 - if ($this->preview) { 328 + if ($is_preview) { 331 329 $anchor = null; 332 330 } else { 333 331 $anchor = phutil_tag( ··· 364 362 } 365 363 366 364 $actions = null; 367 - if ($action_buttons) { 365 + if ($action_buttons || $menu_items) { 368 366 $actions = new PHUIButtonBarView(); 369 367 $actions->setBorderless(true); 370 368 $actions->addClass('inline-button-divider'); 371 369 foreach ($action_buttons as $button) { 372 370 $actions->addButton($button); 371 + } 372 + 373 + if (!$is_preview) { 374 + $menu_button = id(new PHUIButtonView()) 375 + ->setTag('a') 376 + ->setColor(PHUIButtonView::GREY) 377 + ->setDropdown(true) 378 + ->setAuralLabel(pht('Inline Actions')) 379 + ->addSigil('inline-action-dropdown'); 380 + 381 + $actions->addButton($menu_button); 373 382 } 374 383 } 375 384 ··· 400 409 ->setMaximumGlyphs(96) 401 410 ->truncateString($inline->getContent()); 402 411 $metadata['snippet'] = pht('%s: %s', $author, $snippet); 412 + 413 + $metadata['menuItems'] = $menu_items; 403 414 404 415 $markup = javelin_tag( 405 416 'div',
+9 -29
webroot/rsrc/js/application/diff/DiffChangesetList.js
··· 20 20 var onmenu = JX.bind(this, this._ifawake, this._onmenu); 21 21 JX.Stratcom.listen('click', 'differential-view-options', onmenu); 22 22 23 - var oncollapse = JX.bind(this, this._ifawake, this._oncollapse, true); 24 - JX.Stratcom.listen('click', 'hide-inline', oncollapse); 25 - 26 23 var onexpand = JX.bind(this, this._ifawake, this._oncollapse, false); 27 24 JX.Stratcom.listen('click', 'reveal-inline', onexpand); 28 25 ··· 336 333 var inline = cursor.target; 337 334 if (inline.canReply()) { 338 335 this.setFocus(null); 339 - 340 - var text; 341 - if (is_quote) { 342 - text = inline.getRawText(); 343 - text = '> ' + text.replace(/\n/g, '\n> ') + '\n\n'; 344 - } else { 345 - text = ''; 346 - } 347 - 348 - inline.reply(text); 336 + inline.reply(true); 349 337 return; 350 338 } 351 339 } ··· 2094 2082 'differential-inline-comment-undo', 2095 2083 onundo); 2096 2084 2097 - var onedit = JX.bind(this, this._onInlineEvent, 'edit'); 2098 - JX.Stratcom.listen( 2099 - 'click', 2100 - ['differential-inline-comment', 'differential-inline-edit'], 2101 - onedit); 2102 - 2103 2085 var ondone = JX.bind(this, this._onInlineEvent, 'done'); 2104 2086 JX.Stratcom.listen( 2105 2087 'click', ··· 2112 2094 ['differential-inline-comment', 'differential-inline-delete'], 2113 2095 ondelete); 2114 2096 2115 - var onreply = JX.bind(this, this._onInlineEvent, 'reply'); 2097 + var onmenu = JX.bind(this, this._onInlineEvent, 'menu'); 2116 2098 JX.Stratcom.listen( 2117 2099 'click', 2118 - ['differential-inline-comment', 'differential-inline-reply'], 2119 - onreply); 2100 + ['differential-inline-comment', 'inline-action-dropdown'], 2101 + onmenu); 2120 2102 2121 2103 var ondraft = JX.bind(this, this._onInlineEvent, 'draft'); 2122 2104 JX.Stratcom.listen( ··· 2156 2138 return; 2157 2139 } 2158 2140 2159 - if (action !== 'draft') { 2141 + if (action !== 'draft' && action !== 'menu') { 2160 2142 e.kill(); 2161 2143 } 2162 2144 ··· 2201 2183 case 'undo': 2202 2184 inline.undo(); 2203 2185 break; 2204 - case 'edit': 2205 - inline.edit(); 2206 - break; 2207 2186 case 'done': 2208 2187 inline.toggleDone(); 2209 2188 break; 2210 2189 case 'delete': 2211 2190 inline.delete(is_ref); 2212 2191 break; 2213 - case 'reply': 2214 - inline.reply(); 2215 - break; 2216 2192 case 'draft': 2217 2193 inline.triggerDraft(); 2194 + break; 2195 + case 'menu': 2196 + var node = e.getNode('inline-action-dropdown'); 2197 + inline.activateMenu(node, e); 2218 2198 break; 2219 2199 } 2220 2200 }
+116 -21
webroot/rsrc/js/application/diff/DiffInline.js
··· 21 21 _replyToCommentPHID: null, 22 22 _originalText: null, 23 23 _snippet: null, 24 + _menuItems: null, 24 25 _documentEngineKey: null, 25 26 26 27 _isDeleted: false, ··· 45 46 46 47 _draftRequest: null, 47 48 _skipFocus: false, 49 + _menu: null, 48 50 49 51 bindToRow: function(row) { 50 52 this._row = row; ··· 89 91 this._changesetID = data.changesetID; 90 92 this._isNew = false; 91 93 this._snippet = data.snippet; 94 + this._menuItems = data.menuItems; 92 95 this._documentEngineKey = data.documentEngineKey; 93 96 94 97 this._isEditing = data.isEditing; ··· 252 255 }, 253 256 254 257 canReply: function() { 255 - if (!this._hasAction('reply')) { 256 - return false; 257 - } 258 - 259 - return true; 258 + return this._hasMenuAction('reply'); 260 259 }, 261 260 262 261 canEdit: function() { 263 - if (!this._hasAction('edit')) { 264 - return false; 265 - } 266 - 267 - return true; 262 + return this._hasMenuAction('edit'); 268 263 }, 269 264 270 265 canDone: function() { ··· 276 271 }, 277 272 278 273 canCollapse: function() { 279 - if (!JX.DOM.scry(this._row, 'a', 'hide-inline').length) { 280 - return false; 281 - } 282 - 283 - return true; 274 + return this._hasMenuAction('collapse'); 284 275 }, 285 276 286 277 getRawText: function() { 287 278 return this._originalText; 288 279 }, 289 280 290 - _hasAction: function(action) { 291 - var nodes = JX.DOM.scry(this._row, 'a', 'differential-inline-' + action); 292 - return (nodes.length > 0); 293 - }, 294 - 295 281 _newRow: function() { 296 282 var attributes = { 297 283 sigil: 'inline-row' ··· 312 298 }, 313 299 314 300 setCollapsed: function(collapsed) { 301 + this._closeMenu(); 302 + 315 303 this._isCollapsed = collapsed; 316 304 317 305 var op; ··· 393 381 .send(); 394 382 }, 395 383 396 - reply: function(text) { 384 + reply: function(with_quote) { 385 + this._closeMenu(); 386 + 387 + var text; 388 + if (with_quote) { 389 + text = this.getRawText(); 390 + text = '> ' + text.replace(/\n/g, '\n> ') + '\n\n'; 391 + } else { 392 + text = ''; 393 + } 394 + 397 395 var changeset = this.getChangeset(); 398 396 return changeset.newInlineReply(this, text); 399 397 }, 400 398 401 399 edit: function(text, skip_focus) { 400 + this._closeMenu(); 401 + 402 402 this._skipFocus = !!skip_focus; 403 403 404 404 // If you edit an inline ("A"), modify the text ("AB"), cancel, and then ··· 880 880 triggerDraft: function() { 881 881 if (this._draftRequest) { 882 882 this._draftRequest.trigger(); 883 + } 884 + }, 885 + 886 + activateMenu: function(button, e) { 887 + // If we already have a menu for this button, let the menu handle the 888 + // event. 889 + var data = JX.Stratcom.getData(button); 890 + if (data.menu) { 891 + return; 892 + } 893 + 894 + e.prevent(); 895 + 896 + var menu = new JX.PHUIXDropdownMenu(button) 897 + .setWidth(240); 898 + 899 + var list = new JX.PHUIXActionListView(); 900 + var items = this._newMenuItems(menu); 901 + for (var ii = 0; ii < items.length; ii++) { 902 + list.addItem(items[ii]); 903 + } 904 + 905 + menu.setContent(list.getNode()); 906 + 907 + data.menu = menu; 908 + this._menu = menu; 909 + 910 + menu.listen('open', JX.bind(this, function() { 911 + var changeset_list = this.getChangeset().getChangesetList(); 912 + changeset_list.selectInline(this, true); 913 + })); 914 + 915 + menu.open(); 916 + }, 917 + 918 + _newMenuItems: function(menu) { 919 + var items = []; 920 + 921 + for (var ii = 0; ii < this._menuItems.length; ii++) { 922 + var spec = this._menuItems[ii]; 923 + 924 + var onmenu = JX.bind(this, this._onMenuItem, menu, spec.action); 925 + 926 + var item = new JX.PHUIXActionView() 927 + .setIcon(spec.icon) 928 + .setName(spec.label) 929 + .setHandler(onmenu); 930 + 931 + if (spec.key) { 932 + item.setKeyCommand(spec.key); 933 + } 934 + 935 + items.push(item); 936 + } 937 + 938 + return items; 939 + }, 940 + 941 + _onMenuItem: function(menu, action, e) { 942 + e.prevent(); 943 + menu.close(); 944 + 945 + switch (action) { 946 + case 'reply': 947 + this.reply(); 948 + break; 949 + case 'quote': 950 + this.reply(true); 951 + break; 952 + case 'collapse': 953 + this.setCollapsed(true); 954 + break; 955 + case 'delete': 956 + this.delete(); 957 + break; 958 + case 'edit': 959 + this.edit(); 960 + break; 961 + } 962 + 963 + }, 964 + 965 + _hasMenuAction: function(action) { 966 + for (var ii = 0; ii < this._menuItems.length; ii++) { 967 + var spec = this._menuItems[ii]; 968 + if (spec.action === action) { 969 + return true; 970 + } 971 + } 972 + return false; 973 + }, 974 + 975 + _closeMenu: function() { 976 + if (this._menu) { 977 + this._menu.close(); 883 978 } 884 979 } 885 980