/** * @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'}); var old_cells = []; JX.DOM.hide(reticle); var origin = null; var target = null; var root = null; var changeset = null; var editor = null; function updateReticle() { JX.DOM.getContentFrame().appendChild(reticle); var top = origin; var bot = target; if (JX.$V(top).y > JX.$V(bot).y) { var tmp = top; top = bot; bot = tmp; } // Find the leftmost cell that we're going to highlight: this is the next // in the row. In 2up views, it should be directly adjacent. In // 1up views, we may have to skip over the other line number column. var l = top; while (JX.DOM.isType(l, 'th')) { l = l.nextSibling; } // Find the rightmost cell that we're going to highlight: this is the // farthest consecutive, adjacent in the row. Sometimes the left // and right nodes are the same (left side of 2up view); sometimes we're // going to highlight several nodes (copy + code + coverage). var r = l; while (r.nextSibling && JX.DOM.isType(r.nextSibling, 'td')) { r = r.nextSibling; } var pos = JX.$V(l) .add(JX.Vector.getAggregateScrollForNode(l)); var dim = JX.$V(r) .add(JX.Vector.getAggregateScrollForNode(r)) .add(-pos.x, -pos.y) .add(JX.Vector.getDim(r)); var bpos = JX.$V(bot) .add(JX.Vector.getAggregateScrollForNode(bot)); dim.y = (bpos.y - pos.y) + JX.Vector.getDim(bot).y; pos.setPos(reticle); dim.setDim(reticle); JX.DOM.show(reticle); // Find all the cells in the same row position between the top and bottom // cell, so we can highlight them. var seq = 0; var row = top.parentNode; for (seq = 0; seq < row.childNodes.length; seq++) { if (row.childNodes[seq] == top) { break; } } var cells = []; while (true) { cells.push(row.childNodes[seq]); if (row.childNodes[seq] == bot) { break; } row = row.nextSibling; } setSelectedCells(cells); } function setSelectedCells(new_cells) { updateSelectedCellsClass(old_cells, false); updateSelectedCellsClass(new_cells, true); old_cells = new_cells; } function updateSelectedCellsClass(cells, selected) { for (var ii = 0; ii < cells.length; ii++) { JX.DOM.alterClass(cells[ii], 'selected', selected); } } function hideReticle() { JX.DOM.hide(reticle); setSelectedCells([]); } 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 (e.isRightButton() || getRowNumber(e.getTarget()) === undefined) { return; } if (editor) { new JX.DifferentialInlineCommentEditor(config.uri) .setOperation('busy') .setRow(editor.getRow().previousSibling) .start(); return; } if (selecting) { 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', 'mouseout'], ['differential-changeset', 'tag:th'], function(e) { if (editor) { // Don't update the reticle if we're editing a comment, since this // would be distracting and we want to keep the lines corresponding // to the comment highlighted during the edit. return; } if (getRowNumber(e.getTarget()) === undefined) { // Don't update the reticle if this "" doesn't correspond to a // line number. For instance, this may be a dead line number, like the // empty line numbers on the left hand side of a newly added file. return; } if (selecting) { if (isOnRight(e.getTarget()) != isOnRight(origin)) { // Don't update the reticle if we're selecting a line range and the // "" under the cursor is on the wrong side of the file. You // can only leave inline comments on the left or right side of a // file, not across lines on both sides. return; } if (e.getNode('differential-changeset') !== root) { // Don't update the reticle if we're selecting a line range and // the "" under the cursor corresponds to a different file. // You can only leave inline comments on lines in a single file, // not across multiple files. return; } } if (e.getType() == 'mouseout') { if (selecting) { // Don't hide the reticle if we're selecting, since we want to // keep showing the line range that will be used if the mouse is // released. return; } hideReticle(); } else { target = e.getTarget(); if (!selecting) { // If we're just hovering the mouse and not selecting a line range, // set the origin to the current row so we highlight it. origin = target; } 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; } var view = JX.ChangesetViewManager.getForNode(root); editor = new JX.DifferentialInlineCommentEditor(config.uri) .setTemplates(view.getUndoTemplates()) .setOperation('new') .setChangesetID(changeset) .setLineNumber(o) .setLength(len) .setIsNew(isNewFile(target) ? 1 : 0) .setOnRight(isOnRight(target) ? 1 : 0) .setRow(insert.nextSibling) .setTable(insert.parentNode) .setRenderer(view.getRenderer()) .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; var new_part = data.isNewFile ? '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); // If you click an action in the preview at the bottom of the page, we // find the corresponding node and simulate clicking that, if it's // present on the page. This gives the editor a more consistent view // of the document. if (JX.Stratcom.hasSigil(node, 'differential-inline-comment-preview')) { var nodes = JX.DOM.scry( JX.DOM.getContentFrame(), 'div', 'differential-inline-comment'); var found = false; var node_data; for (var ii = 0; ii < nodes.length; ++ii) { if (nodes[ii] == node) { // Don't match the preview itself. continue; } node_data = JX.Stratcom.getData(nodes[ii]); if (node_data.id == data.id) { node = nodes[ii]; data = node_data; found = true; break; } } if (!found) { switch (op) { case 'delete': new JX.DifferentialInlineCommentEditor(config.uri) .deleteByID(data.id); return; } } if (op == 'delete') { op = 'refdelete'; } } if (op == 'done') { var checkbox = JX.DOM.find(node, 'input', 'differential-inline-done'); new JX.DifferentialInlineCommentEditor(config.uri) .toggleCheckbox(data.id, checkbox); return; } var original = data.original; var reply_phid = null; 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 = ''; reply_phid = data.phid; } var row = JX.DOM.findAbove(node, 'tr'); var changeset_root = JX.DOM.findAbove( node, 'div', 'differential-changeset'); var view = JX.ChangesetViewManager.getForNode(changeset_root); editor = new JX.DifferentialInlineCommentEditor(config.uri) .setTemplates(view.getUndoTemplates()) .setOperation(op) .setID(data.id) .setChangesetID(data.changesetID) .setLineNumber(data.number) .setLength(data.length) .setOnRight(data.on_right) .setOriginalText(original) .setRow(row) .setTable(row.parentNode) .setReplyToCommentPHID(reply_phid) .setRenderer(view.getRenderer()) .start(); set_link_state(true); }; for (var op in {'edit': 1, 'delete': 1, 'reply': 1, 'done': 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); }); // Respond to the user clicking the "Hide Inline" button on an inline // comment. JX.Stratcom.listen('click', 'hide-inline', function(e) { e.kill(); var row = e.getNode('inline-row'); JX.DOM.hide(row); var prev = row.previousSibling; while (prev && JX.Stratcom.hasSigil(prev, 'inline-row')) { prev = prev.previousSibling; } if (!prev) { return; } var comment = e.getNodeData('differential-inline-comment'); var slots = []; for (var ii = 0; ii < prev.childNodes.length; ii++) { if (JX.DOM.isType(prev.childNodes[ii], 'th')) { slots.push(prev.childNodes[ii]); } } // Select the right-hand side if the comment is on the right. var slot = (comment.on_right && slots[1]) || slots[0]; var reveal = JX.DOM.scry(slot, 'a', 'reveal-inlines')[0]; if (!reveal) { reveal = JX.$N( 'a', { className: 'reveal-inlines', sigil: 'reveal-inlines' }, JX.$H(config.revealIcon)); JX.DOM.prependContent(slot, reveal); } new JX.Workflow(config.uri, {op: 'hide', ids: comment.id}) .setHandler(JX.bag) .start(); }); JX.Stratcom.listen('click', 'reveal-inlines', function(e) { e.kill(); var row = e.getNode('tag:tr'); var next = row.nextSibling; var ids = []; var ii; // Show any hidden inline comment rows directly below this one. while (next && JX.Stratcom.hasSigil(next, 'inline-row')) { JX.DOM.show(next); var comments = JX.DOM.scry(next, 'div', 'differential-inline-comment'); for (ii = 0; ii < comments.length; ii++) { var id = JX.Stratcom.getData(comments[ii]).id; if (id) { ids.push(id); } } next = next.nextSibling; } // Remove any "reveal" icons on the row. var reveals = JX.DOM.scry(row, 'a', 'reveal-inlines'); for (ii = 0; ii < reveals.length; ii++) { JX.DOM.remove(reveals[ii]); } new JX.Workflow(config.uri, {op: 'show', ids: ids.join(',')}) .setHandler(JX.bag) .start(); }); });