@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.

Make new DiffInline code handle most "delete" operations

Summary:
Ref T12616. This moves the delete actions to the new, more stateful way of doing things.

These are a little tricky because you can click "Delete" on an inline, but you can also click "Delete" from the preview area at the bottom of the page. If you do, the inline you are deleting may or may not be present on the page.

This has a few bugs -- notably, deleting from the preview without interacting with the on-page inline first won't actually delete the on-page inline yet -- but nothing too serious.

Test Plan: Deleted inlines, undid deletion, deleted from preview.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T12616

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

+309 -135
+34 -34
resources/celerity/map.php
··· 13 13 'core.pkg.js' => '2ff7879f', 14 14 'darkconsole.pkg.js' => '1f9a31bc', 15 15 'differential.pkg.css' => '58712637', 16 - 'differential.pkg.js' => 'e01579c4', 16 + 'differential.pkg.js' => '271a1e1e', 17 17 'diffusion.pkg.css' => 'b93d9b8c', 18 18 'diffusion.pkg.js' => '84c8f8fd', 19 19 'favicon.ico' => '30672e08', ··· 390 390 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 391 391 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 392 392 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 393 - 'rsrc/js/application/diff/DiffChangeset.js' => '80ac3298', 394 - 'rsrc/js/application/diff/DiffChangesetList.js' => '12575699', 395 - 'rsrc/js/application/diff/DiffInline.js' => 'bd1b3258', 393 + 'rsrc/js/application/diff/DiffChangeset.js' => '57b29223', 394 + 'rsrc/js/application/diff/DiffChangesetList.js' => '50bc5b50', 395 + 'rsrc/js/application/diff/DiffInline.js' => '64dfc791', 396 396 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 397 397 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '9ed8d2b6', 398 398 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 399 399 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 400 400 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', 401 - 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '97f363fc', 401 + 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '9d9dbc38', 402 402 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '92904457', 403 403 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', 404 404 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', ··· 624 624 'javelin-behavior-diff-preview-link' => '051c7832', 625 625 'javelin-behavior-differential-comment-jump' => '4fdb476d', 626 626 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 627 - 'javelin-behavior-differential-edit-inline-comments' => '97f363fc', 627 + 'javelin-behavior-differential-edit-inline-comments' => '9d9dbc38', 628 628 'javelin-behavior-differential-feedback-preview' => 'b064af76', 629 629 'javelin-behavior-differential-keyboard-navigation' => '92904457', 630 630 'javelin-behavior-differential-populate' => '5e41c819', ··· 785 785 'phabricator-darklog' => 'c8e1ffe3', 786 786 'phabricator-darkmessage' => 'c48cccdd', 787 787 'phabricator-dashboard-css' => 'fe5b1869', 788 - 'phabricator-diff-changeset' => '80ac3298', 789 - 'phabricator-diff-changeset-list' => '12575699', 790 - 'phabricator-diff-inline' => 'bd1b3258', 788 + 'phabricator-diff-changeset' => '57b29223', 789 + 'phabricator-diff-changeset-list' => '50bc5b50', 790 + 'phabricator-diff-inline' => '64dfc791', 791 791 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 792 792 'phabricator-draggable-list' => 'bea6e7f4', 793 793 'phabricator-fatal-config-template-css' => '8f18fa41', ··· 999 999 'javelin-util', 1000 1000 'javelin-dom', 1001 1001 'javelin-typeahead-normalizer', 1002 - ), 1003 - 12575699 => array( 1004 - 'javelin-install', 1005 1002 ), 1006 1003 '1499a8cb' => array( 1007 1004 'javelin-behavior', ··· 1301 1298 'javelin-typeahead-source', 1302 1299 'javelin-util', 1303 1300 ), 1301 + '50bc5b50' => array( 1302 + 'javelin-install', 1303 + ), 1304 1304 '519705ea' => array( 1305 1305 'javelin-install', 1306 1306 'javelin-dom', ··· 1342 1342 'javelin-vector', 1343 1343 'javelin-dom', 1344 1344 ), 1345 + '57b29223' => array( 1346 + 'javelin-dom', 1347 + 'javelin-util', 1348 + 'javelin-stratcom', 1349 + 'javelin-install', 1350 + 'javelin-workflow', 1351 + 'javelin-router', 1352 + 'javelin-behavior-device', 1353 + 'javelin-vector', 1354 + 'phabricator-diff-inline', 1355 + ), 1345 1356 '58dea2fa' => array( 1346 1357 'javelin-install', 1347 1358 'javelin-util', ··· 1409 1420 'javelin-behavior', 1410 1421 'javelin-stratcom', 1411 1422 'javelin-workflow', 1423 + 'javelin-dom', 1424 + ), 1425 + '64dfc791' => array( 1412 1426 'javelin-dom', 1413 1427 ), 1414 1428 '680ea2c8' => array( ··· 1530 1544 'javelin-vector', 1531 1545 'javelin-stratcom', 1532 1546 ), 1533 - '80ac3298' => array( 1534 - 'javelin-dom', 1535 - 'javelin-util', 1536 - 'javelin-stratcom', 1537 - 'javelin-install', 1538 - 'javelin-workflow', 1539 - 'javelin-router', 1540 - 'javelin-behavior-device', 1541 - 'javelin-vector', 1542 - 'phabricator-diff-inline', 1543 - ), 1544 1547 '834a1173' => array( 1545 1548 'javelin-behavior', 1546 1549 'javelin-scrollbar', ··· 1667 1670 'javelin-mask', 1668 1671 'phabricator-drag-and-drop-file-upload', 1669 1672 ), 1670 - '97f363fc' => array( 1671 - 'javelin-behavior', 1672 - 'javelin-stratcom', 1673 - 'javelin-dom', 1674 - 'javelin-util', 1675 - 'javelin-vector', 1676 - 'differential-inline-comment-editor', 1677 - ), 1678 1673 '988040b4' => array( 1679 1674 'javelin-install', 1680 1675 'javelin-dom', ··· 1702 1697 ), 1703 1698 '9d9685d6' => array( 1704 1699 'phui-oi-list-view-css', 1700 + ), 1701 + '9d9dbc38' => array( 1702 + 'javelin-behavior', 1703 + 'javelin-stratcom', 1704 + 'javelin-dom', 1705 + 'javelin-util', 1706 + 'javelin-vector', 1707 + 'differential-inline-comment-editor', 1705 1708 ), 1706 1709 '9ed8d2b6' => array( 1707 1710 'javelin-dom', ··· 1909 1912 'javelin-stratcom', 1910 1913 'javelin-vector', 1911 1914 'phui-hovercard', 1912 - ), 1913 - 'bd1b3258' => array( 1914 - 'javelin-dom', 1915 1915 ), 1916 1916 'bdaf4d04' => array( 1917 1917 'javelin-behavior',
+17 -1
webroot/rsrc/js/application/diff/DiffChangeset.js
··· 322 322 return JX.Stratcom.getData(this._node); 323 323 }, 324 324 325 - 326 325 _onresponse: function(sequence, response) { 327 326 if (sequence != this._sequence) { 328 327 // If this isn't the most recent request, ignore it. This normally ··· 420 419 } 421 420 422 421 return data.inline; 422 + }, 423 + 424 + getInlineByID: function(id) { 425 + // TODO: Currently, this will only find inlines which the user has 426 + // already interacted with! Inlines are built lazily as events arrive. 427 + // This can not yet find inlines which are passively present in the 428 + // document. 429 + 430 + for (var ii = 0; ii < this._inlines.length; ii++) { 431 + var inline = this._inlines[ii]; 432 + if (inline.getID() == id) { 433 + return inline; 434 + } 435 + } 436 + 437 + return null; 423 438 } 439 + 424 440 425 441 }, 426 442
+76
webroot/rsrc/js/application/diff/DiffChangesetList.js
··· 35 35 'click', 36 36 ['differential-inline-comment', 'differential-inline-done'], 37 37 ondone); 38 + 39 + var ondelete = JX.bind(this, this._ifawake, this._onaction, 'delete'); 40 + JX.Stratcom.listen( 41 + 'click', 42 + ['differential-inline-comment', 'differential-inline-delete'], 43 + ondelete); 38 44 }, 39 45 40 46 properties: { ··· 69 75 70 76 getChangesetForNode: function(node) { 71 77 return JX.DiffChangeset.getForNode(node); 78 + }, 79 + 80 + getInlineByID: function(id) { 81 + var inline = null; 82 + 83 + for (var ii = 0; ii < this._changesets.length; ii++) { 84 + inline = this._changesets[ii].getInlineByID(id); 85 + if (inline) { 86 + break; 87 + } 88 + } 89 + 90 + return inline; 72 91 }, 73 92 74 93 _ifawake: function(f) { ··· 351 370 e.prevent(); 352 371 353 372 var inline = this._getInlineForEvent(e); 373 + var is_ref = false; 374 + 375 + // If we don't have a natural inline object, the user may have clicked 376 + // an action (like "Delete") inside a preview element at the bottom of 377 + // the page. 378 + 379 + // If they did, try to find an associated normal inline to act on, and 380 + // pretend they clicked that instead. This makes the overall state of 381 + // the page more consistent. 382 + 383 + // However, there may be no normal inline (for example, because it is 384 + // on a version of the diff which is not visible). In this case, we 385 + // act by reference. 386 + 387 + if (inline === null) { 388 + var data = e.getNodeData('differential-inline-comment'); 389 + inline = this.getInlineByID(data.id); 390 + if (inline) { 391 + is_ref = true; 392 + } else { 393 + switch (action) { 394 + case 'delete': 395 + this._deleteInlineByID(data.id); 396 + return; 397 + } 398 + } 399 + } 354 400 355 401 // TODO: For normal operations, highlight the inline range here. 356 402 ··· 361 407 case 'done': 362 408 inline.toggleDone(); 363 409 break; 410 + case 'delete': 411 + inline.delete(is_ref); 412 + break; 364 413 } 365 414 }, 366 415 416 + redrawPreview: function() { 417 + // TODO: This isn't the cleanest way to find the preview form, but 418 + // rendering no longer has direct access to it. 419 + var forms = JX.DOM.scry(document.body, 'form', 'transaction-append'); 420 + if (forms.length) { 421 + JX.DOM.invoke(forms[0], 'shouldRefresh'); 422 + } 423 + }, 424 + 425 + _deleteInlineByID: function(id) { 426 + var uri = this.getInlineURI(); 427 + var data = { 428 + op: 'refdelete', 429 + id: id 430 + }; 431 + 432 + var handler = JX.bind(this, this.redrawPreview); 433 + 434 + new JX.Workflow(uri, data) 435 + .setHandler(handler) 436 + .start(); 437 + }, 438 + 367 439 _getInlineForEvent: function(e) { 368 440 var node = e.getNode('differential-changeset'); 441 + if (!node) { 442 + return null; 443 + } 444 + 369 445 var changeset = this.getChangesetForNode(node); 370 446 371 447 var inline_row = e.getNode('inline-row');
+167 -40
webroot/rsrc/js/application/diff/DiffInline.js
··· 47 47 _length: null, 48 48 _displaySide: null, 49 49 _isNewFile: null, 50 + _undoRow: null, 51 + 52 + _isDeleted: false, 53 + _isInvisible: false, 54 + _isLoading: false, 50 55 51 56 setHidden: function(hidden) { 52 57 this._hidden = hidden; ··· 106 111 }, 107 112 108 113 edit: function() { 109 - var handler = JX.bind(this, this._oneditresponse); 110 114 var uri = this._getInlineURI(); 111 - var data = this._newRequestData(); 115 + var handler = JX.bind(this, this._oneditresponse); 116 + var data = this._newRequestData('edit'); 112 117 113 - // TODO: Set state to "loading". 118 + this.setLoading(true); 114 119 115 120 new JX.Request(uri, handler) 116 121 .setData(data) 117 122 .send(); 118 123 }, 119 124 125 + delete: function(is_ref) { 126 + var uri = this._getInlineURI(); 127 + var handler = JX.bind(this, this._ondeleteresponse); 128 + 129 + // NOTE: This may be a direct delete (the user clicked on the inline 130 + // itself) or a "refdelete" (the user clicked somewhere else, like the 131 + // preview, but the inline is present on the page). 132 + 133 + // For a "refdelete", we prompt the user to confirm that they want to 134 + // delete the comment, because they can not undo deletions from the 135 + // preview. We could jump the user to the inline instead, but this would 136 + // be somewhat disruptive and make deleting several comments more 137 + // difficult. 138 + 139 + var op; 140 + if (is_ref) { 141 + op = 'refdelete'; 142 + } else { 143 + op = 'delete'; 144 + } 145 + 146 + var data = this._newRequestData(op); 147 + 148 + this.setLoading(true); 149 + 150 + new JX.Workflow(uri, data) 151 + .setHandler(handler) 152 + .start(); 153 + }, 154 + 120 155 getDisplaySide: function() { 121 156 return this._displaySide; 122 157 }, ··· 133 168 return this._isNewFile; 134 169 }, 135 170 136 - _newRequestData: function() { 171 + getID: function() { 172 + return this._id; 173 + }, 174 + 175 + setDeleted: function(deleted) { 176 + this._isDeleted = deleted; 177 + this._redraw(); 178 + return this; 179 + }, 180 + 181 + setInvisible: function(invisible) { 182 + this._isInvisible = invisible; 183 + this._redraw(); 184 + return this; 185 + }, 186 + 187 + setLoading: function(loading) { 188 + this._isLoading = loading; 189 + this._redraw(); 190 + return this; 191 + }, 192 + 193 + _newRequestData: function(operation) { 137 194 return { 138 - op: 'edit', 195 + op: operation, 139 196 id: this._id, 140 197 on_right: ((this.getDisplaySide() == 'right') ? 1 : 0), 141 198 renderer: this.getChangeset().getRenderer(), ··· 151 208 152 209 this._drawEditRows(rows); 153 210 154 - // TODO: Set the row state to "hidden". 211 + this.setLoading(false); 212 + this.setInvisible(true); 213 + }, 214 + 215 + _ondeleteresponse: function() { 216 + this._drawUndoRows(); 217 + 218 + this.setLoading(false); 219 + this.setDeleted(true); 220 + 221 + this._didUpdate(); 222 + }, 223 + 224 + _drawUndoRows: function() { 225 + var templates = this.getChangeset().getUndoTemplates(); 226 + 227 + var template; 228 + if (this.getDisplaySide() == 'right') { 229 + template = templates.r; 230 + } else { 231 + template = templates.l; 232 + } 233 + template = JX.$H(template).getNode(); 234 + 235 + this._undoRow = this._drawRows(template, this._row, 'undo'); 155 236 }, 156 237 157 238 _drawEditRows: function(rows) { 239 + return this._drawRows(rows, null, 'edit'); 240 + }, 241 + 242 + _drawRows: function(rows, cursor, type) { 158 243 var first_row = JX.DOM.scry(rows, 'tr')[0]; 244 + var first_meta; 159 245 var row = first_row; 160 - var cursor = this._row; 246 + cursor = cursor || this._row.nextSibling; 161 247 248 + var next_row; 162 249 while (row) { 250 + // Grab this first, since it's going to change once we insert the row 251 + // into the document. 252 + next_row = row.nextSibling; 253 + 163 254 cursor.parentNode.insertBefore(row, cursor.nextSibling); 164 255 cursor = row; 165 256 166 257 var row_meta = { 167 258 node: row, 168 - type: 'edit', 259 + type: type, 169 260 listeners: [] 170 261 }; 171 262 172 - row_meta.listeners.push( 173 - JX.DOM.listen( 174 - row, 175 - ['submit', 'didSyntheticSubmit'], 176 - 'inline-edit-form', 177 - JX.bind(this, this._onsubmit, row_meta))); 263 + if (!first_meta) { 264 + first_meta = row_meta; 265 + } 178 266 179 - row_meta.listeners.push( 180 - JX.DOM.listen( 181 - row, 182 - 'click', 183 - 'inline-edit-cancel', 184 - JX.bind(this, this._oncancel, row_meta))); 267 + if (type == 'edit') { 268 + row_meta.listeners.push( 269 + JX.DOM.listen( 270 + row, 271 + ['submit', 'didSyntheticSubmit'], 272 + 'inline-edit-form', 273 + JX.bind(this, this._onsubmit, row_meta))); 185 274 186 - row = row.nextSibling; 275 + row_meta.listeners.push( 276 + JX.DOM.listen( 277 + row, 278 + 'click', 279 + 'inline-edit-cancel', 280 + JX.bind(this, this._oncancel, row_meta))); 281 + } else { 282 + row_meta.listeners.push( 283 + JX.DOM.listen( 284 + row, 285 + 'click', 286 + 'differential-inline-comment-undo', 287 + JX.bind(this, this._onundo, row_meta))); 288 + } 289 + 290 + row = next_row; 187 291 } 188 292 189 - return first_row; 293 + return first_meta; 190 294 }, 191 295 192 296 _onsubmit: function(row, e) { 193 297 e.kill(); 194 298 195 299 var handler = JX.bind(this, this._onsubmitresponse, row); 300 + 301 + this.setLoading(true); 196 302 197 303 JX.Workflow.newFromForm(e.getTarget()) 198 304 .setHandler(handler) 199 305 .start(); 306 + }, 307 + 308 + _onundo: function(row, e) { 309 + e.kill(); 310 + 311 + this._removeRow(row); 200 312 201 - // TODO: Set state to "loading". 313 + var uri = this._getInlineURI(); 314 + var data = this._newRequestData('undelete'); 315 + var handler = JX.bind(this, this._onundelete); 316 + 317 + this.setDeleted(false); 318 + this.setLoading(true); 319 + 320 + new JX.Request(uri, handler) 321 + .setData(data) 322 + .send(); 323 + }, 324 + 325 + _onundelete: function() { 326 + this.setLoading(false); 327 + this._didUpdate(); 202 328 }, 203 329 204 330 _oncancel: function(row, e) { ··· 206 332 207 333 // TODO: Capture edited text and offer "undo". 208 334 209 - JX.DOM.remove(row.node); 210 - this._removeListeners(row.listeners); 335 + this._removeRow(row); 211 336 212 - // TODO: Restore state to "normal". 337 + this.setInvisible(false); 213 338 }, 214 339 215 340 _onsubmitresponse: function(row, response) { 341 + this._removeRow(row); 216 342 217 - JX.DOM.remove(row.node); 218 - this._removeListeners(row.listeners); 219 - 220 - // TODO: Restore state to "normal". 343 + this.setInvisible(false); 221 344 222 345 this._onupdate(response); 223 346 }, ··· 225 348 _onupdate: function(response) { 226 349 var new_row; 227 350 if (response.markup) { 228 - new_row = this._drawEditRows(JX.$H(response.markup).getNode()); 351 + new_row = this._drawEditRows(JX.$H(response.markup).getNode()).node; 229 352 } 230 353 231 354 // TODO: Save the old row so the action it's undo-able if it was a ··· 243 366 _didUpdate: function() { 244 367 // After making changes to inline comments, refresh the transaction 245 368 // preview at the bottom of the page. 369 + this.getChangeset().getChangesetList().redrawPreview(); 370 + }, 246 371 247 - // TODO: This isn't the cleanest way to find the preview form, but 248 - // rendering no longer has direct access to it. 249 - var forms = JX.DOM.scry(document.body, 'form', 'transaction-append'); 250 - if (forms.length) { 251 - JX.DOM.invoke(forms[0], 'shouldRefresh'); 252 - } 372 + _redraw: function() { 373 + var is_invisible = (this._isInvisible || this._isDeleted); 374 + var is_loading = (this._isLoading); 375 + 376 + var row = this._row; 377 + JX.DOM.alterClass(row, 'differential-inline-hidden', is_invisible); 378 + JX.DOM.alterClass(row, 'differential-inline-loading', is_loading); 253 379 }, 254 380 255 - _removeListeners: function(listeners) { 256 - for (var ii = 0; ii < listeners.length; ii++) { 257 - listeners[ii].remove(); 381 + _removeRow: function(row) { 382 + JX.DOM.remove(row.node); 383 + for (var ii = 0; ii < row.listeners.length; ii++) { 384 + row.listeners[ii].remove(); 258 385 } 259 386 }, 260 387
+15 -60
webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js
··· 331 331 var handle_inline_action = function(node, op) { 332 332 var data = JX.Stratcom.getData(node); 333 333 334 - // If you click an action in the preview at the bottom of the page, we 335 - // find the corresponding node and simulate clicking that, if it's 336 - // present on the page. This gives the editor a more consistent view 337 - // of the document. 338 - if (JX.Stratcom.hasSigil(node, 'differential-inline-comment-preview')) { 339 - var nodes = JX.DOM.scry( 340 - JX.DOM.getContentFrame(), 341 - 'div', 342 - 'differential-inline-comment'); 343 - 344 - var found = false; 345 - var node_data; 346 - for (var ii = 0; ii < nodes.length; ++ii) { 347 - if (nodes[ii] == node) { 348 - // Don't match the preview itself. 349 - continue; 350 - } 351 - node_data = JX.Stratcom.getData(nodes[ii]); 352 - if (node_data.id == data.id) { 353 - node = nodes[ii]; 354 - data = node_data; 355 - found = true; 356 - break; 357 - } 358 - } 359 - 360 - if (!found) { 361 - switch (op) { 362 - case 'delete': 363 - new JX.DifferentialInlineCommentEditor(config.uri) 364 - .deleteByID(data.id); 365 - return; 366 - } 367 - } 368 - 369 - if (op == 'delete') { 370 - op = 'refdelete'; 371 - } 372 - } 373 - 374 334 var original = data.original; 375 335 var reply_phid = null; 376 336 if (op == 'reply') { ··· 387 347 'differential-changeset'); 388 348 var view = JX.DiffChangeset.getForNode(changeset_root); 389 349 390 - if (op == 'edit') { 391 - // This is now handled by DiffChangesetList. 392 - editor = true; 393 - } else { 394 - editor = new JX.DifferentialInlineCommentEditor(config.uri) 395 - .setTemplates(view.getUndoTemplates()) 396 - .setOperation(op) 397 - .setID(data.id) 398 - .setChangesetID(data.changesetID) 399 - .setLineNumber(data.number) 400 - .setLength(data.length) 401 - .setOnRight(data.on_right) 402 - .setOriginalText(original) 403 - .setRow(row) 404 - .setTable(row.parentNode) 405 - .setReplyToCommentPHID(reply_phid) 406 - .setRenderer(view.getRenderer()) 407 - .start(); 408 - } 350 + editor = new JX.DifferentialInlineCommentEditor(config.uri) 351 + .setTemplates(view.getUndoTemplates()) 352 + .setOperation(op) 353 + .setID(data.id) 354 + .setChangesetID(data.changesetID) 355 + .setLineNumber(data.number) 356 + .setLength(data.length) 357 + .setOnRight(data.on_right) 358 + .setOriginalText(original) 359 + .setRow(row) 360 + .setTable(row.parentNode) 361 + .setReplyToCommentPHID(reply_phid) 362 + .setRenderer(view.getRenderer()) 363 + .start(); 409 364 410 365 set_link_state(true); 411 366 }; 412 367 413 - for (var op in {'edit': 1, 'delete': 1, 'reply': 1}) { 368 + for (var op in {'reply': 1}) { 414 369 JX.Stratcom.listen( 415 370 'click', 416 371 ['differential-inline-comment', 'differential-inline-' + op],