
There was an issue where line numbers would not properly highlight in inline-diffs rendered in the file view. There were two separate issues that contributed to this problem: 1 - The diff cursor was not notified of the line being selected and needing to move to that cursor stop. 2 - The diff cursor was not properly updated after being moved inside a dom-if statement. Additionally, the way that line selection was previously done was not extendable to a diff cursor that stored multiple files. It queried stops based on line number and side. This change also stores the path of the file in the cursor stop so that the correct diff/line is selected in the case of multiples. Bug: Issue 5977 Change-Id: I7496293e19a4e59a3855dc78e273de6a9852e556
582 lines
16 KiB
JavaScript
582 lines
16 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';
|
|
|
|
var DiffViewMode = {
|
|
SIDE_BY_SIDE: 'SIDE_BY_SIDE',
|
|
UNIFIED: 'UNIFIED_DIFF',
|
|
};
|
|
|
|
var DiffSide = {
|
|
LEFT: 'left',
|
|
RIGHT: 'right',
|
|
};
|
|
|
|
Polymer({
|
|
is: 'gr-diff',
|
|
|
|
/**
|
|
* Fired when the user selects a line.
|
|
* @event line-selected
|
|
*/
|
|
|
|
/**
|
|
* Fired if being logged in is required.
|
|
*
|
|
* @event show-auth-required
|
|
*/
|
|
|
|
properties: {
|
|
changeNum: String,
|
|
noAutoRender: {
|
|
type: Boolean,
|
|
value: false,
|
|
},
|
|
patchRange: Object,
|
|
path: String,
|
|
prefs: {
|
|
type: Object,
|
|
observer: '_prefsObserver',
|
|
},
|
|
projectConfig: {
|
|
type: Object,
|
|
observer: '_projectConfigChanged',
|
|
},
|
|
project: String,
|
|
commit: String,
|
|
displayLine: {
|
|
type: Boolean,
|
|
value: false,
|
|
},
|
|
isImageDiff: {
|
|
type: Boolean,
|
|
computed: '_computeIsImageDiff(_diff)',
|
|
notify: true,
|
|
},
|
|
filesWeblinks: {
|
|
type: Object,
|
|
value: function() { return {}; },
|
|
notify: true,
|
|
},
|
|
hidden: {
|
|
type: Boolean,
|
|
reflectToAttribute: true,
|
|
},
|
|
_loggedIn: {
|
|
type: Boolean,
|
|
value: false,
|
|
},
|
|
lineWrapping: {
|
|
type: Boolean,
|
|
value: false,
|
|
observer: '_lineWrappingObserver',
|
|
},
|
|
viewMode: {
|
|
type: String,
|
|
value: DiffViewMode.SIDE_BY_SIDE,
|
|
observer: '_viewModeObserver',
|
|
},
|
|
_diff: Object,
|
|
_diffTableClass: {
|
|
type: String,
|
|
value: '',
|
|
},
|
|
_comments: Object,
|
|
_baseImage: Object,
|
|
_revisionImage: Object,
|
|
},
|
|
|
|
listeners: {
|
|
'thread-discard': '_handleThreadDiscard',
|
|
'comment-discard': '_handleCommentDiscard',
|
|
'comment-update': '_handleCommentUpdate',
|
|
'comment-save': '_handleCommentSave',
|
|
'create-comment': '_handleCreateComment',
|
|
},
|
|
|
|
attached: function() {
|
|
this._getLoggedIn().then(function(loggedIn) {
|
|
this._loggedIn = loggedIn;
|
|
}.bind(this));
|
|
|
|
},
|
|
|
|
ready: function() {
|
|
if (this._canRender()) {
|
|
this.reload();
|
|
}
|
|
},
|
|
|
|
reload: function() {
|
|
this._clearDiffContent();
|
|
|
|
var promises = [];
|
|
|
|
promises.push(this._getDiff().then(function(diff) {
|
|
this._diff = diff;
|
|
return this._loadDiffAssets();
|
|
}.bind(this)));
|
|
|
|
promises.push(this._getDiffCommentsAndDrafts().then(function(comments) {
|
|
this._comments = comments;
|
|
}.bind(this)));
|
|
|
|
return Promise.all(promises).then(function() {
|
|
if (this.prefs) {
|
|
return this._renderDiffTable();
|
|
}
|
|
return Promise.resolve();
|
|
}.bind(this));
|
|
},
|
|
|
|
getCursorStops: function() {
|
|
if (this.hidden && this.noAutoRender) {
|
|
return [];
|
|
}
|
|
|
|
return Polymer.dom(this.root).querySelectorAll('.diff-row');
|
|
},
|
|
|
|
addDraftAtLine: function(el) {
|
|
this._selectLine(el);
|
|
this._getLoggedIn().then(function(loggedIn) {
|
|
if (!loggedIn) {
|
|
this.fire('show-auth-required');
|
|
return;
|
|
}
|
|
|
|
var value = el.getAttribute('data-value');
|
|
if (value === GrDiffLine.FILE) {
|
|
this._addDraft(el);
|
|
return;
|
|
}
|
|
var lineNum = parseInt(value, 10);
|
|
if (isNaN(lineNum)) {
|
|
throw Error('Invalid line number: ' + value);
|
|
}
|
|
this._addDraft(el, lineNum);
|
|
}.bind(this));
|
|
},
|
|
|
|
isRangeSelected: function() {
|
|
return this.$.highlights.isRangeSelected();
|
|
},
|
|
|
|
toggleLeftDiff: function() {
|
|
this.toggleClass('no-left');
|
|
},
|
|
|
|
_canRender: function() {
|
|
return this.changeNum && this.patchRange && this.path &&
|
|
!this.noAutoRender;
|
|
},
|
|
|
|
_getCommentThreads: function() {
|
|
return Polymer.dom(this.root).querySelectorAll('gr-diff-comment-thread');
|
|
},
|
|
|
|
_computeContainerClass: function(loggedIn, viewMode, displayLine) {
|
|
var classes = ['diffContainer'];
|
|
switch (viewMode) {
|
|
case DiffViewMode.UNIFIED:
|
|
classes.push('unified');
|
|
break;
|
|
case DiffViewMode.SIDE_BY_SIDE:
|
|
classes.push('sideBySide');
|
|
break;
|
|
default:
|
|
throw Error('Invalid view mode: ', viewMode);
|
|
}
|
|
if (loggedIn) {
|
|
classes.push('canComment');
|
|
}
|
|
if (displayLine) {
|
|
classes.push('displayLine');
|
|
}
|
|
return classes.join(' ');
|
|
},
|
|
|
|
_handleTap: function(e) {
|
|
var el = Polymer.dom(e).rootTarget;
|
|
|
|
if (el.classList.contains('showContext')) {
|
|
this.$.diffBuilder.showContext(e.detail.groups, e.detail.section);
|
|
} else if (el.classList.contains('lineNum')) {
|
|
this.addDraftAtLine(el);
|
|
} else if (el.tagName === 'HL' ||
|
|
el.classList.contains('content') ||
|
|
el.classList.contains('contentText')) {
|
|
var target = this.$.diffBuilder.getLineElByChild(el);
|
|
if (target) { this._selectLine(target); }
|
|
}
|
|
},
|
|
|
|
_selectLine: function(el) {
|
|
this.fire('line-selected', {
|
|
side: el.classList.contains('left') ? DiffSide.LEFT : DiffSide.RIGHT,
|
|
number: el.getAttribute('data-value'),
|
|
path: this.path,
|
|
});
|
|
},
|
|
|
|
_handleCreateComment: function(e) {
|
|
var range = e.detail.range;
|
|
var diffSide = e.detail.side;
|
|
var line = range.endLine;
|
|
var lineEl = this.$.diffBuilder.getLineElByNumber(line, diffSide);
|
|
var contentText = this.$.diffBuilder.getContentByLineEl(lineEl);
|
|
var contentEl = contentText.parentElement;
|
|
var patchNum = this._getPatchNumByLineAndContent(lineEl, contentEl);
|
|
var isOnParent =
|
|
this._getIsParentCommentByLineAndContent(lineEl, contentEl);
|
|
var threadEl = this._getOrCreateThreadAtLineRange(contentEl, patchNum,
|
|
diffSide, isOnParent, range);
|
|
|
|
threadEl.addOrEditDraft(line, range);
|
|
},
|
|
|
|
_addDraft: function(lineEl, opt_lineNum) {
|
|
var contentText = this.$.diffBuilder.getContentByLineEl(lineEl);
|
|
var contentEl = contentText.parentElement;
|
|
var patchNum = this._getPatchNumByLineAndContent(lineEl, contentEl);
|
|
var commentSide = this._getCommentSideByLineAndContent(lineEl, contentEl);
|
|
var isOnParent =
|
|
this._getIsParentCommentByLineAndContent(lineEl, contentEl);
|
|
var threadEl = this._getOrCreateThreadAtLineRange(contentEl, patchNum,
|
|
commentSide, isOnParent);
|
|
|
|
threadEl.addOrEditDraft(opt_lineNum);
|
|
},
|
|
|
|
_getThreadForRange: function(threadGroupEl, rangeToCheck) {
|
|
return threadGroupEl.getThreadForRange(rangeToCheck);
|
|
},
|
|
|
|
_getThreadGroupForLine: function(contentEl) {
|
|
return contentEl.querySelector('gr-diff-comment-thread-group');
|
|
},
|
|
|
|
_getOrCreateThreadAtLineRange:
|
|
function(contentEl, patchNum, commentSide, isOnParent, range) {
|
|
var rangeToCheck = range ?
|
|
'range-' +
|
|
range.startLine + '-' +
|
|
range.startChar + '-' +
|
|
range.endLine + '-' +
|
|
range.endChar + '-' +
|
|
commentSide : 'line-' + commentSide;
|
|
|
|
// Check if thread group exists.
|
|
var threadGroupEl = this._getThreadGroupForLine(contentEl);
|
|
if (!threadGroupEl) {
|
|
threadGroupEl = this.$.diffBuilder.createCommentThreadGroup(
|
|
this.changeNum, patchNum, this.path, isOnParent,
|
|
this.projectConfig);
|
|
contentEl.appendChild(threadGroupEl);
|
|
}
|
|
|
|
var threadEl = this._getThreadForRange(threadGroupEl, rangeToCheck);
|
|
|
|
if (!threadEl) {
|
|
threadGroupEl.addNewThread(rangeToCheck, commentSide);
|
|
Polymer.dom.flush();
|
|
threadEl = this._getThreadForRange(threadGroupEl, rangeToCheck);
|
|
threadEl.commentSide = commentSide;
|
|
}
|
|
return threadEl;
|
|
},
|
|
|
|
_getPatchNumByLineAndContent: function(lineEl, contentEl) {
|
|
var patchNum = this.patchRange.patchNum;
|
|
if ((lineEl.classList.contains(DiffSide.LEFT) ||
|
|
contentEl.classList.contains('remove')) &&
|
|
this.patchRange.basePatchNum !== 'PARENT') {
|
|
patchNum = this.patchRange.basePatchNum;
|
|
}
|
|
return patchNum;
|
|
},
|
|
|
|
_getIsParentCommentByLineAndContent: function(lineEl, contentEl) {
|
|
var isOnParent = false;
|
|
if ((lineEl.classList.contains(DiffSide.LEFT) ||
|
|
contentEl.classList.contains('remove')) &&
|
|
this.patchRange.basePatchNum === 'PARENT') {
|
|
isOnParent = true;
|
|
}
|
|
return isOnParent;
|
|
},
|
|
|
|
_getCommentSideByLineAndContent: function(lineEl, contentEl) {
|
|
var side = 'right';
|
|
if (lineEl.classList.contains(DiffSide.LEFT) ||
|
|
contentEl.classList.contains('remove')) {
|
|
side = 'left';
|
|
}
|
|
return side;
|
|
},
|
|
|
|
_handleThreadDiscard: function(e) {
|
|
var el = Polymer.dom(e).rootTarget;
|
|
el.parentNode.removeThread(el.locationRange);
|
|
},
|
|
|
|
_handleCommentDiscard: function(e) {
|
|
var comment = e.detail.comment;
|
|
this._removeComment(comment, e.detail.patchNum);
|
|
},
|
|
|
|
_removeComment: function(comment, opt_patchNum) {
|
|
var side = comment.__commentSide;
|
|
this._removeCommentFromSide(comment, side);
|
|
},
|
|
|
|
_handleCommentSave: function(e) {
|
|
var comment = e.detail.comment;
|
|
var side = e.detail.comment.__commentSide;
|
|
var idx = this._findDraftIndex(comment, side);
|
|
this.set(['_comments', side, idx], comment);
|
|
},
|
|
|
|
_handleCommentUpdate: function(e) {
|
|
var comment = e.detail.comment;
|
|
var side = e.detail.comment.__commentSide;
|
|
var idx = this._findCommentIndex(comment, side);
|
|
if (idx === -1) {
|
|
idx = this._findDraftIndex(comment, side);
|
|
}
|
|
if (idx !== -1) { // Update draft or comment.
|
|
this.set(['_comments', side, idx], comment);
|
|
} else { // Create new draft.
|
|
this.push(['_comments', side], comment);
|
|
}
|
|
},
|
|
|
|
_removeCommentFromSide: function(comment, side) {
|
|
var idx = this._findCommentIndex(comment, side);
|
|
if (idx === -1) {
|
|
idx = this._findDraftIndex(comment, side);
|
|
}
|
|
if (idx !== -1) {
|
|
this.splice('_comments.' + side, idx, 1);
|
|
}
|
|
},
|
|
|
|
_findCommentIndex: function(comment, side) {
|
|
if (!comment.id || !this._comments[side]) {
|
|
return -1;
|
|
}
|
|
return this._comments[side].findIndex(function(item) {
|
|
return item.id === comment.id;
|
|
});
|
|
},
|
|
|
|
_findDraftIndex: function(comment, side) {
|
|
if (!comment.__draftID || !this._comments[side]) {
|
|
return -1;
|
|
}
|
|
return this._comments[side].findIndex(function(item) {
|
|
return item.__draftID === comment.__draftID;
|
|
});
|
|
},
|
|
|
|
_prefsObserver: function(newPrefs, oldPrefs) {
|
|
// Scan the preference objects one level deep to see if they differ.
|
|
var differ = !oldPrefs;
|
|
if (newPrefs && oldPrefs) {
|
|
for (var key in newPrefs) {
|
|
if (newPrefs[key] !== oldPrefs[key]) {
|
|
differ = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (differ) {
|
|
this._prefsChanged(newPrefs);
|
|
}
|
|
},
|
|
|
|
_viewModeObserver: function() {
|
|
this._prefsChanged(this.prefs);
|
|
},
|
|
|
|
_lineWrappingObserver: function() {
|
|
this._prefsChanged(this.prefs);
|
|
},
|
|
|
|
_prefsChanged: function(prefs) {
|
|
if (!prefs) { return; }
|
|
if (prefs.line_wrapping) {
|
|
this._diffTableClass = 'full-width';
|
|
if (this.viewMode === 'SIDE_BY_SIDE') {
|
|
this.customStyle['--content-width'] = 'none';
|
|
}
|
|
} else {
|
|
this._diffTableClass = '';
|
|
this.customStyle['--content-width'] = prefs.line_length + 'ch';
|
|
}
|
|
|
|
if (!!prefs.font_size) {
|
|
this.customStyle['--font-size'] = prefs.font_size + 'px';
|
|
}
|
|
|
|
this.updateStyles();
|
|
|
|
if (this._diff && this._comments) {
|
|
this._renderDiffTable();
|
|
}
|
|
},
|
|
|
|
_renderDiffTable: function() {
|
|
return this.$.diffBuilder.render(this._comments, this.prefs);
|
|
},
|
|
|
|
_clearDiffContent: function() {
|
|
this.$.diffTable.innerHTML = null;
|
|
},
|
|
|
|
_handleGetDiffError: function(response) {
|
|
// Loading the diff may respond with 409 if the file is too large. In this
|
|
// case, use a toast error..
|
|
if (response.status === 409) {
|
|
this.fire('server-error', {response: response});
|
|
return;
|
|
}
|
|
this.fire('page-error', {response: response});
|
|
},
|
|
|
|
_getDiff: function() {
|
|
return this.$.restAPI.getDiff(
|
|
this.changeNum,
|
|
this.patchRange.basePatchNum,
|
|
this.patchRange.patchNum,
|
|
this.path,
|
|
this._handleGetDiffError.bind(this)).then(function(diff) {
|
|
this.filesWeblinks = {
|
|
meta_a: diff && diff.meta_a && diff.meta_a.web_links,
|
|
meta_b: diff && diff.meta_b && diff.meta_b.web_links,
|
|
};
|
|
return diff;
|
|
}.bind(this));
|
|
},
|
|
|
|
_getDiffComments: function() {
|
|
return this.$.restAPI.getDiffComments(
|
|
this.changeNum,
|
|
this.patchRange.basePatchNum,
|
|
this.patchRange.patchNum,
|
|
this.path);
|
|
},
|
|
|
|
_getDiffDrafts: function() {
|
|
return this._getLoggedIn().then(function(loggedIn) {
|
|
if (!loggedIn) {
|
|
return Promise.resolve({baseComments: [], comments: []});
|
|
}
|
|
return this.$.restAPI.getDiffDrafts(
|
|
this.changeNum,
|
|
this.patchRange.basePatchNum,
|
|
this.patchRange.patchNum,
|
|
this.path);
|
|
}.bind(this));
|
|
},
|
|
|
|
_getDiffRobotComments: function() {
|
|
return this.$.restAPI.getDiffRobotComments(
|
|
this.changeNum,
|
|
this.patchRange.basePatchNum,
|
|
this.patchRange.patchNum,
|
|
this.path);
|
|
},
|
|
|
|
_getDiffCommentsAndDrafts: function() {
|
|
var promises = [];
|
|
promises.push(this._getDiffComments());
|
|
promises.push(this._getDiffDrafts());
|
|
promises.push(this._getDiffRobotComments());
|
|
return Promise.all(promises).then(function(results) {
|
|
return Promise.resolve({
|
|
comments: results[0],
|
|
drafts: results[1],
|
|
robotComments: results[2],
|
|
});
|
|
}).then(this._normalizeDiffCommentsAndDrafts.bind(this));
|
|
},
|
|
|
|
_normalizeDiffCommentsAndDrafts: function(results) {
|
|
function markAsDraft(d) {
|
|
d.__draft = true;
|
|
return d;
|
|
}
|
|
var baseDrafts = results.drafts.baseComments.map(markAsDraft);
|
|
var drafts = results.drafts.comments.map(markAsDraft);
|
|
|
|
var baseRobotComments = results.robotComments.baseComments;
|
|
var robotComments = results.robotComments.comments;
|
|
return Promise.resolve({
|
|
meta: {
|
|
path: this.path,
|
|
changeNum: this.changeNum,
|
|
patchRange: this.patchRange,
|
|
projectConfig: this.projectConfig,
|
|
},
|
|
left: results.comments.baseComments.concat(baseDrafts)
|
|
.concat(baseRobotComments),
|
|
right: results.comments.comments.concat(drafts)
|
|
.concat(robotComments),
|
|
});
|
|
},
|
|
|
|
_getLoggedIn: function() {
|
|
return this.$.restAPI.getLoggedIn();
|
|
},
|
|
|
|
_computeIsImageDiff: function() {
|
|
if (!this._diff) { return false; }
|
|
|
|
var isA = this._diff.meta_a &&
|
|
this._diff.meta_a.content_type.indexOf('image/') === 0;
|
|
var isB = this._diff.meta_b &&
|
|
this._diff.meta_b.content_type.indexOf('image/') === 0;
|
|
|
|
return this._diff.binary && (isA || isB);
|
|
},
|
|
|
|
_loadDiffAssets: function() {
|
|
if (this.isImageDiff) {
|
|
return this._getImages().then(function(images) {
|
|
this._baseImage = images.baseImage;
|
|
this._revisionImage = images.revisionImage;
|
|
}.bind(this));
|
|
} else {
|
|
this._baseImage = null;
|
|
this._revisionImage = null;
|
|
return Promise.resolve();
|
|
}
|
|
},
|
|
|
|
_getImages: function() {
|
|
return this.$.restAPI.getImagesForDiff(this.project, this.commit,
|
|
this.changeNum, this._diff, this.patchRange);
|
|
},
|
|
|
|
_projectConfigChanged: function(projectConfig) {
|
|
var threadEls = this._getCommentThreads();
|
|
for (var i = 0; i < threadEls.length; i++) {
|
|
threadEls[i].projectConfig = projectConfig;
|
|
}
|
|
},
|
|
});
|
|
})();
|