Files
gerrit/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js
Tao Zhou 39d3f20d54 Fix the js error on commentRanges change event
The change record on array mutation is not using the number indicate
the index, but using it as the key, so can not retrieve the item by the number,
use `this.get` as recommended from the doc.
https://polymer-library.polymer-project.org/1.0/docs/devguide/observers

Bug: Issue 11375
Change-Id: I04da93aa7c01ec40bddc91d2400bceac101f7c62
2019-08-29 18:09:02 +02:00

210 lines
7.3 KiB
JavaScript

/**
* @license
* 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';
// Polymer 1 adds # before array's key, while Polymer 2 doesn't
const HOVER_PATH_PATTERN = /^(commentRanges\.\#?\d+)\.hovering$/;
const RANGE_HIGHLIGHT = 'style-scope gr-diff range';
const HOVER_HIGHLIGHT = 'style-scope gr-diff rangeHighlight';
/** @typedef {{side: string, range: Gerrit.Range, hovering: boolean}} */
Gerrit.HoveredRange;
Polymer({
is: 'gr-ranged-comment-layer',
_legacyUndefinedCheck: true,
/**
* Fired when the range in a range comment was malformed and had to be
* normalized.
*
* It's `detail` has a `lineNum` and `side` parameter.
*
* @event normalize-range
*/
properties: {
/** @type {!Array<!Gerrit.HoveredRange>} */
commentRanges: Array,
_listeners: {
type: Array,
value() { return []; },
},
_rangesMap: {
type: Object,
value() { return {left: {}, right: {}}; },
},
},
observers: [
'_handleCommentRangesChange(commentRanges.*)',
],
get styleModuleName() {
return 'gr-ranged-comment-styles';
},
/**
* Layer method to add annotations to a line.
* @param {!HTMLElement} el The DIV.contentText element to apply the
* annotation to.
* @param {!HTMLElement} lineNumberEl
* @param {!Object} line The line object. (GrDiffLine)
*/
annotate(el, lineNumberEl, line) {
let ranges = [];
if (line.type === GrDiffLine.Type.REMOVE || (
line.type === GrDiffLine.Type.BOTH &&
el.getAttribute('data-side') !== 'right')) {
ranges = ranges.concat(this._getRangesForLine(line, 'left'));
}
if (line.type === GrDiffLine.Type.ADD || (
line.type === GrDiffLine.Type.BOTH &&
el.getAttribute('data-side') !== 'left')) {
ranges = ranges.concat(this._getRangesForLine(line, 'right'));
}
for (const range of ranges) {
GrAnnotation.annotateElement(el, range.start,
range.end - range.start,
range.hovering ? HOVER_HIGHLIGHT : RANGE_HIGHLIGHT);
}
},
/**
* Register a listener for layer updates.
* @param {function(number, number, string)} fn The update handler function.
* Should accept as arguments the line numbers for the start and end of
* the update and the side as a string.
*/
addListener(fn) {
this._listeners.push(fn);
},
/**
* Notify Layer listeners of changes to annotations.
* @param {number} start The line where the update starts.
* @param {number} end The line where the update ends.
* @param {string} side The side of the update. ('left' or 'right')
*/
_notifyUpdateRange(start, end, side) {
for (const listener of this._listeners) {
listener(start, end, side);
}
},
/**
* Handle change in the ranges by updating the ranges maps and by
* emitting appropriate update notifications.
* @param {Object} record The change record.
*/
_handleCommentRangesChange(record) {
if (!record) return;
// If the entire set of comments was changed.
if (record.path === 'commentRanges') {
this._rangesMap = {left: {}, right: {}};
for (const {side, range, hovering} of record.value) {
this._updateRangesMap(
side, range, hovering, (forLine, start, end, hovering) => {
forLine.push({start, end, hovering});
});
}
}
// If the change only changed the `hovering` property of a comment.
const match = record.path.match(HOVER_PATH_PATTERN);
if (match) {
// The #number indicates the key of that item in the array
// not the index, especially in polymer 1.
const {side, range, hovering} = this.get(match[1]);
this._updateRangesMap(
side, range, hovering, (forLine, start, end, hovering) => {
const index = forLine.findIndex(lineRange =>
lineRange.start === start && lineRange.end === end);
forLine[index].hovering = hovering;
});
}
// If comments were spliced in or out.
if (record.path === 'commentRanges.splices') {
for (const indexSplice of record.value.indexSplices) {
const removed = indexSplice.removed;
for (const {side, range, hovering} of removed) {
this._updateRangesMap(
side, range, hovering, (forLine, start, end) => {
const index = forLine.findIndex(lineRange =>
lineRange.start === start && lineRange.end === end);
forLine.splice(index, 1);
});
}
const added = indexSplice.object.slice(
indexSplice.index, indexSplice.index + indexSplice.addedCount);
for (const {side, range, hovering} of added) {
this._updateRangesMap(
side, range, hovering, (forLine, start, end, hovering) => {
forLine.push({start, end, hovering});
});
}
}
}
},
_updateRangesMap(side, range, hovering, operation) {
const forSide = this._rangesMap[side] || (this._rangesMap[side] = {});
for (let line = range.start_line; line <= range.end_line; line++) {
const forLine = forSide[line] || (forSide[line] = []);
const start = line === range.start_line ? range.start_character : 0;
const end = line === range.end_line ? range.end_character : -1;
operation(forLine, start, end, hovering);
}
this._notifyUpdateRange(range.start_line, range.end_line, side);
},
_getRangesForLine(line, side) {
const lineNum = side === 'left' ? line.beforeNumber : line.afterNumber;
const ranges = this.get(['_rangesMap', side, lineNum]) || [];
return ranges
.map(range => {
// Make a copy, so that the normalization below does not mess with
// our map.
range = Object.assign({}, range);
range.end = range.end === -1 ? line.text.length : range.end;
// Normalize invalid ranges where the start is after the end but the
// start still makes sense. Set the end to the end of the line.
// @see Issue 5744
if (range.start >= range.end && range.start < line.text.length) {
range.end = line.text.length;
this.dispatchEvent(new CustomEvent('normalize-range', {
bubbles: true,
composed: true,
detail: {lineNum, side},
}));
}
return range;
})
// Sort the ranges so that hovering highlights are on top.
.sort((a, b) => a.hovering && !b.hovering ? 1 : 0);
},
});
})();