1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-24 22:40:55 +01:00
phorge-phorge/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js
epriestley e8126fa958 Don't mangle inline comments with tables in them in Differential
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
2013-09-10 15:31:32 -07:00

270 lines
6.8 KiB
JavaScript

/**
* @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'});
JX.DOM.hide(reticle);
document.body.appendChild(reticle);
var origin = null;
var target = null;
var root = null;
var changeset = null;
var editor = null;
function updateReticle() {
var top = origin;
var bot = target;
if (JX.$V(top).y > JX.$V(bot).y) {
var tmp = top;
top = bot;
bot = tmp;
}
var code = target.nextSibling;
var pos = JX.$V(top).add(1 + JX.Vector.getDim(target).x, 0);
var dim = JX.Vector.getDim(code).add(-4, 0);
if (isOnRight(target)) {
dim.x += JX.Vector.getDim(code.nextSibling).x;
}
dim.y = (JX.$V(bot).y - pos.y) + JX.Vector.getDim(bot).y;
pos.setPos(reticle);
dim.setDim(reticle);
JX.DOM.show(reticle);
}
function hideReticle() {
JX.DOM.hide(reticle);
}
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 (editor ||
selecting ||
e.isRightButton() ||
getRowNumber(e.getTarget()) === undefined) {
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',
['differential-changeset', 'tag:th'],
function(e) {
if (!selecting ||
editor ||
(getRowNumber(e.getTarget()) === undefined) ||
(isOnRight(e.getTarget()) != isOnRight(origin)) ||
(e.getNode('differential-changeset') !== root)) {
return;
}
target = e.getTarget();
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;
}
editor = new JX.DifferentialInlineCommentEditor(config.uri)
.setTemplates(config.undo_templates)
.setOperation('new')
.setChangeset(changeset)
.setLineNumber(o)
.setLength(len)
.setIsNew(isNewFile(target) ? 1 : 0)
.setOnRight(isOnRight(target) ? 1 : 0)
.setRow(insert.nextSibling)
.setTable(insert.parentNode)
.start();
set_link_state(true);
e.kill();
});
JX.Stratcom.listen(
['mouseover', 'mouseout'],
'differential-inline-comment',
function(e) {
if (e.getType() == 'mouseout') {
hideReticle();
} else {
root = e.getNode('differential-changeset');
if (root) {
var data = e.getNodeData('differential-inline-comment');
var change = e.getNodeData('differential-changeset');
var id_part = data.on_right ? change.right : change.left;
// NOTE: We can't just look for 'tag:td' because the event might be
// inside a table which is inside an inline comment.
var comment = e.getNode('differential-inline-comment');
var td = JX.DOM.findAbove(comment, 'td');
var th = td.previousSibling;
var new_part = isNewFile(th) ? '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();
}
}
});
var action_handler = function(op, e) {
e.kill();
if (editor) {
return;
}
var node = e.getNode('differential-inline-comment');
handle_inline_action(node, op);
};
var handle_inline_action = function(node, op) {
var data = JX.Stratcom.getData(node);
var row = node.parentNode.parentNode;
var other_rows = [];
if (JX.Stratcom.hasSigil(node, 'differential-inline-comment-preview')) {
// The DOM structure around the comment is different if it's part of the
// preview, so make sure not to pass the wrong container.
row = node;
if (op === 'delete') {
// Furthermore, deleting a comment in the preview does not automatically
// delete other occurrences of the same comment, so do that manually.
var nodes = JX.DOM.scry(
document.body,
'div',
'differential-inline-comment');
for (var i = 0; i < nodes.length; ++i) {
if (JX.Stratcom.getData(nodes[i]).id === data.id) {
other_rows.push(nodes[i]);
}
}
}
}
var original = data.original;
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 = '';
}
editor = new JX.DifferentialInlineCommentEditor(config.uri)
.setTemplates(config.undo_templates)
.setOperation(op)
.setID(data.id)
.setLineNumber(data.number)
.setLength(data.length)
.setOnRight(data.on_right)
.setOriginalText(original)
.setRow(row)
.setOtherRows(other_rows)
.setTable(row.parentNode)
.start();
set_link_state(true);
};
for (var op in {'edit' : 1, 'delete' : 1, 'reply' : 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);
});
});