mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-04 20:52:43 +01:00
e8126fa958
Summary: Fixes T3814. Broadly, remarkup tables in inline comments did not work properly. I ran into several messes here and cleaned up some of them: - Some of this code is doing `JX.$N('div', {}, JX.$H(response.markup))`, to turn an HTML response into a node, passing that around, and then doing junk with it. This is super old and gross. - The slightly more modern pattern is `JX.$H(response.markup).getFragment().firstChild`, but this is kind of yuck too and not as safe as it could be. - Introduce `JX.$H(response.markup).getNode()`, which actually expresses intent here. We have a bunch of `getFragment().firstChild` callsites which should switch to this, but I didn't clean those up yet because I don't want to test them all. - Switch the `JX.$N('div', {}, JX.$H(response.markup))`-style callsites to `JX.$H(response.markup).getNode()`. - `copyRows()` is too aggressive in finding `<tr />` tags. This actually causes the bug in T3814. We only want to find these tags at top level, not all tags. Don't copy `<tr />` tags which belong to some deeper table. - Once this is fixed, there's another bug with mousing over the cells in tables in inline comments. We select the nearest `<td />`, but that's the cell in the remarkup table. Instead, select the correct `<td />`. - At this point, these last two callsites were looking ugly. I provided `findAbove()` to clean them up. Test Plan: Created, edited, deleted, moused over, and reloaded a revision with inline comments including remarkup tables. Used "Show more context" links. Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T3814 Differential Revision: https://secure.phabricator.com/D6924
275 lines
7.7 KiB
JavaScript
275 lines
7.7 KiB
JavaScript
/**
|
|
* @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,
|
|
_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(),
|
|
length : this.getLength(),
|
|
changeset : this.getChangeset(),
|
|
text : this.getText() || ''
|
|
};
|
|
},
|
|
_draw : function(content, exact_row) {
|
|
var row = this.getRow();
|
|
var table = this.getTable();
|
|
var target = exact_row ? row : this._skipOverInlineCommentRows(row);
|
|
|
|
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]);
|
|
}
|
|
}
|
|
},
|
|
_undo : function() {
|
|
this._removeUndoLink();
|
|
|
|
this.setText(this._undoText);
|
|
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());
|
|
}
|
|
|
|
// These operations remove the old row (edit adds a new row first).
|
|
var remove_old = (op == 'edit' || op == 'delete');
|
|
if (remove_old) {
|
|
JX.DOM.remove(this.getRow());
|
|
var other_rows = this.getOtherRows();
|
|
for(var i = 0; i < other_rows.length; ++i) {
|
|
JX.DOM.remove(other_rows[i]);
|
|
}
|
|
}
|
|
|
|
// Once the user saves something, get rid of the 'undo' option. A
|
|
// particular case where we need this is saving a delete, when we might
|
|
// otherwise leave around an 'undo' for an earlier edit to the same
|
|
// comment.
|
|
this._removeUndoLink();
|
|
|
|
JX.Stratcom.invoke('differential-inline-comment-update');
|
|
this.invoke('done');
|
|
},
|
|
_didCancelWorkflow : function() {
|
|
this.invoke('done');
|
|
|
|
var op = this.getOperation();
|
|
if (op == 'delete') {
|
|
// No undo for delete, we prompt the user explicitly.
|
|
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;
|
|
|
|
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;
|
|
},
|
|
|
|
start : function() {
|
|
this._registerUndoListener();
|
|
|
|
var data = this._buildRequestData();
|
|
|
|
var op = this.getOperation();
|
|
|
|
|
|
if (op == 'delete') {
|
|
this._setRowState('loading');
|
|
var oncomplete = JX.bind(this, this._didCompleteWorkflow);
|
|
var onclose = JX.bind(this, function() {
|
|
this._setRowState('visible');
|
|
this._didCancelWorkflow();
|
|
});
|
|
|
|
new JX.Workflow(this._uri, data)
|
|
.setHandler(oncomplete)
|
|
.setCloseHandler(onclose)
|
|
.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;
|
|
}
|
|
},
|
|
|
|
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,
|
|
otherRows: [],
|
|
table : null,
|
|
onRight : null,
|
|
ID : null,
|
|
lineNumber : null,
|
|
changeset : null,
|
|
length : null,
|
|
isNew : null,
|
|
text : null,
|
|
templates : null,
|
|
originalText : null
|
|
}
|
|
|
|
});
|