
Formerly, diff content elements mixed text with comment threads. For example, a diff content node with an intraline highlight, a ranged comment, and a gr-diff-comment-thread may have been organized as below: TD.content ╠ #text ╠ HL (intraline difference) ║ ╚ #text ╠ #text ╠ HL.range (ranged comment highlight) ║ ╚ #text ╠ #text ╚ GR-DIFF-COMMENT-THREAD ╠ GR-DIFF-COMMENT ╚ ... Note that the comment thread was inserted at the same level as the text of the diff line. With this change, the text is separated from the comment thread by introducing a DIV to contain the text with class `contentText` as sibling to comment threads. TD.content ╠ DIV.contentText ║ ╠ #text ║ ╠ HL ║ ║ ╚ #text ║ ╠ #text ║ ╠ HL.range ║ ║ ╚ #text ║ ╚ #text ╚ GR-DIFF-COMMENT-THREAD ╠ GR-DIFF-COMMENT ╚ ... Modifies the `getContentByLine` method of gr-diff-builder to return the `DIV.contentText` element rather than the `TD.content` element which is its parent. In most uses of this function, the text is what is needed rather than the TD or comment thread, but in other cases, they can be easily DOM traversed. Change-Id: I0eded34afd3d22963252efc7eabfee290ae21a9c
661 lines
22 KiB
JavaScript
661 lines
22 KiB
JavaScript
// Copyright (C) 2016 The Android Open Source Project
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
(function() {
|
|
'use strict';
|
|
|
|
// Astral code point as per https://mathiasbynens.be/notes/javascript-unicode
|
|
var REGEX_ASTRAL_SYMBOL = /[\uD800-\uDBFF][\uDC00-\uDFFF]/;
|
|
var RANGE_HIGHLIGHT = 'range';
|
|
var HOVER_HIGHLIGHT = 'rangeHighlight';
|
|
|
|
Polymer({
|
|
is: 'gr-diff-highlight',
|
|
|
|
properties: {
|
|
comments: Object,
|
|
enabled: {
|
|
type: Boolean,
|
|
observer: '_enabledChanged',
|
|
},
|
|
loggedIn: Boolean,
|
|
_cachedDiffBuilder: Object,
|
|
_enabledListeners: {
|
|
type: Object,
|
|
value: function() {
|
|
return {
|
|
'comment-discard': '_handleCommentDiscard',
|
|
'comment-mouse-out': '_handleCommentMouseOut',
|
|
'comment-mouse-over': '_handleCommentMouseOver',
|
|
'create-comment': '_createComment',
|
|
'render': '_handleRender',
|
|
'show-context': '_handleShowContext',
|
|
'thread-discard': '_handleThreadDiscard',
|
|
};
|
|
},
|
|
},
|
|
},
|
|
|
|
get diffBuilder() {
|
|
if (!this._cachedDiffBuilder) {
|
|
this._cachedDiffBuilder =
|
|
Polymer.dom(this).querySelector('gr-diff-builder');
|
|
}
|
|
return this._cachedDiffBuilder;
|
|
},
|
|
|
|
detached: function() {
|
|
this.enabled = false;
|
|
},
|
|
|
|
_enabledChanged: function() {
|
|
if (this.enabled) {
|
|
this.listen(document, 'selectionchange', '_handleSelectionChange');
|
|
} else {
|
|
this.unlisten(document, 'selectionchange', '_handleSelectionChange');
|
|
}
|
|
for (var eventName in this._enabledListeners) {
|
|
var methodName = this._enabledListeners[eventName];
|
|
if (this.enabled) {
|
|
this.listen(this, eventName, methodName);
|
|
} else {
|
|
this.unlisten(this, eventName, methodName);
|
|
}
|
|
}
|
|
},
|
|
|
|
isRangeSelected: function() {
|
|
return !!this.$$('gr-selection-action-box');
|
|
},
|
|
|
|
_handleThreadDiscard: function(e) {
|
|
var comment = e.detail.lastComment;
|
|
// Comment Element was removed from DOM already.
|
|
if (comment.range) {
|
|
this._renderCommentRange(comment, e.target);
|
|
}
|
|
},
|
|
|
|
_handleCommentDiscard: function(e) {
|
|
var comment = e.detail.comment;
|
|
if (comment.range) {
|
|
this._renderCommentRange(comment, e.target);
|
|
}
|
|
},
|
|
|
|
_handleSelectionChange: function() {
|
|
// Can't use up or down events to handle selection started and/or ended in
|
|
// in comment threads or outside of diff.
|
|
// Debounce removeActionBox to give it a chance to react to click/tap.
|
|
this._removeActionBoxDebounced();
|
|
this.debounce('selectionChange', this._handleSelection, 200);
|
|
},
|
|
|
|
_handleRender: function() {
|
|
this._applyAllHighlights();
|
|
},
|
|
|
|
_handleShowContext: function() {
|
|
// TODO (viktard): Re-render expanded sections only.
|
|
this._applyAllHighlights();
|
|
},
|
|
|
|
_handleCommentMouseOver: function(e) {
|
|
var comment = e.detail.comment;
|
|
var range = comment.range;
|
|
if (!range) {
|
|
return;
|
|
}
|
|
var lineEl = this.diffBuilder.getLineElByChild(e.target);
|
|
var side = this.diffBuilder.getSideByLineEl(lineEl);
|
|
this._applyRangedHighlight(
|
|
HOVER_HIGHLIGHT, range.start_line, range.start_character,
|
|
range.end_line, range.end_character, side);
|
|
},
|
|
|
|
_handleCommentMouseOut: function(e) {
|
|
var comment = e.detail.comment;
|
|
var range = comment.range;
|
|
if (!range) {
|
|
return;
|
|
}
|
|
var lineEl = this.diffBuilder.getLineElByChild(e.target);
|
|
var side = this.diffBuilder.getSideByLineEl(lineEl);
|
|
var contentEls = this.diffBuilder.getContentsByLineRange(
|
|
range.start_line, range.end_line, side);
|
|
contentEls.forEach(function(content) {
|
|
Polymer.dom(content).querySelectorAll('.' + HOVER_HIGHLIGHT).forEach(
|
|
function(el) {
|
|
el.classList.remove(HOVER_HIGHLIGHT);
|
|
el.classList.add(RANGE_HIGHLIGHT);
|
|
});
|
|
}, this);
|
|
},
|
|
|
|
/**
|
|
* Convert DOM Range selection to concrete numbers (line, column, side).
|
|
* Moves range end if it's not inside td.content.
|
|
* Returns null if selection end is not valid (outside of diff).
|
|
*
|
|
* @param {Node} node td.content child
|
|
* @param {number} offset offset within node
|
|
* @return {{
|
|
* node: Node,
|
|
* side: string,
|
|
* line: Number,
|
|
* column: Number
|
|
* }}
|
|
*/
|
|
_normalizeSelectionSide: function(node, offset) {
|
|
var column;
|
|
if (!this.contains(node)) {
|
|
return;
|
|
}
|
|
var lineEl = this.diffBuilder.getLineElByChild(node);
|
|
if (!lineEl) {
|
|
return;
|
|
}
|
|
var side = this.diffBuilder.getSideByLineEl(lineEl);
|
|
if (!side) {
|
|
return;
|
|
}
|
|
var line = this.diffBuilder.getLineNumberByChild(lineEl);
|
|
if (!line) {
|
|
return;
|
|
}
|
|
var contentText = this.diffBuilder.getContentByLineEl(lineEl);
|
|
if (!contentText) {
|
|
return;
|
|
}
|
|
var contentTd = contentText.parentElement;
|
|
if (!contentTd.contains(node)) {
|
|
node = contentText;
|
|
column = 0;
|
|
} else {
|
|
var thread = contentTd.querySelector('gr-diff-comment-thread');
|
|
if (thread && thread.contains(node)) {
|
|
column = this._getLength(contentText);
|
|
node = contentText;
|
|
} else {
|
|
column = this._convertOffsetToColumn(node, offset);
|
|
}
|
|
}
|
|
|
|
return {
|
|
node: node,
|
|
side: side,
|
|
line: line,
|
|
column: column,
|
|
};
|
|
},
|
|
|
|
_handleSelection: function() {
|
|
var selection = window.getSelection();
|
|
if (selection.rangeCount != 1) {
|
|
return;
|
|
}
|
|
var range = selection.getRangeAt(0);
|
|
if (range.collapsed) {
|
|
return;
|
|
}
|
|
var start =
|
|
this._normalizeSelectionSide(range.startContainer, range.startOffset);
|
|
if (!start) {
|
|
return;
|
|
}
|
|
var end =
|
|
this._normalizeSelectionSide(range.endContainer, range.endOffset);
|
|
if (!end) {
|
|
return;
|
|
}
|
|
if (start.side !== end.side ||
|
|
end.line < start.line ||
|
|
(start.line === end.line && start.column === end.column)) {
|
|
return;
|
|
}
|
|
|
|
// TODO (viktard): Drop empty first and last lines from selection.
|
|
|
|
var actionBox = document.createElement('gr-selection-action-box');
|
|
Polymer.dom(this.root).appendChild(actionBox);
|
|
actionBox.range = {
|
|
startLine: start.line,
|
|
startChar: start.column,
|
|
endLine: end.line,
|
|
endChar: end.column,
|
|
};
|
|
actionBox.side = start.side;
|
|
if (start.line === end.line) {
|
|
actionBox.placeAbove(range);
|
|
} else if (start.node instanceof Text) {
|
|
actionBox.placeAbove(start.node.splitText(start.column));
|
|
start.node.parentElement.normalize(); // Undo splitText from above.
|
|
} else if (start.node.classList.contains('content') &&
|
|
start.node.firstChild) {
|
|
actionBox.placeAbove(start.node.firstChild);
|
|
} else {
|
|
actionBox.placeAbove(start.node);
|
|
}
|
|
},
|
|
|
|
_renderCommentRange: function(comment, el) {
|
|
var lineEl = this.diffBuilder.getLineElByChild(el);
|
|
if (!lineEl) {
|
|
return;
|
|
}
|
|
var side = this.diffBuilder.getSideByLineEl(lineEl);
|
|
this._rerenderByLines(
|
|
comment.range.start_line, comment.range.end_line, side);
|
|
},
|
|
|
|
_createComment: function(e) {
|
|
this._removeActionBox();
|
|
var side = e.detail.side;
|
|
var range = e.detail.range;
|
|
if (!range) {
|
|
return;
|
|
}
|
|
this._applyRangedHighlight(
|
|
RANGE_HIGHLIGHT, range.startLine, range.startChar,
|
|
range.endLine, range.endChar, side);
|
|
},
|
|
|
|
_removeActionBoxDebounced: function() {
|
|
this.debounce('removeActionBox', this._removeActionBox, 10);
|
|
},
|
|
|
|
_removeActionBox: function() {
|
|
var actionBox = this.$$('gr-selection-action-box');
|
|
if (actionBox) {
|
|
Polymer.dom(this.root).removeChild(actionBox);
|
|
}
|
|
},
|
|
|
|
_convertOffsetToColumn: function(el, offset) {
|
|
if (el instanceof Element && el.classList.contains('content')) {
|
|
return offset;
|
|
}
|
|
while (el.previousSibling ||
|
|
!el.parentElement.classList.contains('content')) {
|
|
if (el.previousSibling) {
|
|
el = el.previousSibling;
|
|
offset += this._getLength(el);
|
|
} else {
|
|
el = el.parentElement;
|
|
}
|
|
}
|
|
return offset;
|
|
},
|
|
|
|
/**
|
|
* Traverse Element from right to left, call callback for each node.
|
|
* Stops if callback returns true.
|
|
*
|
|
* @param {!Node} startNode
|
|
* @param {function(Node):boolean} callback
|
|
* @param {Object=} opt_flags If flags.left is true, traverse left.
|
|
*/
|
|
_traverseContentSiblings: function(startNode, callback, opt_flags) {
|
|
var travelLeft = opt_flags && opt_flags.left;
|
|
var node = startNode;
|
|
while (node) {
|
|
if (node instanceof Element &&
|
|
node.tagName !== 'HL' &&
|
|
node.tagName !== 'SPAN') {
|
|
break;
|
|
}
|
|
var nextNode = travelLeft ? node.previousSibling : node.nextSibling;
|
|
if (callback(node)) {
|
|
break;
|
|
}
|
|
node = nextNode;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get length of a node. If the node is a content node, then only give the
|
|
* length of its .contentText child.
|
|
*
|
|
* @param {!Node} node
|
|
* @return {number}
|
|
*/
|
|
_getLength: function(node) {
|
|
if (node instanceof Element && node.classList.contains('content')) {
|
|
return this._getLength(node.querySelector('.contentText'));
|
|
} else {
|
|
// DOM API for textContent.length is broken for Unicode:
|
|
// https://mathiasbynens.be/notes/javascript-unicode
|
|
return node.textContent.replace(REGEX_ASTRAL_SYMBOL, '_').length;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Wraps node in hl tag with cssClass, replacing the node in DOM.
|
|
*
|
|
* @return {!Element} Wrapped node.
|
|
*/
|
|
_wrapInHighlight: function(node, cssClass) {
|
|
var hl;
|
|
if (node.tagName === 'HL') {
|
|
hl = node;
|
|
hl.classList.add(cssClass);
|
|
} else {
|
|
hl = document.createElement('hl');
|
|
hl.className = cssClass;
|
|
Polymer.dom(node.parentElement).replaceChild(hl, node);
|
|
hl.appendChild(node);
|
|
}
|
|
return hl;
|
|
},
|
|
|
|
/**
|
|
* Node.prototype.splitText Unicode-valid alternative.
|
|
*
|
|
* @param {!Text} node
|
|
* @param {number} offset
|
|
* @return {!Text} Trailing Text Node.
|
|
*/
|
|
_splitTextNode: function(node, offset) {
|
|
if (node.textContent.match(REGEX_ASTRAL_SYMBOL)) {
|
|
// DOM Api for splitText() is broken for Unicode:
|
|
// https://mathiasbynens.be/notes/javascript-unicode
|
|
// TODO (viktard): Polyfill Array.from for IE10.
|
|
var head = Array.from(node.textContent);
|
|
var tail = head.splice(offset);
|
|
var parent = node.parentElement;
|
|
var headNode = document.createTextNode(head.join(''));
|
|
parent.replaceChild(headNode, node);
|
|
var tailNode = document.createTextNode(tail.join(''));
|
|
parent.insertBefore(tailNode, headNode.nextSibling);
|
|
return tailNode;
|
|
} else {
|
|
return node.splitText(offset);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Split Node at offset.
|
|
* If Node is Element, it's cloned and the node at offset is split too.
|
|
*
|
|
* @param {!Node} node
|
|
* @param {number} offset
|
|
* @return {!Node} Trailing Node.
|
|
*/
|
|
_splitNode: function(element, offset) {
|
|
if (element instanceof Text) {
|
|
return this._splitTextNode(element, offset);
|
|
}
|
|
var tail = element.cloneNode(false);
|
|
element.parentElement.insertBefore(tail, element.nextSibling);
|
|
// Skip nodes before offset.
|
|
var node = element.firstChild;
|
|
while (node &&
|
|
this._getLength(node) <= offset ||
|
|
this._getLength(node) === 0) {
|
|
offset -= this._getLength(node);
|
|
node = node.nextSibling;
|
|
}
|
|
if (this._getLength(node) > offset) {
|
|
tail.appendChild(this._splitNode(node, offset));
|
|
}
|
|
while (node.nextSibling) {
|
|
tail.appendChild(node.nextSibling);
|
|
}
|
|
return tail;
|
|
},
|
|
|
|
/**
|
|
* Split Text Node and wrap it in hl with cssClass.
|
|
* Wraps trailing part after split, tailing one if opt_firstPart is true.
|
|
*
|
|
* @param {!Node} node
|
|
* @param {number} offset
|
|
* @param {string} cssClass
|
|
* @param {boolean=} opt_firstPart
|
|
*/
|
|
_splitAndWrapInHighlight: function(node, offset, cssClass, opt_firstPart) {
|
|
if (this._getLength(node) === offset || offset === 0) {
|
|
return this._wrapInHighlight(node, cssClass);
|
|
} else {
|
|
if (opt_firstPart) {
|
|
this._splitNode(node, offset);
|
|
// Node points to first part of the Text, second one is sibling.
|
|
} else {
|
|
node = this._splitNode(node, offset);
|
|
}
|
|
return this._wrapInHighlight(node, cssClass);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Creates hl tag with cssClass for starting side of range highlight.
|
|
*
|
|
* @param {!Element} startContent Range start diff content
|
|
* aka div.contentText.
|
|
* @param {!Element} endContent Range end diff content
|
|
* aka div.contentText.
|
|
* @param {number} startOffset Range start within start content.
|
|
* @param {number} endOffset Range end within end content.
|
|
* @param {string} cssClass
|
|
* @return {!Element} Range start node.
|
|
*/
|
|
_normalizeStart: function(
|
|
startContent, endContent, startOffset, endOffset, cssClass) {
|
|
var isOneLine = startContent === endContent;
|
|
var startNode = startContent.firstChild;
|
|
var length = endOffset - startOffset;
|
|
|
|
if (!startNode) {
|
|
return startNode;
|
|
}
|
|
|
|
// Skip nodes before startOffset.
|
|
var nodeLength = this._getLength(startNode);
|
|
while (startNode && (nodeLength <= startOffset || nodeLength === 0)) {
|
|
startOffset -= nodeLength;
|
|
startNode = startNode.nextSibling;
|
|
nodeLength = startNode && this._getLength(startNode);
|
|
}
|
|
if (!startNode) { return null; }
|
|
|
|
// Split Text node.
|
|
if (startNode instanceof Text) {
|
|
startNode =
|
|
this._splitAndWrapInHighlight(startNode, startOffset, cssClass);
|
|
// Edge case: single line, text node wraps the highlight.
|
|
if (isOneLine && this._getLength(startNode) > length) {
|
|
var extra = this._splitTextNode(startNode.firstChild, length);
|
|
startContent.insertBefore(extra, startNode.nextSibling);
|
|
startContent.normalize();
|
|
}
|
|
} else if (startNode.tagName == 'HL') {
|
|
if (!startNode.classList.contains(cssClass)) {
|
|
// Edge case: single line, <hl> wraps the highlight.
|
|
// Should leave wrapping HL's content after the highlight.
|
|
if (isOneLine && startOffset + length < this._getLength(startNode)) {
|
|
this._splitNode(startNode, startOffset + length);
|
|
}
|
|
startNode =
|
|
this._splitAndWrapInHighlight(startNode, startOffset, cssClass);
|
|
}
|
|
} else {
|
|
startNode = null;
|
|
}
|
|
return startNode;
|
|
},
|
|
|
|
/**
|
|
* Creates hl tag with cssClass for ending side of range highlight.
|
|
*
|
|
* @param {!Element} startContent Range start diff content
|
|
* aka div.contentText.
|
|
* @param {!Element} endContent Range end diff content
|
|
* aka div.contentText.
|
|
* @param {number} startOffset Range start within start content.
|
|
* @param {number} endOffset Range end within end content.
|
|
* @param {string} cssClass
|
|
* @return {!Element} Range start node.
|
|
*/
|
|
_normalizeEnd: function(
|
|
startContent, endContent, startOffset, endOffset, cssClass) {
|
|
var endNode = endContent.firstChild;
|
|
|
|
if (!endNode) {
|
|
return endNode;
|
|
}
|
|
|
|
// Find the node where endOffset points at.
|
|
var nodeLength = this._getLength(endNode);
|
|
while (endNode && (nodeLength < endOffset || nodeLength === 0)) {
|
|
endOffset -= nodeLength;
|
|
endNode = endNode.nextSibling;
|
|
nodeLength = endNode && this._getLength(endNode);
|
|
}
|
|
if (!endNode) { return null; }
|
|
|
|
if (endNode instanceof Text) {
|
|
endNode =
|
|
this._splitAndWrapInHighlight(endNode, endOffset, cssClass, true);
|
|
} else if (endNode.tagName == 'HL') {
|
|
if (!endNode.classList.contains(cssClass)) {
|
|
// Split text inside HL.
|
|
var hl = endNode;
|
|
endNode = this._splitAndWrapInHighlight(
|
|
endNode, endOffset, cssClass, true);
|
|
if (hl.textContent.length === 0) {
|
|
hl.remove();
|
|
}
|
|
}
|
|
} else {
|
|
endNode = null;
|
|
}
|
|
return endNode;
|
|
},
|
|
|
|
/**
|
|
* Applies highlight to first and last lines in range.
|
|
*
|
|
* @param {!Element} startContent Range start diff content
|
|
* aka div.contentText.
|
|
* @param {!Element} endContent Range end diff content
|
|
* aka div.contentText.
|
|
* @param {number} startOffset Range start within start content.
|
|
* @param {number} endOffset Range end within end content.
|
|
* @param {string} cssClass
|
|
*/
|
|
_highlightSides: function(
|
|
startContent, endContent, startOffset, endOffset, cssClass) {
|
|
var isOneLine = startContent === endContent;
|
|
var startNode = this._normalizeStart(
|
|
startContent, endContent, startOffset, endOffset, cssClass);
|
|
var endNode = this._normalizeEnd(
|
|
startContent, endContent, startOffset, endOffset, cssClass);
|
|
|
|
// Grow starting highlight until endNode or end of line.
|
|
if (startNode && startNode != endNode) {
|
|
var growStartHl = function(node) {
|
|
if (node instanceof Text || node.tagName === 'SPAN') {
|
|
startNode.appendChild(node);
|
|
} else if (node.tagName === 'HL') {
|
|
this._traverseContentSiblings(node.firstChild, growStartHl);
|
|
node.remove();
|
|
}
|
|
return node == endNode;
|
|
}.bind(this);
|
|
this._traverseContentSiblings(startNode.nextSibling, growStartHl);
|
|
startNode.normalize();
|
|
}
|
|
|
|
if (!isOneLine && endNode) {
|
|
var growEndHl = function(node) {
|
|
if (node instanceof Text || node.tagName === 'SPAN') {
|
|
endNode.insertBefore(node, endNode.firstChild);
|
|
} else if (node.tagName === 'HL') {
|
|
this._traverseContentSiblings(node.firstChild, growEndHl);
|
|
node.remove();
|
|
}
|
|
}.bind(this);
|
|
// Prepend text up to line start to the ending highlight.
|
|
this._traverseContentSiblings(
|
|
endNode.previousSibling, growEndHl, {left: true});
|
|
endNode.normalize();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @param {string} cssClass
|
|
* @param {number} startLine Range start code line number.
|
|
* @param {number} startCol Range start column number.
|
|
* @param {number} endLine Range end line number.
|
|
* @param {number} endCol Range end column number.
|
|
* @param {string=} opt_side Side selector (right or left).
|
|
*/
|
|
_applyRangedHighlight: function(
|
|
cssClass, startLine, startCol, endLine, endCol, opt_side) {
|
|
var startEl = this.diffBuilder.getContentByLine(startLine, opt_side);
|
|
var endEl = this.diffBuilder.getContentByLine(endLine, opt_side);
|
|
this._highlightSides(startEl, endEl, startCol, endCol, cssClass);
|
|
if (endLine - startLine > 1) {
|
|
// There is at least one line in between.
|
|
var contents = this.diffBuilder.getContentsByLineRange(
|
|
startLine + 1, endLine - 1, opt_side);
|
|
// Wrap contents in highlight.
|
|
contents.forEach(function(content) {
|
|
if (content.textContent.length === 0) {
|
|
return;
|
|
}
|
|
var threadEl =
|
|
this.diffBuilder.getCommentThreadByContentEl(content);
|
|
if (threadEl) {
|
|
threadEl.remove();
|
|
}
|
|
var text = document.createTextNode(content.textContent);
|
|
while (content.firstChild) {
|
|
content.removeChild(content.firstChild);
|
|
}
|
|
content.appendChild(text);
|
|
if (threadEl) {
|
|
content.appendChild(threadEl);
|
|
}
|
|
this._wrapInHighlight(text, cssClass);
|
|
}, this);
|
|
}
|
|
},
|
|
|
|
_applyAllHighlights: function() {
|
|
var rangedLeft =
|
|
this.comments.left.filter(function(item) { return !!item.range; });
|
|
var rangedRight =
|
|
this.comments.right.filter(function(item) { return !!item.range; });
|
|
rangedLeft.forEach(function(item) {
|
|
var range = item.range;
|
|
this._applyRangedHighlight(
|
|
RANGE_HIGHLIGHT, range.start_line, range.start_character,
|
|
range.end_line, range.end_character, 'left');
|
|
}, this);
|
|
rangedRight.forEach(function(item) {
|
|
var range = item.range;
|
|
this._applyRangedHighlight(
|
|
RANGE_HIGHLIGHT, range.start_line, range.start_character,
|
|
range.end_line, range.end_character, 'right');
|
|
}, this);
|
|
},
|
|
|
|
_rerenderByLines: function(startLine, endLine, opt_side) {
|
|
this.async(function() {
|
|
this.diffBuilder.renderLineRange(startLine, endLine, opt_side);
|
|
}, 1);
|
|
},
|
|
});
|
|
})();
|