
This removes the repetitiveness of always having to specify json-prefix and debounce-duration on requests with the auto attribute set. Change-Id: I0ef6b752866602742621d98d4b4666bc977c8b47
757 lines
25 KiB
HTML
757 lines
25 KiB
HTML
<!--
|
||
Copyright (C) 2015 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.
|
||
-->
|
||
|
||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||
<link rel="import" href="../bower_components/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
|
||
<link rel="import" href="gr-ajax.html">
|
||
<link rel="import" href="gr-diff-comment-thread.html">
|
||
|
||
<dom-module id="gr-diff-view">
|
||
<template>
|
||
<style>
|
||
:host {
|
||
background-color: var(--view-background-color);
|
||
display: block;
|
||
}
|
||
h3 {
|
||
margin-top: 1em;
|
||
padding: .75em var(--default-horizontal-margin);
|
||
}
|
||
.mainContainer {
|
||
border-bottom: 1px solid #eee;
|
||
border-collapse: collapse;
|
||
border-top: 1px solid #eee;
|
||
width: 100%;
|
||
}
|
||
.diffNumbers,
|
||
.diffContent {
|
||
vertical-align: top;
|
||
}
|
||
.diffContainer {
|
||
font-family: 'Source Code Pro', monospace;
|
||
white-space: pre;
|
||
}
|
||
.diffNumbers {
|
||
background-color: #eee;
|
||
color: #666;
|
||
padding: 0 .75em;
|
||
text-align: right;
|
||
}
|
||
.diffContent {
|
||
min-width: 80ch;
|
||
max-width: 120ch;
|
||
overflow: hidden;
|
||
}
|
||
.diffContainer.leftOnly .diffContent,
|
||
.diffContainer.rightOnly .diffContent {
|
||
overflow: visible;
|
||
}
|
||
.diffContainer.leftOnly .right,
|
||
.diffContainer.rightOnly .left {
|
||
display: none;
|
||
}
|
||
.ruler {
|
||
display: block;
|
||
background-color: #ddd;
|
||
height: 1.3em;
|
||
position: absolute;
|
||
top: 0;
|
||
width: 1px;
|
||
}
|
||
.lineNum:before,
|
||
.content:before {
|
||
/* To ensure the height is non-zero in these elements, a
|
||
zero-width space is set as its content. The character
|
||
itself doesn't matter. Just that there is something
|
||
there. */
|
||
content: '\200B';
|
||
}
|
||
.content {
|
||
position: relative;
|
||
}
|
||
.lineNum.blank,
|
||
.threadFiller--redLine {
|
||
border-right: 2px solid #F34D4D;
|
||
margin-right: 3px;
|
||
}
|
||
|
||
.lineNum:not(.blank) {
|
||
cursor: pointer;
|
||
}
|
||
.lineNum:hover {
|
||
text-decoration: underline;
|
||
}
|
||
.lightRed {
|
||
background-color: #ffecec;
|
||
}
|
||
.darkRed {
|
||
background-color: #faa;
|
||
}
|
||
.lightGreen {
|
||
background-color: #eaffea;
|
||
}
|
||
.darkGreen {
|
||
background-color: #9f9;
|
||
}
|
||
</style>
|
||
<gr-ajax id="changeDetailXHR"
|
||
auto
|
||
url="[[_computeChangeDetailPath(_changeNum)]]"
|
||
params="[[_computeChangeDetailQueryParams()]]"
|
||
last-response="{{_change}}"></gr-ajax>
|
||
<gr-ajax id="diffXHR"
|
||
url="[[_computeDiffPath(_changeNum, _patchNum, _path)]]"
|
||
on-response="_handleDiffResponse"></gr-ajax>
|
||
<gr-ajax id="leftCommentsXHR"
|
||
url="[[_computeCommentsPath(_changeNum, _basePatchNum)]]"
|
||
json-prefix=")]}'"
|
||
on-response="_handleLeftCommentsResponse"></gr-ajax>
|
||
<gr-ajax id="rightCommentsXHR"
|
||
url="[[_computeCommentsPath(_changeNum, _patchNum)]]"
|
||
on-response="_handleRightCommentsResponse"></gr-ajax>
|
||
<!-- TODO(andybons): This is populated in gr-change-view. Use that instead
|
||
of incurring an extra ajax call. -->
|
||
<gr-ajax id="filesXHR"
|
||
url="[[_computeFilesPath(_changeNum, _patchNum)]]"
|
||
on-response="_handleFilesResponse"></gr-ajax>
|
||
<gr-ajax id="leftDraftsXHR"
|
||
url="[[_computeDraftsPath(_changeNum, _basePatchNum)]]"
|
||
on-response="_handleLeftDraftsResponse"></gr-ajax>
|
||
<gr-ajax id="rightDraftsXHR"
|
||
url="[[_computeDraftsPath(_changeNum, _patchNum)]]"
|
||
on-response="_handleRightDraftsResponse"></gr-ajax>
|
||
|
||
<h3>
|
||
<a href$="[[_computeChangePath(_changeNum)]]">[[_changeNum]]</a><span>:</span>
|
||
<span>[[_change.subject]]</span> — <span>[[params.path]]</span>
|
||
</h3>
|
||
<table class="mainContainer">
|
||
<tr class="diffContainer" id="diffContainer">
|
||
<td class="diffNumbers left" id="leftDiffNumbers"></td>
|
||
<td class="diffContent left" id="leftDiffContent"></td>
|
||
<td class="diffNumbers right" id="rightDiffNumbers"></td>
|
||
<td class="diffContent right" id="rightDiffContent"></td>
|
||
</tr>
|
||
</table>
|
||
</template>
|
||
<script>
|
||
(function() {
|
||
'use strict';
|
||
|
||
Polymer({
|
||
is: 'gr-diff-view',
|
||
|
||
behaviors: [
|
||
Polymer.IronA11yKeysBehavior
|
||
],
|
||
|
||
properties: {
|
||
keyEventTarget: {
|
||
type: Object,
|
||
value: function() {
|
||
return document.body;
|
||
},
|
||
},
|
||
/**
|
||
* URL params passed from the router.
|
||
*/
|
||
params: {
|
||
type: Object,
|
||
observer: '_paramsChanged',
|
||
},
|
||
rulerWidth: {
|
||
type: Number,
|
||
value: 80,
|
||
observer: '_rulerWidthChanged',
|
||
},
|
||
_basePatchNum: String,
|
||
_change: Object,
|
||
_changeNum: String,
|
||
_diff: Object,
|
||
_fileList: {
|
||
type: Array,
|
||
value: function() { return []; },
|
||
},
|
||
_leftComments: {
|
||
type: Array,
|
||
value: function() { return []; },
|
||
},
|
||
_leftDrafts: {
|
||
type: Array,
|
||
value: function() { return []; },
|
||
},
|
||
_patchNum: String,
|
||
_path: String,
|
||
_rendered: Boolean,
|
||
_rightComments: {
|
||
type: Array,
|
||
value: function() { return []; },
|
||
},
|
||
_rightDrafts: {
|
||
type: Array,
|
||
value: function() { return []; },
|
||
},
|
||
},
|
||
|
||
listeners: {
|
||
'diffContainer.tap': '_diffContainerTapHandler',
|
||
},
|
||
|
||
keyBindings: {
|
||
'[ ] u': '_handleKey',
|
||
},
|
||
|
||
_paramsChanged: function(value) {
|
||
this._changeNum = value.changeNum;
|
||
this._patchNum = value.patchNum;
|
||
this._basePatchNum = value.basePatchNum;
|
||
this._path = value.path;
|
||
if (!this._patchNum) {
|
||
this._change = null;
|
||
this._basePatchNum = null;
|
||
this._patchNum = null;
|
||
this._diff = null;
|
||
this._path = null;
|
||
this._leftComments = [];
|
||
this._rightComments = [];
|
||
this._leftDrafts = [];
|
||
this._rightDrafts = [];
|
||
this._rendered = false;
|
||
return;
|
||
}
|
||
// Assign the params here since a computed binding relying on
|
||
// `_basePatchNum` won't fire in the case where it's not defined.
|
||
this.$.diffXHR.params = this._diffQueryParams(this._basePatchNum);
|
||
|
||
var requestPromises = [];
|
||
requestPromises.push(this.$.diffXHR.generateRequest().completes);
|
||
|
||
if (this._basePatchNum) {
|
||
requestPromises.push(
|
||
this.$.leftCommentsXHR.generateRequest().completes);
|
||
}
|
||
|
||
requestPromises.push(
|
||
this.$.rightCommentsXHR.generateRequest().completes);
|
||
requestPromises.push(this.$.filesXHR.generateRequest().completes);
|
||
|
||
app.accountReady.then(function() {
|
||
if (app.loggedIn) {
|
||
if (this._basePatchNum) {
|
||
requestPromises.push(
|
||
this.$.leftDraftsXHR.generateRequest().completes);
|
||
}
|
||
requestPromises.push(
|
||
this.$.rightDraftsXHR.generateRequest().completes);
|
||
}
|
||
|
||
Promise.all(requestPromises).then(function(requests) {
|
||
this._renderDiff(this._diff, this._leftComments,
|
||
this._rightComments, this._leftDrafts, this._rightDrafts);
|
||
}.bind(this), function(err) {
|
||
alert('Oops. Something went wrong. Check the console and bug the ' +
|
||
'PolyGerrit team for assistance.');
|
||
throw err;
|
||
});
|
||
}.bind(this));
|
||
},
|
||
|
||
_rulerWidthChanged: function(newValue, oldValue) {
|
||
if (newValue < 0) {
|
||
throw Error('ruler width must be greater than zero.');
|
||
}
|
||
if (oldValue == 0) {
|
||
this._renderRulerElements();
|
||
}
|
||
var remove = newValue == 0;
|
||
var rulerEls = Polymer.dom(this.root).querySelectorAll('.ruler');
|
||
for (var i = 0; i < rulerEls.length; i++) {
|
||
if (remove) {
|
||
rulerEls[i].parentNode.removeChild(rulerEls[i]);
|
||
} else {
|
||
rulerEls[i].style.left = newValue + 'ch';
|
||
}
|
||
}
|
||
},
|
||
|
||
_computeChangePath: function(changeNum) {
|
||
return '/c/' + changeNum;
|
||
},
|
||
|
||
_computeChangeDetailPath: function(changeNum) {
|
||
return '/changes/' + changeNum + '/detail';
|
||
},
|
||
|
||
_computeChangeDetailQueryParams: function() {
|
||
var options = Changes.listChangesOptionsToHex(
|
||
Changes.ListChangesOption.ALL_REVISIONS
|
||
);
|
||
return { O: options };
|
||
},
|
||
|
||
_computeDiffPath: function(changeNum, patchNum, path) {
|
||
return '/changes/' + changeNum + '/revisions/' + patchNum + '/files/' +
|
||
encodeURIComponent(path) + '/diff';
|
||
},
|
||
|
||
_computeCommentsPath: function(changeNum, patchNum) {
|
||
return '/changes/' + changeNum + '/revisions/' + patchNum + '/comments';
|
||
},
|
||
|
||
_computeFilesPath: function(changeNum, patchNum) {
|
||
return '/changes/' + changeNum + '/revisions/' + patchNum + '/files';
|
||
},
|
||
|
||
_computeDraftsPath: function(changeNum, patchNum) {
|
||
return '/changes/' + changeNum + '/revisions/' + patchNum + '/drafts';
|
||
},
|
||
|
||
_diffQueryParams: function(basePatchNum) {
|
||
var params = {
|
||
context: 'ALL',
|
||
intraline: null
|
||
};
|
||
if (!!basePatchNum) {
|
||
params.base = basePatchNum;
|
||
}
|
||
return params;
|
||
},
|
||
|
||
_diffContainerTapHandler: function(e) {
|
||
var el = e.detail.sourceEvent.target;
|
||
// This tap handler only handles line number taps.
|
||
if (!el.classList.contains('lineNum')) { return; }
|
||
|
||
var leftSide = el.parentNode == this.$.leftDiffNumbers;
|
||
var rightSide = el.parentNode == this.$.rightDiffNumbers;
|
||
if (leftSide == rightSide) {
|
||
throw Error('Comment tap event cannot originate from both left and ' +
|
||
'right side');
|
||
}
|
||
|
||
// If a draft or comment is already present at that line, don’t do
|
||
// anything.
|
||
var lineNum = el.getAttribute('data-line-num');
|
||
var patchNum = el.getAttribute('data-patch-num');
|
||
|
||
var existingEl = this.$$('gr-diff-comment-thread' +
|
||
'[data-patch-num="' + patchNum + '"]' +
|
||
'[data-line-num="' + lineNum + '"]');
|
||
if (existingEl) {
|
||
// A comment or draft is already present at this line.
|
||
return;
|
||
}
|
||
|
||
var tempDraftID = Math.floor(Math.random() * Math.pow(10, 10)) + '';
|
||
var drafts = [{
|
||
__draft: true,
|
||
__draftID: tempDraftID,
|
||
path: this._path,
|
||
line: lineNum,
|
||
}];
|
||
|
||
// If the comment is on the left side of a side-by-side diff with the
|
||
// parent on the left and a patch with patchNum on the right, the patch
|
||
// number passed to the backend is the right side patchNum when mutating
|
||
// a draft. The property `side` is used to determine that it should be
|
||
// on the parent patch, which is inconsistent and why this looks weird.
|
||
var patchNum = this._patchNum;
|
||
if (leftSide && this._basePatchNum == null) {
|
||
drafts[0].side = 'PARENT';
|
||
patchNum = 'PARENT';
|
||
}
|
||
|
||
this._addThread(drafts, patchNum, lineNum);
|
||
},
|
||
|
||
_handleLeftCommentsResponse: function(e, req) {
|
||
this._leftComments = e.detail.response[this._path] || [];
|
||
},
|
||
|
||
_handleRightCommentsResponse: function(e, req) {
|
||
this._rightComments = e.detail.response[this._path] || [];
|
||
},
|
||
|
||
_handleLeftDraftsResponse: function(e, req) {
|
||
this._leftDrafts = e.detail.response[this._path] || [];
|
||
},
|
||
|
||
_handleRightDraftsResponse: function(e, req) {
|
||
this._rightDrafts = e.detail.response[this._path] || [];
|
||
},
|
||
|
||
_handleFilesResponse: function(e, req) {
|
||
this._fileList = Object.keys(e.detail.response).sort();
|
||
},
|
||
|
||
_handleDiffResponse: function(e, req) {
|
||
this._diff = e.detail.response;
|
||
},
|
||
|
||
_handleKey: function(e) {
|
||
if (util.shouldSupressKeyboardShortcut(e)) { return; }
|
||
|
||
switch(e.detail.combo) {
|
||
case '[':
|
||
this._navToFile(this._fileList, -1);
|
||
break;
|
||
case ']':
|
||
this._navToFile(this._fileList, 1);
|
||
break;
|
||
case 'u':
|
||
if (this._changeNum) {
|
||
page.show(this._computeChangePath(this._changeNum));
|
||
}
|
||
break;
|
||
}
|
||
},
|
||
|
||
_navToFile: function(fileList, direction) {
|
||
if (fileList.length == 0) { return; }
|
||
|
||
var idx = fileList.indexOf(this._path) + direction;
|
||
if (idx < 0 || idx > fileList.length - 1) {
|
||
page.show(this._computeChangePath(this._changeNum));
|
||
return;
|
||
}
|
||
page.show(
|
||
this._diffURL(this._changeNum, this._patchNum, fileList[idx]));
|
||
},
|
||
|
||
_diffURL: function(changeNum, patchNum, path) {
|
||
return '/c/' + changeNum + '/' + patchNum + '/' + path;
|
||
},
|
||
|
||
_threadID: function(patchNum, lineNum) {
|
||
return 'thread-' + patchNum + '-' + lineNum;
|
||
},
|
||
|
||
_renderCommentsAndDrafts: function(comments, drafts, patchNum) {
|
||
// Drafts and comments are combined here, with drafts annotated with a
|
||
// property.
|
||
var annotatedDrafts = drafts.map(function(d) {
|
||
d.__draft = true;
|
||
return d;
|
||
});
|
||
comments = comments.concat(annotatedDrafts);
|
||
|
||
// Group the comments and drafts by line number. Absence of a line
|
||
// number indicates a top-level file comment or draft.
|
||
var threads = {};
|
||
|
||
for (var i = 0; i < comments.length; i++) {
|
||
var line = comments[i].line || 'FILE';
|
||
if (threads[line] == null) {
|
||
threads[line] = []
|
||
}
|
||
threads[line].push(comments[i]);
|
||
}
|
||
for (var lineNum in threads) {
|
||
this._addThread(threads[lineNum], patchNum, lineNum);
|
||
}
|
||
},
|
||
|
||
_addThread: function(comments, patchNum, lineNum) {
|
||
var el = document.createElement('gr-diff-comment-thread');
|
||
el.comments = comments;
|
||
el.changeNum = this._changeNum;
|
||
// Assign the element's patchNum to the right side patchNum if the
|
||
// passed patchNum is 'PARENT' due to the odd behavior of the REST API.
|
||
// Don't overwrite patchNum since 'PARENT' is used for other properties.
|
||
el.patchNum = patchNum == 'PARENT' ? this._patchNum : patchNum;
|
||
|
||
var threadID = this._threadID(patchNum, lineNum);
|
||
el.setAttribute('data-thread-id', threadID);
|
||
el.setAttribute('data-line-num', lineNum);
|
||
el.setAttribute('data-patch-num', patchNum);
|
||
|
||
// Find the element that the thread should be appended after. In the
|
||
// case of a file comment, it will be appended after the first line.
|
||
// TODO: Show file comment above the file itself.
|
||
var fileComment = lineNum == 'FILE';
|
||
if (fileComment) {
|
||
lineNum = 1;
|
||
}
|
||
var contentEl = this.$$('.content' +
|
||
'[data-patch-num="' + patchNum + '"]' +
|
||
'[data-line-num="' + lineNum + '"]');
|
||
var rowNum = contentEl.getAttribute('data-row-num');
|
||
el.addEventListener('gr-diff-comment-thread-height-changed',
|
||
this._handleCommentThreadHeightChange.bind(this, rowNum, threadID));
|
||
Polymer.dom(contentEl.parentNode).insertBefore(
|
||
el, contentEl.nextSibling);
|
||
},
|
||
|
||
_handleCommentThreadHeightChange: function(rowNum, threadID, e) {
|
||
// Adjust the filler element heights if they're present in the DOM.
|
||
var els = Polymer.dom(this.root).querySelectorAll(
|
||
'.js-threadFiller[data-thread-id="' + threadID + '"]');
|
||
if (els.length > 0) {
|
||
for (var i = 0; i < els.length; i++) {
|
||
els[i].style.height = e.detail.height + 'px';
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Create the filler elements if they're not already present.
|
||
var els = Polymer.dom(this.root).querySelectorAll(
|
||
'[data-row-num="' + rowNum + '"]');
|
||
for (var i = 0; i < els.length; i++) {
|
||
// Is this is the column with the comment? Skip if so.
|
||
if (els[i].nextSibling &&
|
||
els[i].nextSibling.tagName == 'GR-DIFF-COMMENT-THREAD') {
|
||
continue;
|
||
}
|
||
var fillerEl = document.createElement('div');
|
||
fillerEl.setAttribute('data-thread-id', threadID);
|
||
fillerEl.classList.add('js-threadFiller');
|
||
if (els[i].classList.contains('lineNum')) {
|
||
fillerEl.classList.add('threadFiller--redLine');
|
||
}
|
||
fillerEl.style.height = e.detail.height + 'px';
|
||
Polymer.dom(els[i].parentNode).insertBefore(
|
||
fillerEl, els[i].nextSibling);
|
||
}
|
||
},
|
||
|
||
_clearChildren: function(el) {
|
||
while (el.firstChild) {
|
||
el.removeChild(el.firstChild);
|
||
}
|
||
},
|
||
|
||
_renderDiff: function(
|
||
diff, leftComments, rightComments, leftDrafts, rightDrafts) {
|
||
if (this._rendered) {
|
||
this._clearChildren(this.$.leftDiffNumbers);
|
||
this._clearChildren(this.$.leftDiffContent);
|
||
this._clearChildren(this.$.rightDiffNumbers);
|
||
this._clearChildren(this.$.rightDiffContent);
|
||
}
|
||
|
||
this.$.diffContainer.classList.toggle('rightOnly',
|
||
diff.change_type == Changes.DiffType.ADDED);
|
||
this.$.diffContainer.classList.toggle('leftOnly',
|
||
diff.change_type == Changes.DiffType.DELETED);
|
||
|
||
var initialLineNum = 0 + (diff.content.skip || 0);
|
||
var ctx = {
|
||
rowNum: 0,
|
||
left: {
|
||
lineNum: initialLineNum,
|
||
content: '',
|
||
cssClass: '',
|
||
},
|
||
right: {
|
||
lineNum: initialLineNum,
|
||
content: '',
|
||
cssClass: '',
|
||
}
|
||
};
|
||
for (var i = 0; i < diff.content.length; i++) {
|
||
this._addDiffChunk(ctx, diff.content[i]);
|
||
}
|
||
|
||
if (leftComments) {
|
||
this._renderCommentsAndDrafts(leftComments, leftDrafts,
|
||
this._basePatchNum);
|
||
}
|
||
if (rightComments) {
|
||
this._renderCommentsAndDrafts(rightComments, rightDrafts,
|
||
this._patchNum);
|
||
}
|
||
|
||
if (this.rulerWidth) {
|
||
this._renderRulerElements();
|
||
}
|
||
|
||
if (window.location.hash.length > 0) {
|
||
// Allow for the initial rendering to complete before scrolling to the
|
||
// appropriate line.
|
||
this.async(function() {
|
||
this._jumpToLine(parseInt(window.location.hash.substring(1), 10));
|
||
}.bind(this), 1);
|
||
}
|
||
|
||
this._rendered = true;
|
||
},
|
||
|
||
_jumpToLine: function(lineNum) {
|
||
if (isNaN(lineNum) || lineNum < 1) { return; }
|
||
|
||
var el = this.$$(
|
||
'.diffNumbers.right .lineNum[data-line-num="' + lineNum + '"]');
|
||
if (!el) { return; }
|
||
|
||
// Calculate where the line is relative to the window.
|
||
var top = el.offsetTop;
|
||
for (var offsetParent = el.offsetParent;
|
||
offsetParent;
|
||
offsetParent = offsetParent.offsetParent) {
|
||
top += offsetParent.offsetTop;
|
||
}
|
||
|
||
// Scroll the element to the middle of the window. Dividing by a third
|
||
// instead of half the inner height feels a bit better otherwise the
|
||
// element appears to be below the center of the window even when it
|
||
// isn't.
|
||
window.scrollTo(0, top - (window.innerHeight / 3) - el.offsetHeight);
|
||
},
|
||
|
||
_addDiffChunk: function(ctx, diffChunk) {
|
||
// Simplest case where both sides have the same content.
|
||
if (diffChunk.ab) {
|
||
for (var i = 0; i < diffChunk.ab.length; i++) {
|
||
ctx.left.lineNum++;
|
||
ctx.right.lineNum++;
|
||
ctx.left.content = ctx.right.content = diffChunk.ab[i];
|
||
ctx.left.cssClass = ctx.right.cssClass = null;
|
||
this._addRow(ctx);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (diffChunk.a) {
|
||
ctx.left.cssClass = 'lightRed';
|
||
} else {
|
||
delete(ctx.left.cssClass);
|
||
}
|
||
if (diffChunk.b) {
|
||
ctx.right.cssClass = 'lightGreen';
|
||
} else {
|
||
delete(ctx.right.cssClass);
|
||
}
|
||
|
||
var aLen = (diffChunk.a && diffChunk.a.length) || 0;
|
||
var bLen = (diffChunk.b && diffChunk.b.length) || 0;
|
||
var maxLen = Math.max(aLen, bLen);
|
||
for (var i = 0; i < maxLen; i++) {
|
||
if (diffChunk.a && i < diffChunk.a.length) {
|
||
ctx.left.lineNum++;
|
||
ctx.left.content = diffChunk.a[i];
|
||
} else {
|
||
delete(ctx.left.content);
|
||
}
|
||
if (diffChunk.b && i < diffChunk.b.length) {
|
||
ctx.right.lineNum++;
|
||
ctx.right.content = diffChunk.b[i];
|
||
} else {
|
||
delete(ctx.right.content);
|
||
}
|
||
this._addRow(ctx);
|
||
}
|
||
},
|
||
|
||
_addRow: function(ctx) {
|
||
var leftLineNumEl = this._createElement('div', 'lineNum');
|
||
var leftColEl = this._createElement('div', 'content');
|
||
var rightLineNumEl = this._createElement('div', 'lineNum');
|
||
var rightColEl = this._createElement('div', 'content');
|
||
|
||
[leftColEl,
|
||
rightColEl,
|
||
leftLineNumEl,
|
||
rightLineNumEl].forEach(function(el) {
|
||
el.setAttribute('data-row-num', ctx.rowNum);
|
||
});
|
||
|
||
[leftLineNumEl, leftColEl].forEach(function(el) {
|
||
el.setAttribute('data-patch-num', this._basePatchNum || 'PARENT');
|
||
}.bind(this));
|
||
|
||
[rightLineNumEl, rightColEl].forEach(function(el) {
|
||
el.setAttribute('data-patch-num', this._patchNum);
|
||
}.bind(this));
|
||
|
||
if (ctx.left.content != null) {
|
||
leftLineNumEl.textContent = ctx.left.lineNum;
|
||
[leftLineNumEl, leftColEl].forEach(function(el) {
|
||
el.setAttribute('data-line-num', ctx.left.lineNum);
|
||
});
|
||
} else {
|
||
leftLineNumEl.classList.add('blank');
|
||
}
|
||
if (ctx.right.content != null) {
|
||
rightLineNumEl.textContent = ctx.right.lineNum;
|
||
[rightLineNumEl, rightColEl].forEach(function(el) {
|
||
el.setAttribute('data-line-num', ctx.right.lineNum);
|
||
});
|
||
} else {
|
||
rightLineNumEl.classList.add('blank');
|
||
}
|
||
|
||
// Content must be defined to prevent the HTML from showing 'undefined'.
|
||
// Additionally, a newline is used place of an empty string to ensure
|
||
// copy/paste works correctly.
|
||
ctx.left.content = ctx.left.content || '\n';
|
||
ctx.right.content = ctx.right.content || '\n';
|
||
|
||
if (!!ctx.left.cssClass) {
|
||
leftColEl.classList.add(ctx.left.cssClass);
|
||
}
|
||
if (!!ctx.right.cssClass) {
|
||
rightColEl.classList.add(ctx.right.cssClass);
|
||
}
|
||
|
||
var leftHTML = util.escapeHTML(ctx.left.content);
|
||
var rightHTML = util.escapeHTML(ctx.right.content);
|
||
|
||
// If the html is equivalent to the text then it didn't get highlighted
|
||
// or escaped. Use textContent which is faster than innerHTML.
|
||
if (ctx.left.content == leftHTML) {
|
||
leftColEl.textContent = ctx.left.content;
|
||
} else {
|
||
leftColEl.innerHTML = leftHTML;
|
||
}
|
||
if (ctx.right.content == rightHTML) {
|
||
rightColEl.textContent = ctx.right.content;
|
||
} else {
|
||
rightColEl.innerHTML = rightHTML;
|
||
}
|
||
|
||
this.$.leftDiffNumbers.appendChild(leftLineNumEl);
|
||
this.$.leftDiffContent.appendChild(leftColEl);
|
||
this.$.rightDiffNumbers.appendChild(rightLineNumEl);
|
||
this.$.rightDiffContent.appendChild(rightColEl);
|
||
|
||
ctx.rowNum++;
|
||
},
|
||
|
||
_renderRulerElements: function() {
|
||
var contentEls = Polymer.dom(this.root).querySelectorAll('.content');
|
||
for (var i = 0; i < contentEls.length; i++) {
|
||
var rulerEl = this._createElement('i', 'ruler');
|
||
rulerEl.style.left = this.rulerWidth + 'ch';
|
||
contentEls[i].appendChild(rulerEl);
|
||
}
|
||
},
|
||
|
||
_createElement: function(tagName, className) {
|
||
var el = document.createElement(tagName);
|
||
// When Shady DOM is being used, these classes are added to account for
|
||
// Polymer's polyfill behavior. In order to guarantee sufficient
|
||
// specificity within the CSS rules, these are added to every element.
|
||
// Since the Polymer DOM utility functions (which would do this
|
||
// automatically) are not being used for performance reasons, this is
|
||
// done manually.
|
||
el.className = 'style-scope gr-diff-view ' + className;
|
||
return el;
|
||
},
|
||
});
|
||
})();
|
||
</script>
|
||
</dom-module>
|