Move gr-diff-new to gr-diff
Change-Id: Ifaad016f806c31f3df43143b3238b757faa18b20
This commit is contained in:
@@ -194,6 +194,10 @@ limitations under the License.
|
||||
<td><span class="key">a</span></td>
|
||||
<td>Review and publish comments</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="key">,</span></td>
|
||||
<td>Show diff preferences</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
|
@@ -44,7 +44,6 @@ limitations under the License.
|
||||
draft="[[comment.__draft]]"
|
||||
show-actions="[[_showActions]]"
|
||||
project-config="[[projectConfig]]"
|
||||
on-height-change="_handleCommentHeightChange"
|
||||
on-reply="_handleCommentReply"
|
||||
on-comment-discard="_handleCommentDiscard"
|
||||
on-done="_handleCommentDone"></gr-diff-comment>
|
||||
|
@@ -17,12 +17,6 @@
|
||||
Polymer({
|
||||
is: 'gr-diff-comment-thread',
|
||||
|
||||
/**
|
||||
* Fired when the height of the thread changes.
|
||||
*
|
||||
* @event height-change
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when the thread should be discarded.
|
||||
*
|
||||
@@ -44,11 +38,6 @@
|
||||
},
|
||||
|
||||
_showActions: Boolean,
|
||||
_boundWindowResizeHandler: {
|
||||
type: Function,
|
||||
value: function() { return this._handleWindowResize.bind(this); }
|
||||
},
|
||||
_lastHeight: Number,
|
||||
_orderedComments: Array,
|
||||
},
|
||||
|
||||
@@ -64,12 +53,6 @@
|
||||
this._getLoggedIn().then(function(loggedIn) {
|
||||
this._showActions = loggedIn;
|
||||
}.bind(this));
|
||||
|
||||
window.addEventListener('resize', this._boundWindowResizeHandler);
|
||||
},
|
||||
|
||||
detached: function() {
|
||||
window.removeEventListener('resize', this._boundWindowResizeHandler);
|
||||
},
|
||||
|
||||
addDraft: function(opt_lineNum) {
|
||||
@@ -88,10 +71,6 @@
|
||||
return this.$.restAPI.getLoggedIn();
|
||||
},
|
||||
|
||||
_handleWindowResize: function(e) {
|
||||
this._heightChanged();
|
||||
},
|
||||
|
||||
_commentsChanged: function(changeRecord) {
|
||||
this._orderedComments = this._sortedComments(this.comments);
|
||||
},
|
||||
@@ -133,11 +112,6 @@
|
||||
}
|
||||
},
|
||||
|
||||
_handleCommentHeightChange: function(e) {
|
||||
e.stopPropagation();
|
||||
this._heightChanged();
|
||||
},
|
||||
|
||||
_handleCommentReply: function(e) {
|
||||
var comment = e.detail.comment;
|
||||
var quoteStr;
|
||||
@@ -153,7 +127,6 @@
|
||||
this.async(function() {
|
||||
var commentEl = this._commentElWithDraftID(reply.__draftID);
|
||||
commentEl.editing = true;
|
||||
this.async(this._heightChanged.bind(this), 1);
|
||||
}.bind(this), 1);
|
||||
},
|
||||
|
||||
@@ -166,7 +139,6 @@
|
||||
this.async(function() {
|
||||
var commentEl = this._commentElWithDraftID(reply.__draftID);
|
||||
commentEl.save();
|
||||
this.async(this._heightChanged.bind(this), 1);
|
||||
}.bind(this), 1);
|
||||
},
|
||||
|
||||
@@ -215,15 +187,6 @@
|
||||
this.fire('thread-discard');
|
||||
return;
|
||||
}
|
||||
this.async(this._heightChanged.bind(this), 1);
|
||||
},
|
||||
|
||||
_heightChanged: function() {
|
||||
var height = this.$.container.offsetHeight;
|
||||
if (height == this._lastHeight) { return; }
|
||||
|
||||
this.fire('height-change', {height: height}, {bubbles: false});
|
||||
this._lastHeight = height;
|
||||
},
|
||||
|
||||
_indexOf: function(comment, arr) {
|
||||
|
@@ -129,7 +129,6 @@ limitations under the License.
|
||||
disabled="{{disabled}}"
|
||||
rows="4"
|
||||
bind-value="{{_editDraft}}"
|
||||
on-keyup="_handleTextareaKeyup"
|
||||
on-keydown="_handleTextareaKeydown"></iron-autogrow-textarea>
|
||||
<gr-linked-text class="message"
|
||||
pre
|
||||
|
@@ -17,12 +17,6 @@
|
||||
Polymer({
|
||||
is: 'gr-diff-comment',
|
||||
|
||||
/**
|
||||
* Fired when the height of the comment changes.
|
||||
*
|
||||
* @event height-change
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when the Reply action is triggered.
|
||||
*
|
||||
@@ -75,10 +69,6 @@
|
||||
this.editing = this._editDraft.length == 0;
|
||||
},
|
||||
|
||||
attached: function() {
|
||||
this._heightChanged();
|
||||
},
|
||||
|
||||
save: function() {
|
||||
this.comment.message = this._editDraft;
|
||||
this.disabled = true;
|
||||
@@ -101,13 +91,6 @@
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_heightChanged: function() {
|
||||
this.async(function() {
|
||||
this.fire('height-change', {height: this.offsetHeight},
|
||||
{bubbles: false});
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_draftChanged: function(draft) {
|
||||
this.$.container.classList.toggle('draft', draft);
|
||||
},
|
||||
@@ -126,7 +109,6 @@
|
||||
if (this.comment && this.comment.id) {
|
||||
this.$$('.cancel').hidden = !editing;
|
||||
}
|
||||
this._heightChanged();
|
||||
},
|
||||
|
||||
_computeLinkToComment: function(comment) {
|
||||
@@ -137,12 +119,6 @@
|
||||
return draft == null || draft.trim() == '';
|
||||
},
|
||||
|
||||
_handleTextareaKeyup: function(e) {
|
||||
// TODO(andybons): This isn't always true, but I can't currently think
|
||||
// of a better metric.
|
||||
this._heightChanged();
|
||||
},
|
||||
|
||||
_handleTextareaKeydown: function(e) {
|
||||
if (e.keyCode == 27) { // 'esc'
|
||||
this._handleCancel(e);
|
||||
|
@@ -1,97 +0,0 @@
|
||||
<!--
|
||||
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="../gr-diff-comment-thread/gr-diff-comment-thread.html">
|
||||
|
||||
<dom-module id="gr-diff-side">
|
||||
<template>
|
||||
<style>
|
||||
:host,
|
||||
.container {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.lineNum:before,
|
||||
.code: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';
|
||||
}
|
||||
.lineNum {
|
||||
background-color: #eee;
|
||||
color: #666;
|
||||
padding: 0 .75em;
|
||||
text-align: right;
|
||||
}
|
||||
.canComment .lineNum {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.canComment .lineNum:hover {
|
||||
background-color: #ccc;
|
||||
}
|
||||
.lightHighlight {
|
||||
background-color: var(--light-highlight-color);
|
||||
}
|
||||
hl,
|
||||
.darkHighlight {
|
||||
background-color: var(--dark-highlight-color);
|
||||
}
|
||||
.br:after {
|
||||
/* Line feed */
|
||||
content: '\A';
|
||||
}
|
||||
.tab {
|
||||
display: inline-block;
|
||||
}
|
||||
.tab.withIndicator:before {
|
||||
color: #C62828;
|
||||
/* >> character */
|
||||
content: '\00BB';
|
||||
}
|
||||
.numbers,
|
||||
.content {
|
||||
white-space: pre;
|
||||
}
|
||||
.numbers .filler {
|
||||
background-color: #eee;
|
||||
}
|
||||
.contextControl {
|
||||
background-color: #fef;
|
||||
}
|
||||
.contextControl a:link,
|
||||
.contextControl a:visited {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
}
|
||||
.numbers .contextControl {
|
||||
padding: 0 .75em;
|
||||
text-align: right;
|
||||
}
|
||||
.content .contextControl {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<div class$="[[_computeContainerClass(canComment)]]">
|
||||
<div class="numbers" id="numbers"></div>
|
||||
<div class="content" id="content"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script src="gr-diff-side.js"></script>
|
||||
</dom-module>
|
@@ -1,612 +0,0 @@
|
||||
// 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 CharCode = {
|
||||
LESS_THAN: '<'.charCodeAt(0),
|
||||
GREATER_THAN: '>'.charCodeAt(0),
|
||||
AMPERSAND: '&'.charCodeAt(0),
|
||||
SEMICOLON: ';'.charCodeAt(0),
|
||||
};
|
||||
|
||||
var TAB_REGEX = /\t/g;
|
||||
|
||||
Polymer({
|
||||
is: 'gr-diff-side',
|
||||
|
||||
/**
|
||||
* Fired when an expand context control is clicked.
|
||||
*
|
||||
* @event expand-context
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when a thread's height is changed.
|
||||
*
|
||||
* @event thread-height-change
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when a draft should be added.
|
||||
*
|
||||
* @event add-draft
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when a thread is removed.
|
||||
*
|
||||
* @event remove-thread
|
||||
*/
|
||||
|
||||
properties: {
|
||||
canComment: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
content: {
|
||||
type: Array,
|
||||
notify: true,
|
||||
observer: '_contentChanged',
|
||||
},
|
||||
prefs: {
|
||||
type: Object,
|
||||
value: function() { return {}; },
|
||||
},
|
||||
changeNum: String,
|
||||
patchNum: String,
|
||||
path: String,
|
||||
projectConfig: {
|
||||
type: Object,
|
||||
observer: '_projectConfigChanged',
|
||||
},
|
||||
|
||||
_lineFeedHTML: {
|
||||
type: String,
|
||||
value: '<span class="style-scope gr-diff-side br"></span>',
|
||||
readOnly: true,
|
||||
},
|
||||
_highlightStartTag: {
|
||||
type: String,
|
||||
value: '<hl class="style-scope gr-diff-side">',
|
||||
readOnly: true,
|
||||
},
|
||||
_highlightEndTag: {
|
||||
type: String,
|
||||
value: '</hl>',
|
||||
readOnly: true,
|
||||
},
|
||||
_diffChunkLineNums: {
|
||||
type: Array,
|
||||
value: function() { return []; },
|
||||
},
|
||||
_commentThreadLineNums: {
|
||||
type: Array,
|
||||
value: function() { return []; },
|
||||
},
|
||||
_focusedLineNum: {
|
||||
type: Number,
|
||||
value: 1,
|
||||
},
|
||||
},
|
||||
|
||||
listeners: {
|
||||
'tap': '_tapHandler',
|
||||
},
|
||||
|
||||
observers: [
|
||||
'_prefsChanged(prefs.*)',
|
||||
],
|
||||
|
||||
rowInserted: function(index) {
|
||||
this.renderLineIndexRange(index, index);
|
||||
this._updateDOMIndices();
|
||||
this._updateJumpIndices();
|
||||
},
|
||||
|
||||
rowRemoved: function(index) {
|
||||
var removedEls = Polymer.dom(this.root).querySelectorAll(
|
||||
'[data-index="' + index + '"]');
|
||||
for (var i = 0; i < removedEls.length; i++) {
|
||||
removedEls[i].parentNode.removeChild(removedEls[i]);
|
||||
}
|
||||
this._updateDOMIndices();
|
||||
this._updateJumpIndices();
|
||||
},
|
||||
|
||||
rowUpdated: function(index) {
|
||||
var removedEls = Polymer.dom(this.root).querySelectorAll(
|
||||
'[data-index="' + index + '"]');
|
||||
for (var i = 0; i < removedEls.length; i++) {
|
||||
removedEls[i].parentNode.removeChild(removedEls[i]);
|
||||
}
|
||||
this.renderLineIndexRange(index, index);
|
||||
},
|
||||
|
||||
scrollToLine: function(lineNum) {
|
||||
if (isNaN(lineNum) || lineNum < 1) { return; }
|
||||
|
||||
var el = this.$$('.numbers .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);
|
||||
},
|
||||
|
||||
scrollToNextDiffChunk: function() {
|
||||
this._scrollToNextChunkOrThread(this._diffChunkLineNums);
|
||||
},
|
||||
|
||||
scrollToPreviousDiffChunk: function() {
|
||||
this._scrollToPreviousChunkOrThread(this._diffChunkLineNums);
|
||||
},
|
||||
|
||||
scrollToNextCommentThread: function() {
|
||||
this._scrollToNextChunkOrThread(this._commentThreadLineNums);
|
||||
},
|
||||
|
||||
scrollToPreviousCommentThread: function() {
|
||||
this._scrollToPreviousChunkOrThread(this._commentThreadLineNums);
|
||||
},
|
||||
|
||||
renderLineIndexRange: function(startIndex, endIndex) {
|
||||
this._render(this.content, startIndex, endIndex);
|
||||
},
|
||||
|
||||
hideElementsWithIndex: function(index) {
|
||||
var els = Polymer.dom(this.root).querySelectorAll(
|
||||
'[data-index="' + index + '"]');
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
els[i].setAttribute('hidden', true);
|
||||
}
|
||||
},
|
||||
|
||||
getRowHeight: function(index) {
|
||||
var row = this.content[index];
|
||||
// Filler elements should not be taken into account when determining
|
||||
// height calculations.
|
||||
if (row.type == 'FILLER') {
|
||||
return 0;
|
||||
}
|
||||
if (row.height != null) {
|
||||
return row.height;
|
||||
}
|
||||
|
||||
var selector = '[data-index="' + index + '"]';
|
||||
var els = Polymer.dom(this.root).querySelectorAll(selector);
|
||||
if (els.length != 2) {
|
||||
throw Error('Rows should only consist of two elements');
|
||||
}
|
||||
return Math.max(els[0].offsetHeight, els[1].offsetHeight);
|
||||
},
|
||||
|
||||
getRowNaturalHeight: function(index) {
|
||||
var contentEl = this.$$('.content [data-index="' + index + '"]');
|
||||
return contentEl.naturalHeight || contentEl.offsetHeight;
|
||||
},
|
||||
|
||||
setRowNaturalHeight: function(index) {
|
||||
var lineEl = this.$$('.numbers [data-index="' + index + '"]');
|
||||
var contentEl = this.$$('.content [data-index="' + index + '"]');
|
||||
contentEl.style.height = null;
|
||||
var height = contentEl.offsetHeight;
|
||||
lineEl.style.height = height + 'px';
|
||||
this.content[index].height = height;
|
||||
return height;
|
||||
},
|
||||
|
||||
setRowHeight: function(index, height) {
|
||||
var selector = '[data-index="' + index + '"]';
|
||||
var els = Polymer.dom(this.root).querySelectorAll(selector);
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
els[i].style.height = height + 'px';
|
||||
}
|
||||
this.content[index].height = height;
|
||||
},
|
||||
|
||||
_scrollToNextChunkOrThread: function(lineNums) {
|
||||
for (var i = 0; i < lineNums.length; i++) {
|
||||
if (lineNums[i] > this._focusedLineNum) {
|
||||
this._focusedLineNum = lineNums[i];
|
||||
this.scrollToLine(this._focusedLineNum);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_scrollToPreviousChunkOrThread: function(lineNums) {
|
||||
for (var i = lineNums.length - 1; i >= 0; i--) {
|
||||
if (this._focusedLineNum > lineNums[i]) {
|
||||
this._focusedLineNum = lineNums[i];
|
||||
this.scrollToLine(this._focusedLineNum);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_updateJumpIndices: function() {
|
||||
this._commentThreadLineNums = [];
|
||||
this._diffChunkLineNums = [];
|
||||
var inHighlight = false;
|
||||
for (var i = 0; i < this.content.length; i++) {
|
||||
switch (this.content[i].type) {
|
||||
case 'COMMENT_THREAD':
|
||||
this._commentThreadLineNums.push(
|
||||
this.content[i].comments[0].line);
|
||||
break;
|
||||
case 'CODE':
|
||||
// Only grab the first line of the highlighted chunk.
|
||||
if (!inHighlight && this.content[i].highlight) {
|
||||
this._diffChunkLineNums.push(this.content[i].lineNum);
|
||||
inHighlight = true;
|
||||
} else if (!this.content[i].highlight) {
|
||||
inHighlight = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_updateDOMIndices: function() {
|
||||
// There is no way to select elements with a data-index greater than a
|
||||
// given value. For now, just update all DOM elements.
|
||||
var lineEls = Polymer.dom(this.root).querySelectorAll(
|
||||
'.numbers [data-index]');
|
||||
var contentEls = Polymer.dom(this.root).querySelectorAll(
|
||||
'.content [data-index]');
|
||||
if (lineEls.length != contentEls.length) {
|
||||
throw Error(
|
||||
'There must be the same number of line and content elements');
|
||||
}
|
||||
var index = 0;
|
||||
for (var i = 0; i < this.content.length; i++) {
|
||||
if (this.content[i].hidden) { continue; }
|
||||
|
||||
lineEls[index].setAttribute('data-index', i);
|
||||
contentEls[index].setAttribute('data-index', i);
|
||||
index++;
|
||||
}
|
||||
},
|
||||
|
||||
_prefsChanged: function(changeRecord) {
|
||||
var prefs = changeRecord.base;
|
||||
this.$.content.style.width = prefs.line_length + 'ch';
|
||||
},
|
||||
|
||||
_projectConfigChanged: function(projectConfig) {
|
||||
var threadEls =
|
||||
Polymer.dom(this.root).querySelectorAll('gr-diff-comment-thread');
|
||||
for (var i = 0; i < threadEls.length; i++) {
|
||||
threadEls[i].projectConfig = projectConfig;
|
||||
}
|
||||
},
|
||||
|
||||
_contentChanged: function(diff) {
|
||||
this._clearChildren(this.$.numbers);
|
||||
this._clearChildren(this.$.content);
|
||||
this._render(diff, 0, diff.length - 1);
|
||||
this._updateJumpIndices();
|
||||
},
|
||||
|
||||
_computeContainerClass: function(canComment) {
|
||||
return 'container' + (canComment ? ' canComment' : '');
|
||||
},
|
||||
|
||||
_tapHandler: function(e) {
|
||||
var lineEl = Polymer.dom(e).rootTarget;
|
||||
if (!this.canComment || !lineEl.classList.contains('lineNum')) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
var index = parseInt(lineEl.getAttribute('data-index'), 10);
|
||||
var line = parseInt(lineEl.getAttribute('data-line-num'), 10);
|
||||
this.fire('add-draft', {
|
||||
index: index,
|
||||
line: line
|
||||
}, {bubbles: false});
|
||||
},
|
||||
|
||||
_clearChildren: function(el) {
|
||||
while (el.firstChild) {
|
||||
el.removeChild(el.firstChild);
|
||||
}
|
||||
},
|
||||
|
||||
_handleContextControlClick: function(context, e) {
|
||||
e.preventDefault();
|
||||
this.fire('expand-context', {context: context}, {bubbles: false});
|
||||
},
|
||||
|
||||
_render: function(diff, startIndex, endIndex) {
|
||||
var beforeLineEl;
|
||||
var beforeContentEl;
|
||||
if (endIndex != diff.length - 1) {
|
||||
beforeLineEl = this.$$('.numbers [data-index="' + endIndex + '"]');
|
||||
beforeContentEl = this.$$('.content [data-index="' + endIndex + '"]');
|
||||
if (!beforeLineEl && !beforeContentEl) {
|
||||
// `endIndex` may be present within the model, but not in the DOM.
|
||||
// Insert it before its successive element.
|
||||
beforeLineEl = this.$$(
|
||||
'.numbers [data-index="' + (endIndex + 1) + '"]');
|
||||
beforeContentEl = this.$$(
|
||||
'.content [data-index="' + (endIndex + 1) + '"]');
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = startIndex; i <= endIndex; i++) {
|
||||
if (diff[i].hidden) { continue; }
|
||||
|
||||
switch (diff[i].type) {
|
||||
case 'CODE':
|
||||
this._renderCode(diff[i], i, beforeLineEl, beforeContentEl);
|
||||
break;
|
||||
case 'FILLER':
|
||||
this._renderFiller(diff[i], i, beforeLineEl, beforeContentEl);
|
||||
break;
|
||||
case 'CONTEXT_CONTROL':
|
||||
this._renderContextControl(diff[i], i, beforeLineEl,
|
||||
beforeContentEl);
|
||||
break;
|
||||
case 'COMMENT_THREAD':
|
||||
this._renderCommentThread(diff[i], i, beforeLineEl,
|
||||
beforeContentEl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_handleCommentThreadHeightChange: function(e) {
|
||||
var threadEl = Polymer.dom(e).rootTarget;
|
||||
var index = parseInt(threadEl.getAttribute('data-index'), 10);
|
||||
this.content[index].height = e.detail.height;
|
||||
var lineEl = this.$$('.numbers [data-index="' + index + '"]');
|
||||
lineEl.style.height = e.detail.height + 'px';
|
||||
this.fire('thread-height-change', {
|
||||
index: index,
|
||||
height: e.detail.height,
|
||||
}, {bubbles: false});
|
||||
},
|
||||
|
||||
_handleCommentThreadDiscard: function(e) {
|
||||
var threadEl = Polymer.dom(e).rootTarget;
|
||||
var index = parseInt(threadEl.getAttribute('data-index'), 10);
|
||||
this.fire('remove-thread', {index: index}, {bubbles: false});
|
||||
},
|
||||
|
||||
_renderCommentThread: function(thread, index, beforeLineEl,
|
||||
beforeContentEl) {
|
||||
var lineEl = this._createElement('div', 'commentThread');
|
||||
lineEl.classList.add('filler');
|
||||
lineEl.setAttribute('data-index', index);
|
||||
var threadEl = document.createElement('gr-diff-comment-thread');
|
||||
threadEl.addEventListener('height-change',
|
||||
this._handleCommentThreadHeightChange.bind(this));
|
||||
threadEl.addEventListener('thread-discard',
|
||||
this._handleCommentThreadDiscard.bind(this));
|
||||
threadEl.setAttribute('data-index', index);
|
||||
threadEl.changeNum = this.changeNum;
|
||||
threadEl.patchNum = thread.patchNum || this.patchNum;
|
||||
threadEl.path = this.path;
|
||||
threadEl.comments = thread.comments;
|
||||
threadEl.projectConfig = this.projectConfig;
|
||||
|
||||
this.$.numbers.insertBefore(lineEl, beforeLineEl);
|
||||
this.$.content.insertBefore(threadEl, beforeContentEl);
|
||||
},
|
||||
|
||||
_renderContextControl: function(control, index, beforeLineEl,
|
||||
beforeContentEl) {
|
||||
var lineEl = this._createElement('div', 'contextControl');
|
||||
lineEl.setAttribute('data-index', index);
|
||||
lineEl.textContent = '@@';
|
||||
var contentEl = this._createElement('div', 'contextControl');
|
||||
contentEl.setAttribute('data-index', index);
|
||||
var a = this._createElement('a');
|
||||
a.href = '#';
|
||||
a.textContent = 'Show ' + control.numLines + ' common ' +
|
||||
(control.numLines == 1 ? 'line' : 'lines') + '...';
|
||||
a.addEventListener('click',
|
||||
this._handleContextControlClick.bind(this, control));
|
||||
contentEl.appendChild(a);
|
||||
|
||||
this.$.numbers.insertBefore(lineEl, beforeLineEl);
|
||||
this.$.content.insertBefore(contentEl, beforeContentEl);
|
||||
},
|
||||
|
||||
_renderFiller: function(filler, index, beforeLineEl, beforeContentEl) {
|
||||
var lineFillerEl = this._createElement('div', 'filler');
|
||||
lineFillerEl.setAttribute('data-index', index);
|
||||
var fillerEl = this._createElement('div', 'filler');
|
||||
fillerEl.setAttribute('data-index', index);
|
||||
var numLines = filler.numLines || 1;
|
||||
|
||||
lineFillerEl.textContent = '\n'.repeat(numLines);
|
||||
for (var i = 0; i < numLines; i++) {
|
||||
var newlineEl = this._createElement('span', 'br');
|
||||
fillerEl.appendChild(newlineEl);
|
||||
}
|
||||
|
||||
this.$.numbers.insertBefore(lineFillerEl, beforeLineEl);
|
||||
this.$.content.insertBefore(fillerEl, beforeContentEl);
|
||||
},
|
||||
|
||||
_renderCode: function(code, index, beforeLineEl, beforeContentEl) {
|
||||
var lineNumEl = this._createElement('div', 'lineNum');
|
||||
lineNumEl.setAttribute('data-line-num', code.lineNum);
|
||||
lineNumEl.setAttribute('data-index', index);
|
||||
var numLines = code.numLines || 1;
|
||||
lineNumEl.textContent = code.lineNum + '\n'.repeat(numLines);
|
||||
|
||||
var contentEl = this._createElement('div', 'code');
|
||||
contentEl.setAttribute('data-line-num', code.lineNum);
|
||||
contentEl.setAttribute('data-index', index);
|
||||
|
||||
if (code.highlight) {
|
||||
contentEl.classList.add(code.intraline.length > 0 ?
|
||||
'lightHighlight' : 'darkHighlight');
|
||||
}
|
||||
|
||||
var html = util.escapeHTML(code.content);
|
||||
if (code.highlight && code.intraline.length > 0) {
|
||||
html = this._addIntralineHighlights(code.content, html,
|
||||
code.intraline);
|
||||
}
|
||||
if (numLines > 1) {
|
||||
html = this._addNewLines(code.content, html, numLines);
|
||||
}
|
||||
html = this._addTabWrappers(code.content, html);
|
||||
|
||||
// If the html is equivalent to the text then it didn't get highlighted
|
||||
// or escaped. Use textContent which is faster than innerHTML.
|
||||
if (code.content == html) {
|
||||
contentEl.textContent = code.content;
|
||||
} else {
|
||||
contentEl.innerHTML = html;
|
||||
}
|
||||
|
||||
this.$.numbers.insertBefore(lineNumEl, beforeLineEl);
|
||||
this.$.content.insertBefore(contentEl, beforeContentEl);
|
||||
},
|
||||
|
||||
// Advance `index` by the appropriate number of characters that would
|
||||
// represent one source code character and return that index. For
|
||||
// example, for source code '<span>' the escaped html string is
|
||||
// '<span>'. Advancing from index 0 on the prior html string would
|
||||
// return 4, since < maps to one source code character ('<').
|
||||
_advanceChar: function(html, index) {
|
||||
// Any tags don't count as characters
|
||||
while (index < html.length &&
|
||||
html.charCodeAt(index) == CharCode.LESS_THAN) {
|
||||
while (index < html.length &&
|
||||
html.charCodeAt(index) != CharCode.GREATER_THAN) {
|
||||
index++;
|
||||
}
|
||||
index++; // skip the ">" itself
|
||||
}
|
||||
// An HTML entity (e.g., <) counts as one character.
|
||||
if (index < html.length &&
|
||||
html.charCodeAt(index) == CharCode.AMPERSAND) {
|
||||
while (index < html.length &&
|
||||
html.charCodeAt(index) != CharCode.SEMICOLON) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return index + 1;
|
||||
},
|
||||
|
||||
_addIntralineHighlights: function(content, html, highlights) {
|
||||
var startTag = this._highlightStartTag;
|
||||
var endTag = this._highlightEndTag;
|
||||
|
||||
for (var i = 0; i < highlights.length; i++) {
|
||||
var hl = highlights[i];
|
||||
|
||||
var htmlStartIndex = 0;
|
||||
for (var j = 0; j < hl.startIndex; j++) {
|
||||
htmlStartIndex = this._advanceChar(html, htmlStartIndex);
|
||||
}
|
||||
|
||||
var htmlEndIndex = 0;
|
||||
if (hl.endIndex != null) {
|
||||
for (var j = 0; j < hl.endIndex; j++) {
|
||||
htmlEndIndex = this._advanceChar(html, htmlEndIndex);
|
||||
}
|
||||
} else {
|
||||
// If endIndex isn't present, continue to the end of the line.
|
||||
htmlEndIndex = html.length;
|
||||
}
|
||||
// The start and end indices could be the same if a highlight is meant
|
||||
// to start at the end of a line and continue onto the next one.
|
||||
// Ignore it.
|
||||
if (htmlStartIndex != htmlEndIndex) {
|
||||
html = html.slice(0, htmlStartIndex) + startTag +
|
||||
html.slice(htmlStartIndex, htmlEndIndex) + endTag +
|
||||
html.slice(htmlEndIndex);
|
||||
}
|
||||
}
|
||||
return html;
|
||||
},
|
||||
|
||||
_addNewLines: function(content, html, numLines) {
|
||||
var htmlIndex = 0;
|
||||
var indices = [];
|
||||
var numChars = 0;
|
||||
for (var i = 0; i < content.length; i++) {
|
||||
if (numChars > 0 && numChars % this.prefs.line_length == 0) {
|
||||
indices.push(htmlIndex);
|
||||
}
|
||||
htmlIndex = this._advanceChar(html, htmlIndex);
|
||||
if (content[i] == '\t') {
|
||||
numChars += this.prefs.tab_size;
|
||||
} else {
|
||||
numChars++;
|
||||
}
|
||||
}
|
||||
var result = html;
|
||||
var linesLeft = numLines;
|
||||
// Since the result string is being altered in place, start from the end
|
||||
// of the string so that the insertion indices are not affected as the
|
||||
// result string changes.
|
||||
for (var i = indices.length - 1; i >= 0; i--) {
|
||||
result = result.slice(0, indices[i]) + this._lineFeedHTML +
|
||||
result.slice(indices[i]);
|
||||
linesLeft--;
|
||||
}
|
||||
// numLines is the total number of lines this code block should take up.
|
||||
// Fill in the remaining ones.
|
||||
for (var i = 0; i < linesLeft; i++) {
|
||||
result += this._lineFeedHTML;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
_addTabWrappers: function(content, html) {
|
||||
// TODO(andybons): CSS tab-size is not supported in IE.
|
||||
// Force this to be a number to prevent arbitrary injection.
|
||||
var tabSize = +this.prefs.tab_size;
|
||||
var htmlStr = '<span class="style-scope gr-diff-side tab ' +
|
||||
(this.prefs.show_tabs ? 'withIndicator" ' : '" ') +
|
||||
'style="tab-size:' + tabSize + ';' +
|
||||
'-moz-tab-size:' + tabSize + ';">\t</span>';
|
||||
return html.replace(TAB_REGEX, htmlStr);
|
||||
},
|
||||
|
||||
_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.classList.add('style-scope', 'gr-diff-side');
|
||||
if (!!className) {
|
||||
el.classList.add(className);
|
||||
}
|
||||
return el;
|
||||
},
|
||||
});
|
||||
})();
|
@@ -1,300 +0,0 @@
|
||||
<!DOCTYPE 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.
|
||||
-->
|
||||
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-diff-side</title>
|
||||
|
||||
<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
|
||||
<script src="../../../bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../scripts/util.js"></script>
|
||||
|
||||
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
|
||||
<link rel="import" href="gr-diff-side.html">
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
<gr-diff-side></gr-diff-side>
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-diff-side tests', function() {
|
||||
var element;
|
||||
|
||||
function isVisibleInWindow(el) {
|
||||
var rect = el.getBoundingClientRect();
|
||||
return rect.top >= 0 && rect.left >= 0 &&
|
||||
rect.bottom <= window.innerHeight && rect.right <= window.innerWidth;
|
||||
}
|
||||
|
||||
setup(function() {
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
test('comments', function() {
|
||||
assert.isFalse(element.$$('.container').classList.contains('canComment'));
|
||||
element.canComment = true;
|
||||
assert.isTrue(element.$$('.container').classList.contains('canComment'));
|
||||
// TODO(andybons): Check for comment creation events firing/not firing
|
||||
// when implemented.
|
||||
});
|
||||
|
||||
test('scroll to line', function() {
|
||||
var content = [];
|
||||
for (var i = 0; i < 300; i++) {
|
||||
content.push({
|
||||
type: 'CODE',
|
||||
content: 'All work and no play makes Jack a dull boy',
|
||||
numLines: 1,
|
||||
lineNum: i + 1,
|
||||
highlight: false,
|
||||
intraline: [],
|
||||
});
|
||||
}
|
||||
element.content = content;
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
element.scrollToLine(-12849);
|
||||
assert.equal(window.scrollY, 0);
|
||||
element.scrollToLine('sup');
|
||||
assert.equal(window.scrollY, 0);
|
||||
var lineEl = element.$$('.numbers .lineNum[data-line-num="150"]');
|
||||
assert.ok(lineEl);
|
||||
element.scrollToLine(150);
|
||||
assert.isAbove(window.scrollY, 0);
|
||||
assert.isTrue(isVisibleInWindow(lineEl), 'element should be visible');
|
||||
});
|
||||
|
||||
test('intraline highlights', function() {
|
||||
var content = ' <gr-linked-text content="' +
|
||||
'[[_computeCurrentRevisionMessage(change)]]"></gr-linked-text>';
|
||||
var html = util.escapeHTML(content);
|
||||
var highlights = [
|
||||
{startIndex: 0, endIndex: 33},
|
||||
{startIndex: 75},
|
||||
];
|
||||
assert.equal(
|
||||
content.slice(highlights[0].startIndex, highlights[0].endIndex),
|
||||
' <gr-linked-text content="');
|
||||
assert.equal(content.slice(highlights[1].startIndex),
|
||||
'"></gr-linked-text>');
|
||||
var result = element._addIntralineHighlights(content, html, highlights);
|
||||
var expected = element._highlightStartTag +
|
||||
' <gr-linked-text content="' +
|
||||
element._highlightEndTag +
|
||||
'[[_computeCurrentRevisionMessage(change)]]' +
|
||||
element._highlightStartTag +
|
||||
'"></gr-linked-text>' +
|
||||
element._highlightEndTag;
|
||||
assert.equal(result, expected);
|
||||
});
|
||||
|
||||
test('newlines', function() {
|
||||
element.prefs = {
|
||||
line_length: 80,
|
||||
tab_size: 4,
|
||||
};
|
||||
|
||||
element.content = [{
|
||||
type: 'CODE',
|
||||
content: 'A'.repeat(50),
|
||||
numLines: 1,
|
||||
lineNum: 1,
|
||||
highlight: false,
|
||||
intraline: [],
|
||||
}];
|
||||
|
||||
var lineEl = element.$$('.numbers .lineNum[data-line-num="1"]');
|
||||
assert.ok(lineEl);
|
||||
var contentEl = element.$$('.content .code[data-line-num="1"]');
|
||||
assert.ok(contentEl);
|
||||
assert.equal(contentEl.innerHTML, 'A'.repeat(50));
|
||||
|
||||
element.content = [{
|
||||
type: 'CODE',
|
||||
content: 'A'.repeat(100),
|
||||
numLines: 2,
|
||||
lineNum: 1,
|
||||
highlight: false,
|
||||
intraline: [],
|
||||
}];
|
||||
|
||||
lineEl = element.$$('.numbers .lineNum[data-line-num="1"]');
|
||||
assert.ok(lineEl);
|
||||
contentEl = element.$$('.content .code[data-line-num="1"]');
|
||||
assert.ok(contentEl);
|
||||
assert.equal(contentEl.innerHTML,
|
||||
'A'.repeat(80) + element._lineFeedHTML +
|
||||
'A'.repeat(20) + element._lineFeedHTML);
|
||||
});
|
||||
|
||||
test('tabs', function(done) {
|
||||
element.prefs = {
|
||||
line_length: 80,
|
||||
tab_size: 4,
|
||||
show_tabs: true,
|
||||
};
|
||||
|
||||
element.content = [{
|
||||
type: 'CODE',
|
||||
content: 'A'.repeat(50) + '\t' + 'A'.repeat(50),
|
||||
numLines: 2,
|
||||
lineNum: 1,
|
||||
highlight: false,
|
||||
intraline: [],
|
||||
}];
|
||||
|
||||
var lineEl = element.$$('.numbers .lineNum[data-line-num="1"]');
|
||||
assert.ok(lineEl);
|
||||
var contentEl = element.$$('.content .code[data-line-num="1"]');
|
||||
assert.ok(contentEl);
|
||||
var spanEl = contentEl.childNodes[1];
|
||||
assert.equal(spanEl.tagName, 'SPAN');
|
||||
assert.isTrue(spanEl.classList.contains(
|
||||
'style-scope', 'gr-diff-side', 'tab', 'withIndicator'));
|
||||
|
||||
element.prefs.show_tabs = false;
|
||||
element.content = [{
|
||||
type: 'CODE',
|
||||
content: 'A'.repeat(50) + '\t' + 'A'.repeat(50),
|
||||
numLines: 2,
|
||||
lineNum: 1,
|
||||
highlight: false,
|
||||
intraline: [],
|
||||
}];
|
||||
contentEl = element.$$('.content .code[data-line-num="1"]');
|
||||
assert.ok(contentEl);
|
||||
spanEl = contentEl.childNodes[1];
|
||||
assert.equal(spanEl.tagName, 'SPAN');
|
||||
assert.isTrue(spanEl.classList.contains(
|
||||
'style-scope', 'gr-diff-side', 'tab'));
|
||||
|
||||
var alertStub = sinon.stub(window, 'alert');
|
||||
element.prefs.tab_size =
|
||||
'"><img src="/" onerror="alert(1);"><span class="';
|
||||
element.content = [{
|
||||
type: 'CODE',
|
||||
content: '\t',
|
||||
numLines: 1,
|
||||
lineNum: 1,
|
||||
highlight: false,
|
||||
intraline: [],
|
||||
}];
|
||||
flush(function() {
|
||||
assert.isFalse(alertStub.called);
|
||||
alertStub.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('diff context', function() {
|
||||
var content = [
|
||||
{type: 'CODE', hidden: true, content: '<!DOCTYPE html>'},
|
||||
{type: 'CODE', hidden: true, content: '<meta charset="utf-8">'},
|
||||
{type: 'CODE', hidden: true, content: '<title>My great page</title>'},
|
||||
{type: 'CODE', hidden: true, content: '<style>'},
|
||||
{type: 'CODE', hidden: true, content: ' *,'},
|
||||
{type: 'CODE', hidden: true, content: ' *:before,'},
|
||||
{type: 'CODE', hidden: true, content: ' *:after {'},
|
||||
{type: 'CODE', hidden: true, content: ' box-sizing: border-box;'},
|
||||
{type: 'CONTEXT_CONTROL', numLines: 8, start: 0, end: 8},
|
||||
{type: 'CODE', hidden: false, content: ' }'},
|
||||
];
|
||||
element.content = content;
|
||||
|
||||
// Only the context elements and the following code line elements should
|
||||
// be present in the DOM.
|
||||
var contextEls =
|
||||
Polymer.dom(element.root).querySelectorAll('.contextControl');
|
||||
assert.equal(contextEls.length, 2);
|
||||
var codeLineEls =
|
||||
Polymer.dom(element.root).querySelectorAll('.lineNum, .code');
|
||||
assert.equal(codeLineEls.length, 2);
|
||||
|
||||
for (var i = 0; i <= 8; i++) {
|
||||
element.content[i].hidden = false;
|
||||
}
|
||||
element.renderLineIndexRange(0, 8);
|
||||
element.hideElementsWithIndex(8);
|
||||
|
||||
contextEls =
|
||||
Polymer.dom(element.root).querySelectorAll('.contextControl');
|
||||
for (var i = 0; i < contextEls.length; i++) {
|
||||
assert.isTrue(contextEls[i].hasAttribute('hidden'));
|
||||
}
|
||||
|
||||
codeLineEls =
|
||||
Polymer.dom(element.root).querySelectorAll('.lineNum, .code');
|
||||
|
||||
// Nine lines should now be present in the DOM.
|
||||
assert.equal(codeLineEls.length, 9 * 2);
|
||||
});
|
||||
|
||||
test('tap line to add a draft', function() {
|
||||
var numAddDraftEvents = 0;
|
||||
sinon.stub(element, 'fire', function(eventName) {
|
||||
if (eventName == 'add-draft') {
|
||||
numAddDraftEvents++;
|
||||
}
|
||||
});
|
||||
element.content = [{type: 'CODE', content: '<!DOCTYPE html>'}];
|
||||
element.canComment = false;
|
||||
flushAsynchronousOperations();
|
||||
|
||||
var lineEl = element.$$('.lineNum');
|
||||
assert.ok(lineEl);
|
||||
MockInteractions.tap(lineEl);
|
||||
assert.equal(numAddDraftEvents, 0);
|
||||
|
||||
element.canComment = true;
|
||||
MockInteractions.tap(lineEl);
|
||||
assert.equal(numAddDraftEvents, 1);
|
||||
});
|
||||
|
||||
test('jump to diff chunk/thread', function() {
|
||||
element.content = [
|
||||
{type: 'CODE', content: '', intraline: [], lineNum: 1, highlight: true},
|
||||
{type: 'CODE', content: '', intraline: [], lineNum: 2, highlight: true},
|
||||
{type: 'CODE', content: '', intraline: [], lineNum: 3 },
|
||||
{type: 'CODE', content: '', intraline: [], lineNum: 4 },
|
||||
{type: 'COMMENT_THREAD', comments: [ { line: 4 }]},
|
||||
{type: 'CODE', content: '', intraline: [], lineNum: 5 },
|
||||
{type: 'CODE', content: '', intraline: [], lineNum: 6, highlight: true},
|
||||
{type: 'CODE', content: '', intraline: [], lineNum: 7, highlight: true},
|
||||
{type: 'CODE', content: '', intraline: [], lineNum: 8 },
|
||||
{type: 'COMMENT_THREAD', comments: [ { line: 8 }]},
|
||||
{type: 'CODE', content: '', intraline: [], lineNum: 9 },
|
||||
{type: 'CODE', content: '', intraline: [], lineNum: 10,
|
||||
highlight: true},
|
||||
];
|
||||
|
||||
var scrollToLineStub = sinon.stub(element, 'scrollToLine');
|
||||
element.scrollToNextDiffChunk();
|
||||
assert.isTrue(scrollToLineStub.lastCall.calledWithExactly(6));
|
||||
element.scrollToPreviousDiffChunk();
|
||||
assert.isTrue(scrollToLineStub.lastCall.calledWithExactly(1));
|
||||
element.scrollToNextCommentThread();
|
||||
assert.isTrue(scrollToLineStub.lastCall.calledWithExactly(4));
|
||||
element.scrollToNextCommentThread();
|
||||
assert.isTrue(scrollToLineStub.lastCall.calledWithExactly(8));
|
||||
element.scrollToPreviousDiffChunk();
|
||||
assert.isTrue(scrollToLineStub.lastCall.calledWithExactly(6));
|
||||
|
||||
scrollToLineStub.restore();
|
||||
});
|
||||
});
|
||||
</script>
|
@@ -162,7 +162,6 @@ limitations under the License.
|
||||
</h3>
|
||||
<gr-diff id="diff"
|
||||
change-num="[[_changeNum]]"
|
||||
prefs="{{prefs}}"
|
||||
patch-range="[[_patchRange]]"
|
||||
path="[[_path]]"
|
||||
project-config="[[_projectConfig]]"
|
||||
|
@@ -26,10 +26,6 @@
|
||||
*/
|
||||
|
||||
properties: {
|
||||
prefs: {
|
||||
type: Object,
|
||||
notify: true,
|
||||
},
|
||||
/**
|
||||
* URL params passed from the router.
|
||||
*/
|
||||
|
@@ -32,7 +32,7 @@
|
||||
GrDiffBuilder.TAB_REGEX = /\t/g;
|
||||
|
||||
GrDiffBuilder.LINE_FEED_HTML =
|
||||
'<span class="style-scope gr-new-diff br"></span>';
|
||||
'<span class="style-scope gr-diff br"></span>';
|
||||
|
||||
GrDiffBuilder.GroupType = {
|
||||
ADDED: 'b',
|
||||
@@ -509,7 +509,7 @@
|
||||
|
||||
GrDiffBuilder.prototype._addIntralineHighlights = function(content, html,
|
||||
highlights) {
|
||||
var START_TAG = '<hl class="style-scope gr-new-diff">';
|
||||
var START_TAG = '<hl class="style-scope gr-diff">';
|
||||
var END_TAG = '</hl>';
|
||||
|
||||
for (var i = 0; i < highlights.length; i++) {
|
||||
@@ -549,7 +549,7 @@
|
||||
throw Error('Invalid tab size from preferences.');
|
||||
}
|
||||
|
||||
var str = '<span class="style-scope gr-new-diff tab ';
|
||||
var str = '<span class="style-scope gr-diff tab ';
|
||||
if (showTabs) {
|
||||
str += 'withIndicator';
|
||||
}
|
||||
@@ -569,7 +569,7 @@
|
||||
// Since the Polymer DOM utility functions (which would do this
|
||||
// automatically) are not being used for performance reasons, this is
|
||||
// done manually.
|
||||
el.classList.add('style-scope', 'gr-new-diff');
|
||||
el.classList.add('style-scope', 'gr-diff');
|
||||
if (!!className) {
|
||||
el.classList.add(className);
|
||||
}
|
@@ -20,13 +20,19 @@ limitations under the License.
|
||||
<link rel="import" href="../../shared/gr-request/gr-request.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
|
||||
<link rel="import" href="../gr-diff-comment-thread/gr-diff-comment-thread.html">
|
||||
<link rel="import" href="../gr-diff-preferences/gr-diff-preferences.html">
|
||||
<link rel="import" href="../gr-diff-side/gr-diff-side.html">
|
||||
<link rel="import" href="../gr-patch-range-select/gr-patch-range-select.html">
|
||||
|
||||
<dom-module id="gr-diff">
|
||||
<template>
|
||||
<style>
|
||||
:host {
|
||||
--light-remove-highlight-color: #fee;
|
||||
--dark-remove-highlight-color: #ffd4d4;
|
||||
--light-add-highlight-color: #efe;
|
||||
--dark-add-highlight-color: #d4ffd4;
|
||||
}
|
||||
.loading {
|
||||
padding: 0 var(--default-horizontal-margin) 1em;
|
||||
color: #666;
|
||||
@@ -45,16 +51,99 @@ limitations under the License.
|
||||
display: flex;
|
||||
font: 12px var(--monospace-font-family);
|
||||
overflow-x: auto;
|
||||
will-change: transform;
|
||||
}
|
||||
gr-diff-side:first-of-type {
|
||||
--light-highlight-color: #fee;
|
||||
--dark-highlight-color: #ffd4d4;
|
||||
}
|
||||
gr-diff-side:last-of-type {
|
||||
--light-highlight-color: #efe;
|
||||
--dark-highlight-color: #d4ffd4;
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-right: 1px solid #ddd;
|
||||
}
|
||||
.section {
|
||||
background-color: #eee;
|
||||
}
|
||||
.blank,
|
||||
.content {
|
||||
background-color: #fff;
|
||||
}
|
||||
.lineNum,
|
||||
.content {
|
||||
vertical-align: top;
|
||||
white-space: pre;
|
||||
}
|
||||
.contextLineNum:before,
|
||||
.lineNum:before {
|
||||
display: inline-block;
|
||||
color: #666;
|
||||
content: attr(data-value);
|
||||
padding: 0 .75em;
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
}
|
||||
.canComment .lineNum[data-value] {
|
||||
cursor: pointer;
|
||||
}
|
||||
.canComment .lineNum[data-value]:before {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.canComment .lineNum[data-value]:hover:before {
|
||||
background-color: #ccc;
|
||||
}
|
||||
.canComment .lineNum[data-value="FILE"]:before {
|
||||
content: 'File';
|
||||
}
|
||||
.content {
|
||||
overflow: hidden;
|
||||
min-width: var(--content-width, 80ch);
|
||||
}
|
||||
.content.left {
|
||||
-webkit-user-select: var(--left-user-select, text);
|
||||
-moz-user-select: var(--left-user-select, text);
|
||||
-ms-user-select: var(--left-user-select, text);
|
||||
user-select: var(--left-user-select, text);
|
||||
}
|
||||
.content.right {
|
||||
-webkit-user-select: var(--right-user-select, text);
|
||||
-moz-user-select: var(--right-user-select, text);
|
||||
-ms-user-select: var(--right-user-select, text);
|
||||
user-select: var(--right-user-select, text);
|
||||
}
|
||||
.content.add hl,
|
||||
.content.add.darkHighlight {
|
||||
background-color: var(--dark-add-highlight-color);
|
||||
}
|
||||
.content.add.lightHighlight {
|
||||
background-color: var(--light-add-highlight-color);
|
||||
}
|
||||
.content.remove hl,
|
||||
.content.remove.darkHighlight {
|
||||
background-color: var(--dark-remove-highlight-color);
|
||||
}
|
||||
.content.remove.lightHighlight {
|
||||
background-color: var(--light-remove-highlight-color);
|
||||
}
|
||||
.contextControl {
|
||||
color: #849;
|
||||
background-color: #fef;
|
||||
}
|
||||
.contextControl gr-button {
|
||||
display: block;
|
||||
font-family: var(--monospace-font-family);
|
||||
text-decoration: none;
|
||||
}
|
||||
.contextControl td:not(.lineNum) {
|
||||
text-align: center;
|
||||
}
|
||||
.br:after {
|
||||
/* Line feed */
|
||||
content: '\A';
|
||||
}
|
||||
.tab {
|
||||
display: inline-block;
|
||||
}
|
||||
.tab.withIndicator:before {
|
||||
color: #C62828;
|
||||
/* >> character */
|
||||
content: '\00BB';
|
||||
}
|
||||
</style>
|
||||
<div class="loading" hidden$="[[!_loading]]">Loading...</div>
|
||||
<div hidden$="[[_loading]]" hidden>
|
||||
@@ -67,44 +156,29 @@ limitations under the License.
|
||||
<gr-button link
|
||||
class="prefsButton"
|
||||
on-tap="_handlePrefsTap"
|
||||
hidden$="[[!prefs]]"
|
||||
hidden$="[[_computePrefsButtonHidden(_prefs, _loggedIn)]]"
|
||||
hidden>Diff View Preferences</gr-button>
|
||||
</div>
|
||||
<gr-overlay id="prefsOverlay" with-backdrop>
|
||||
<gr-diff-preferences
|
||||
prefs="{{prefs}}"
|
||||
prefs="{{_prefs}}"
|
||||
on-save="_handlePrefsSave"
|
||||
on-cancel="_handlePrefsCancel"></gr-diff-preferences>
|
||||
</gr-overlay>
|
||||
|
||||
<div class="diffContainer">
|
||||
<gr-diff-side id="leftDiff"
|
||||
change-num="[[changeNum]]"
|
||||
patch-num="[[patchRange.basePatchNum]]"
|
||||
path="[[path]]"
|
||||
content="{{_diff.leftSide}}"
|
||||
prefs="[[prefs]]"
|
||||
can-comment="[[_loggedIn]]"
|
||||
project-config="[[projectConfig]]"
|
||||
on-expand-context="_handleExpandContext"
|
||||
on-thread-height-change="_handleThreadHeightChange"
|
||||
on-add-draft="_handleAddDraft"
|
||||
on-remove-thread="_handleRemoveThread"></gr-diff-side>
|
||||
<gr-diff-side id="rightDiff"
|
||||
change-num="[[changeNum]]"
|
||||
patch-num="[[patchRange.patchNum]]"
|
||||
path="[[path]]"
|
||||
content="{{_diff.rightSide}}"
|
||||
prefs="[[prefs]]"
|
||||
can-comment="[[_loggedIn]]"
|
||||
project-config="[[projectConfig]]"
|
||||
on-expand-context="_handleExpandContext"
|
||||
on-thread-height-change="_handleThreadHeightChange"
|
||||
on-add-draft="_handleAddDraft"
|
||||
on-remove-thread="_handleRemoveThread"></gr-diff-side>
|
||||
<div class$="[[_computeContainerClass(_loggedIn, _viewMode)]]"
|
||||
on-tap="_handleTap"
|
||||
on-mousedown="_handleMouseDown"
|
||||
on-copy="_handleCopy">
|
||||
<table id="diffTable"></table>
|
||||
</div>
|
||||
</div>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-diff-line.js"></script>
|
||||
<script src="gr-diff-group.js"></script>
|
||||
<script src="gr-diff-builder.js"></script>
|
||||
<script src="gr-diff-builder-side-by-side.js"></script>
|
||||
<script src="gr-diff-builder-unified.js"></script>
|
||||
<script src="gr-diff.js"></script>
|
||||
</dom-module>
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -20,8 +20,6 @@ limitations under the License.
|
||||
|
||||
<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
|
||||
<script src="../../../bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/fake-app.js"></script>
|
||||
<script src="../../../scripts/util.js"></script>
|
||||
|
||||
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
|
||||
<link rel="import" href="gr-diff.html">
|
||||
@@ -35,458 +33,214 @@ limitations under the License.
|
||||
<script>
|
||||
suite('gr-diff tests', function() {
|
||||
var element;
|
||||
var server;
|
||||
var getDiffStub;
|
||||
var getCommentsStub;
|
||||
|
||||
setup(function() {
|
||||
stub('gr-rest-api-interface', {
|
||||
getLoggedIn: function() { return Promise.resolve(false); },
|
||||
})
|
||||
element = fixture('basic');
|
||||
element.changeNum = 42;
|
||||
element.path = 'sieve.go';
|
||||
element.prefs = {
|
||||
context: 10,
|
||||
tab_size: 8,
|
||||
};
|
||||
|
||||
getDiffStub = sinon.stub(element.$.restAPI, 'getDiff', function() {
|
||||
return Promise.resolve({
|
||||
change_type: 'MODIFIED',
|
||||
content: [
|
||||
{
|
||||
ab: [
|
||||
'<!DOCTYPE html>',
|
||||
'<meta charset="utf-8">',
|
||||
'<title>My great page</title>',
|
||||
'<style>',
|
||||
' *,',
|
||||
' *:before,',
|
||||
' *:after {',
|
||||
' box-sizing: border-box;',
|
||||
' }',
|
||||
'</style>',
|
||||
'<header>',
|
||||
]
|
||||
},
|
||||
{
|
||||
a: [
|
||||
' Welcome ',
|
||||
' to the wooorld of tomorrow!',
|
||||
],
|
||||
b: [
|
||||
' Hello, world!',
|
||||
],
|
||||
},
|
||||
{
|
||||
ab: [
|
||||
'</header>',
|
||||
'<body>',
|
||||
'Leela: This is the only place the ship can’t hear us, so ',
|
||||
'everyone pretend to shower.',
|
||||
'Fry: Same as every day. Got it.',
|
||||
]
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
getCommentsStub = sinon.stub(element.$.restAPI, 'getDiffComments',
|
||||
function() {
|
||||
return Promise.resolve({
|
||||
baseComments: [
|
||||
{
|
||||
author: {
|
||||
_account_id: 1000000,
|
||||
name: 'Andrew Bonventre',
|
||||
email: 'andybons@gmail.com',
|
||||
},
|
||||
id: '9af53d3f_5f2b8b82',
|
||||
line: 1,
|
||||
message: 'this isn’t quite right',
|
||||
updated: '2015-12-10 02:50:21.627000000',
|
||||
}
|
||||
],
|
||||
comments: [
|
||||
{
|
||||
author: {
|
||||
_account_id: 1010008,
|
||||
name: 'Dave Borowitz',
|
||||
email: 'dborowitz@google.com',
|
||||
},
|
||||
id: '001a2067_f30f3048',
|
||||
line: 12,
|
||||
message: 'What on earth are you thinking, here?',
|
||||
updated: '2015-12-12 02:51:37.973000000',
|
||||
},
|
||||
{
|
||||
author: {
|
||||
_account_id: 1000000,
|
||||
name: 'Andrew Bonventre',
|
||||
email: 'andybons@gmail.com',
|
||||
},
|
||||
id: 'a0407443_30dfe8fb',
|
||||
in_reply_to: '001a2067_f30f3048',
|
||||
line: 12,
|
||||
message: '¯\\_(ツ)_/¯',
|
||||
updated: '2015-12-12 18:50:21.627000000',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
server = sinon.fakeServer.create();
|
||||
server.respondWith(
|
||||
'PUT',
|
||||
'/accounts/self/preferences.diff',
|
||||
[
|
||||
200,
|
||||
{'Content-Type': 'application/json'},
|
||||
')]}\'\n' +
|
||||
JSON.stringify({context: 25}),
|
||||
]
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
getDiffStub.restore();
|
||||
getCommentsStub.restore();
|
||||
server.restore();
|
||||
});
|
||||
test('get drafts logged out', function(done) {
|
||||
element.patchRange = {basePatchNum: 0, patchNum: 0};
|
||||
|
||||
test('comment rendering', function(done) {
|
||||
element.prefs.context = -1;
|
||||
element._loggedIn = true;
|
||||
element.patchRange = {
|
||||
basePatchNum: 1,
|
||||
patchNum: 2,
|
||||
};
|
||||
|
||||
element.reload().then(function() {
|
||||
flush(function() {
|
||||
var leftThreadEls =
|
||||
Polymer.dom(element.$.leftDiff.root).querySelectorAll(
|
||||
'gr-diff-comment-thread');
|
||||
assert.equal(leftThreadEls.length, 1);
|
||||
assert.equal(leftThreadEls[0].comments.length, 1);
|
||||
|
||||
var rightThreadEls =
|
||||
Polymer.dom(element.$.rightDiff.root).querySelectorAll(
|
||||
'gr-diff-comment-thread');
|
||||
assert.equal(rightThreadEls.length, 1);
|
||||
assert.equal(rightThreadEls[0].comments.length, 2);
|
||||
|
||||
var index = leftThreadEls[0].getAttribute('data-index');
|
||||
var leftFillerEls =
|
||||
Polymer.dom(element.$.leftDiff.root).querySelectorAll(
|
||||
'.commentThread.filler[data-index="' + index + '"]');
|
||||
assert.equal(leftFillerEls.length, 1);
|
||||
var rightFillerEls =
|
||||
Polymer.dom(element.$.rightDiff.root).querySelectorAll(
|
||||
'[data-index="' + index + '"]');
|
||||
assert.equal(rightFillerEls.length, 2);
|
||||
|
||||
for (var i = 0; i < rightFillerEls.length; i++) {
|
||||
assert.isTrue(rightFillerEls[i].classList.contains('filler'));
|
||||
}
|
||||
var originalHeight = rightFillerEls[0].offsetHeight;
|
||||
assert.equal(rightFillerEls[1].offsetHeight, originalHeight);
|
||||
assert.equal(leftThreadEls[0].offsetHeight, originalHeight);
|
||||
assert.equal(leftFillerEls[0].offsetHeight, originalHeight);
|
||||
|
||||
// Create a comment on the opposite side of the first comment.
|
||||
var rightLineEL = element.$.rightDiff.$$(
|
||||
'.lineNum[data-index="' + (index - 1) + '"]');
|
||||
assert.ok(rightLineEL);
|
||||
MockInteractions.tap(rightLineEL);
|
||||
flush(function() {
|
||||
var newThreadEls =
|
||||
Polymer.dom(element.$.rightDiff.root).querySelectorAll(
|
||||
'[data-index="' + index + '"]');
|
||||
assert.equal(newThreadEls.length, 2);
|
||||
for (var i = 0; i < newThreadEls.length; i++) {
|
||||
assert.isTrue(
|
||||
newThreadEls[i].classList.contains('commentThread') ||
|
||||
newThreadEls[i].tagName == 'GR-DIFF-COMMENT-THREAD');
|
||||
}
|
||||
var newHeight = newThreadEls[0].offsetHeight;
|
||||
assert.equal(newThreadEls[1].offsetHeight, newHeight);
|
||||
assert.equal(leftFillerEls[0].offsetHeight, newHeight);
|
||||
assert.equal(leftThreadEls[0].offsetHeight, newHeight);
|
||||
|
||||
// The editing mode height of the right comment will be greater than
|
||||
// the non-editing mode height of the left comment.
|
||||
assert.isAbove(newHeight, originalHeight);
|
||||
|
||||
// Discard the right thread and ensure the left comment heights are
|
||||
// back to their original values.
|
||||
newThreadEls[1].addEventListener('thread-discard', function() {
|
||||
rightFillerEls =
|
||||
Polymer.dom(element.$.rightDiff.root).querySelectorAll(
|
||||
'[data-index="' + index + '"]');
|
||||
assert.equal(rightFillerEls.length, 2);
|
||||
|
||||
for (var i = 0; i < rightFillerEls.length; i++) {
|
||||
assert.isTrue(rightFillerEls[i].classList.contains('filler'));
|
||||
}
|
||||
var originalHeight = rightFillerEls[0].offsetHeight;
|
||||
assert.equal(rightFillerEls[1].offsetHeight, originalHeight);
|
||||
assert.equal(leftThreadEls[0].offsetHeight, originalHeight);
|
||||
assert.equal(leftFillerEls[0].offsetHeight, originalHeight);
|
||||
done();
|
||||
});
|
||||
var commentEl = newThreadEls[1].$$('gr-diff-comment');
|
||||
commentEl.fire('comment-discard');
|
||||
});
|
||||
});
|
||||
});
|
||||
server.respond();
|
||||
});
|
||||
|
||||
test('intraline normalization', function() {
|
||||
// The content and highlights are in the format returned by the Gerrit
|
||||
// REST API.
|
||||
var content = [
|
||||
' <section class="summary">',
|
||||
' <gr-linked-text content="' +
|
||||
'[[_computeCurrentRevisionMessage(change)]]"></gr-linked-text>',
|
||||
' </section>',
|
||||
];
|
||||
var highlights = [
|
||||
[31, 34], [42, 26]
|
||||
];
|
||||
var results = element._normalizeIntralineHighlights(content, highlights);
|
||||
assert.deepEqual(results, [
|
||||
{
|
||||
contentIndex: 0,
|
||||
startIndex: 31,
|
||||
},
|
||||
{
|
||||
contentIndex: 1,
|
||||
startIndex: 0,
|
||||
endIndex: 33,
|
||||
},
|
||||
{
|
||||
contentIndex: 1,
|
||||
startIndex: 75,
|
||||
},
|
||||
{
|
||||
contentIndex: 2,
|
||||
startIndex: 0,
|
||||
endIndex: 6,
|
||||
}
|
||||
]);
|
||||
|
||||
content = [
|
||||
' this._path = value.path;',
|
||||
'',
|
||||
' // When navigating away from the page, there is a possibility that the',
|
||||
' // patch number is no longer a part of the URL (say when navigating to',
|
||||
' // the top-level change info view) and therefore undefined in `params`.',
|
||||
' if (!this._patchRange.patchNum) {',
|
||||
];
|
||||
highlights = [
|
||||
[14, 17],
|
||||
[11, 70],
|
||||
[12, 67],
|
||||
[12, 67],
|
||||
[14, 29],
|
||||
];
|
||||
results = element._normalizeIntralineHighlights(content, highlights);
|
||||
assert.deepEqual(results, [
|
||||
{
|
||||
contentIndex: 0,
|
||||
startIndex: 14,
|
||||
endIndex: 31,
|
||||
},
|
||||
{
|
||||
contentIndex: 2,
|
||||
startIndex: 8,
|
||||
endIndex: 78,
|
||||
},
|
||||
{
|
||||
contentIndex: 3,
|
||||
startIndex: 11,
|
||||
endIndex: 78,
|
||||
},
|
||||
{
|
||||
contentIndex: 4,
|
||||
startIndex: 11,
|
||||
endIndex: 78,
|
||||
},
|
||||
{
|
||||
contentIndex: 5,
|
||||
startIndex: 12,
|
||||
endIndex: 41,
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
test('context', function() {
|
||||
element.prefs.context = 3;
|
||||
element._diffResponse = {
|
||||
content: [
|
||||
{
|
||||
ab: [
|
||||
'<!DOCTYPE html>',
|
||||
'<meta charset="utf-8">',
|
||||
'<title>My great page</title>',
|
||||
'<style>',
|
||||
' *,',
|
||||
' *:before,',
|
||||
' *:after {',
|
||||
' box-sizing: border-box;',
|
||||
' }',
|
||||
'</style>',
|
||||
'<header>',
|
||||
]
|
||||
},
|
||||
{
|
||||
a: [
|
||||
' Welcome ',
|
||||
' to the wooorld of tomorrow!',
|
||||
],
|
||||
b: [
|
||||
' Hello, world!',
|
||||
],
|
||||
},
|
||||
{
|
||||
ab: [
|
||||
'</header>',
|
||||
'<body>',
|
||||
'Leela: This is the only place the ship can’t hear us, so ',
|
||||
'everyone pretend to shower.',
|
||||
'Fry: Same as every day. Got it.',
|
||||
]
|
||||
},
|
||||
]
|
||||
};
|
||||
element._processContent();
|
||||
|
||||
// First eight lines should be hidden on both sides.
|
||||
for (var i = 0; i < 8; i++) {
|
||||
assert.isTrue(element._diff.leftSide[i].hidden);
|
||||
assert.isTrue(element._diff.rightSide[i].hidden);
|
||||
}
|
||||
// A context control should be at index 8 on both sides.
|
||||
var leftContext = element._diff.leftSide[8];
|
||||
var rightContext = element._diff.rightSide[8];
|
||||
assert.deepEqual(leftContext, rightContext);
|
||||
assert.equal(leftContext.numLines, 8);
|
||||
assert.equal(leftContext.start, 0);
|
||||
assert.equal(leftContext.end, 8);
|
||||
|
||||
// Line indices 9-16 should be shown.
|
||||
for (var i = 9; i <= 16; i++) {
|
||||
// notOk (falsy) because the `hidden` attribute may not be present.
|
||||
assert.notOk(element._diff.leftSide[i].hidden);
|
||||
assert.notOk(element._diff.rightSide[i].hidden);
|
||||
}
|
||||
|
||||
// Lines at indices 17 and 18 should be hidden.
|
||||
assert.isTrue(element._diff.leftSide[17].hidden);
|
||||
assert.isTrue(element._diff.rightSide[17].hidden);
|
||||
assert.isTrue(element._diff.leftSide[18].hidden);
|
||||
assert.isTrue(element._diff.rightSide[18].hidden);
|
||||
|
||||
// Context control at index 19.
|
||||
leftContext = element._diff.leftSide[19];
|
||||
rightContext = element._diff.rightSide[19];
|
||||
assert.deepEqual(leftContext, rightContext);
|
||||
assert.equal(leftContext.numLines, 2);
|
||||
assert.equal(leftContext.start, 17);
|
||||
assert.equal(leftContext.end, 19);
|
||||
});
|
||||
|
||||
test('save prefs', function(done) {
|
||||
element._loggedIn = false;
|
||||
|
||||
element.prefs = {
|
||||
tab_size: 4,
|
||||
context: 50,
|
||||
};
|
||||
element.fire('save', {}, {node: element.$$('gr-diff-preferences')});
|
||||
assert.isTrue(element._diffPreferencesPromise == null);
|
||||
|
||||
element._loggedIn = true;
|
||||
element.fire('save', {}, {node: element.$$('gr-diff-preferences')});
|
||||
server.respond();
|
||||
|
||||
element._diffPreferencesPromise.then(function(req) {
|
||||
assert.equal(req.xhr.requestBody, JSON.stringify(element.prefs));
|
||||
var getDraftsStub = sinon.stub(element.$.restAPI, 'getDiffDrafts');
|
||||
var loggedInStub = sinon.stub(element, '_getLoggedIn',
|
||||
function() { return Promise.resolve(false); });
|
||||
element._getDiffDrafts().then(function(result) {
|
||||
assert.deepEqual(result, {baseComments: [], comments: []});
|
||||
sinon.assert.notCalled(getDraftsStub);
|
||||
loggedInStub.restore();
|
||||
getDraftsStub.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('visible line length', function() {
|
||||
assert.equal(element._visibleLineLength('A'.repeat(5)), 5);
|
||||
assert.equal(
|
||||
element._visibleLineLength('A'.repeat(5) + '\t' + 'A'.repeat(5)), 18);
|
||||
test('get drafts logged in', function(done) {
|
||||
element.patchRange = {basePatchNum: 0, patchNum: 0};
|
||||
var draftsResponse = {
|
||||
baseComments: [{id: 'foo'}],
|
||||
comments: [{id: 'bar'}],
|
||||
};
|
||||
var getDraftsStub = sinon.stub(element.$.restAPI, 'getDiffDrafts',
|
||||
function() { return Promise.resolve(draftsResponse); });
|
||||
var loggedInStub = sinon.stub(element, '_getLoggedIn',
|
||||
function() { return Promise.resolve(true); });
|
||||
element._getDiffDrafts().then(function(result) {
|
||||
assert.deepEqual(result, draftsResponse);
|
||||
loggedInStub.restore();
|
||||
getDraftsStub.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('break up common diff chunks', function() {
|
||||
element._groupedBaseComments = {
|
||||
1: {},
|
||||
test('get comments and drafts', function(done) {
|
||||
var loggedInStub = sinon.stub(element, '_getLoggedIn',
|
||||
function() { return Promise.resolve(true); });
|
||||
var comments = {
|
||||
baseComments: [
|
||||
{id: 'bc1'},
|
||||
{id: 'bc2'},
|
||||
],
|
||||
comments: [
|
||||
{id: 'c1'},
|
||||
{id: 'c2'},
|
||||
],
|
||||
};
|
||||
element._groupedComments = {
|
||||
10: {},
|
||||
var diffCommentsStub = sinon.stub(element, '_getDiffComments',
|
||||
function() { return Promise.resolve(comments); });
|
||||
|
||||
var drafts = {
|
||||
baseComments: [
|
||||
{id: 'bd1'},
|
||||
{id: 'bd2'},
|
||||
],
|
||||
comments: [
|
||||
{id: 'd1'},
|
||||
{id: 'd2'},
|
||||
],
|
||||
};
|
||||
var ctx = {
|
||||
left: {lineNum: 0},
|
||||
right: {lineNum: 0},
|
||||
var diffDraftsStub = sinon.stub(element, '_getDiffDrafts',
|
||||
function() { return Promise.resolve(drafts); });
|
||||
|
||||
element.changeNum = '42';
|
||||
element.patchRange = {
|
||||
basePatchNum: 'PARENT',
|
||||
patchNum: 3,
|
||||
};
|
||||
var content = [
|
||||
{
|
||||
ab: [
|
||||
'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.',
|
||||
]
|
||||
}
|
||||
];
|
||||
var result = element._breakUpCommonChunksWithComments(ctx, content);
|
||||
assert.deepEqual(result, [
|
||||
{
|
||||
__noHighlight: true,
|
||||
a: ['Copyright (C) 2015 The Android Open Source Project'],
|
||||
b: ['Copyright (C) 2015 The Android Open Source Project'],
|
||||
element.path = '/path/to/foo';
|
||||
element.projectConfig = {foo: 'bar'};
|
||||
|
||||
element._getDiffCommentsAndDrafts().then(function(result) {
|
||||
assert.deepEqual(result, {
|
||||
meta: {
|
||||
changeNum: '42',
|
||||
patchRange: {
|
||||
basePatchNum: 'PARENT',
|
||||
patchNum: 3,
|
||||
},
|
||||
path: '/path/to/foo',
|
||||
projectConfig: {foo: 'bar'},
|
||||
},
|
||||
left: [
|
||||
{id: 'bc1'},
|
||||
{id: 'bc2'},
|
||||
{id: 'bd1', __draft: true},
|
||||
{id: 'bd2', __draft: true},
|
||||
],
|
||||
right: [
|
||||
{id: 'c1'},
|
||||
{id: 'c2'},
|
||||
{id: 'd1', __draft: true},
|
||||
{id: 'd2', __draft: true},
|
||||
],
|
||||
});
|
||||
|
||||
diffCommentsStub.restore();
|
||||
diffDraftsStub.restore();
|
||||
loggedInStub.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('remove comment', function() {
|
||||
element._comments = {
|
||||
meta: {
|
||||
changeNum: '42',
|
||||
patchRange: {
|
||||
basePatchNum: 'PARENT',
|
||||
patchNum: 3,
|
||||
},
|
||||
path: '/path/to/foo',
|
||||
projectConfig: {foo: 'bar'},
|
||||
},
|
||||
{
|
||||
ab: [
|
||||
'',
|
||||
'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, ',
|
||||
]
|
||||
left: [
|
||||
{id: 'bc1'},
|
||||
{id: 'bc2'},
|
||||
{id: 'bd1', __draft: true},
|
||||
{id: 'bd2', __draft: true},
|
||||
],
|
||||
right: [
|
||||
{id: 'c1'},
|
||||
{id: 'c2'},
|
||||
{id: 'd1', __draft: true},
|
||||
{id: 'd2', __draft: true},
|
||||
],
|
||||
};
|
||||
|
||||
element._removeComment({});
|
||||
// Using JSON.stringify because Safari 9.1 (11601.5.17.1) doesn’t seem to
|
||||
// believe that one object deepEquals another even when they do :-/.
|
||||
assert.equal(JSON.stringify(element._comments), JSON.stringify({
|
||||
meta: {
|
||||
changeNum: '42',
|
||||
patchRange: {
|
||||
basePatchNum: 'PARENT',
|
||||
patchNum: 3,
|
||||
},
|
||||
path: '/path/to/foo',
|
||||
projectConfig: {foo: 'bar'},
|
||||
},
|
||||
{
|
||||
__noHighlight: true,
|
||||
a: ['software distributed under the License is distributed on an '],
|
||||
b: ['software distributed under the License is distributed on an ']
|
||||
left: [
|
||||
{id: 'bc1'},
|
||||
{id: 'bc2'},
|
||||
{id: 'bd1', __draft: true},
|
||||
{id: 'bd2', __draft: true},
|
||||
],
|
||||
right: [
|
||||
{id: 'c1'},
|
||||
{id: 'c2'},
|
||||
{id: 'd1', __draft: true},
|
||||
{id: 'd2', __draft: true},
|
||||
],
|
||||
}));
|
||||
|
||||
element._removeComment({id: 'bc2'});
|
||||
assert.equal(JSON.stringify(element._comments), JSON.stringify({
|
||||
meta: {
|
||||
changeNum: '42',
|
||||
patchRange: {
|
||||
basePatchNum: 'PARENT',
|
||||
patchNum: 3,
|
||||
},
|
||||
path: '/path/to/foo',
|
||||
projectConfig: {foo: 'bar'},
|
||||
},
|
||||
{
|
||||
ab: [
|
||||
'"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.',
|
||||
]
|
||||
}
|
||||
]);
|
||||
left: [
|
||||
{id: 'bc1'},
|
||||
{id: 'bd1', __draft: true},
|
||||
{id: 'bd2', __draft: true},
|
||||
],
|
||||
right: [
|
||||
{id: 'c1'},
|
||||
{id: 'c2'},
|
||||
{id: 'd1', __draft: true},
|
||||
{id: 'd2', __draft: true},
|
||||
],
|
||||
}));
|
||||
|
||||
element._removeComment({id: 'd2'});
|
||||
assert.deepEqual(JSON.stringify(element._comments), JSON.stringify({
|
||||
meta: {
|
||||
changeNum: '42',
|
||||
patchRange: {
|
||||
basePatchNum: 'PARENT',
|
||||
patchNum: 3,
|
||||
},
|
||||
path: '/path/to/foo',
|
||||
projectConfig: {foo: 'bar'},
|
||||
},
|
||||
left: [
|
||||
{id: 'bc1'},
|
||||
{id: 'bd1', __draft: true},
|
||||
{id: 'bd2', __draft: true},
|
||||
],
|
||||
right: [
|
||||
{id: 'c1'},
|
||||
{id: 'c2'},
|
||||
{id: 'd1', __draft: true},
|
||||
],
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
@@ -1,184 +0,0 @@
|
||||
<!--
|
||||
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="../../shared/gr-button/gr-button.html">
|
||||
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
|
||||
<link rel="import" href="../../shared/gr-request/gr-request.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
|
||||
<link rel="import" href="../gr-diff-comment-thread/gr-diff-comment-thread.html">
|
||||
<link rel="import" href="../gr-diff-preferences/gr-diff-preferences.html">
|
||||
<link rel="import" href="../gr-patch-range-select/gr-patch-range-select.html">
|
||||
|
||||
<dom-module id="gr-new-diff">
|
||||
<template>
|
||||
<style>
|
||||
:host {
|
||||
--light-remove-highlight-color: #fee;
|
||||
--dark-remove-highlight-color: #ffd4d4;
|
||||
--light-add-highlight-color: #efe;
|
||||
--dark-add-highlight-color: #d4ffd4;
|
||||
}
|
||||
.loading {
|
||||
padding: 0 var(--default-horizontal-margin) 1em;
|
||||
color: #666;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 0 var(--default-horizontal-margin) .75em;
|
||||
}
|
||||
.prefsButton {
|
||||
text-align: right;
|
||||
}
|
||||
.diffContainer {
|
||||
border-bottom: 1px solid #eee;
|
||||
border-top: 1px solid #eee;
|
||||
display: flex;
|
||||
font: 12px var(--monospace-font-family);
|
||||
overflow-x: auto;
|
||||
will-change: transform;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-right: 1px solid #ddd;
|
||||
}
|
||||
.section {
|
||||
background-color: #eee;
|
||||
}
|
||||
.blank,
|
||||
.content {
|
||||
background-color: #fff;
|
||||
}
|
||||
.lineNum,
|
||||
.content {
|
||||
vertical-align: top;
|
||||
white-space: pre;
|
||||
}
|
||||
.contextLineNum:before,
|
||||
.lineNum:before {
|
||||
display: inline-block;
|
||||
color: #666;
|
||||
content: attr(data-value);
|
||||
padding: 0 .75em;
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
}
|
||||
.canComment .lineNum[data-value] {
|
||||
cursor: pointer;
|
||||
}
|
||||
.canComment .lineNum[data-value]:before {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.canComment .lineNum[data-value]:hover:before {
|
||||
background-color: #ccc;
|
||||
}
|
||||
.canComment .lineNum[data-value="FILE"]:before {
|
||||
content: 'File';
|
||||
}
|
||||
.content {
|
||||
overflow: hidden;
|
||||
min-width: var(--content-width, 80ch);
|
||||
}
|
||||
.content.left {
|
||||
-webkit-user-select: var(--left-user-select, text);
|
||||
-moz-user-select: var(--left-user-select, text);
|
||||
-ms-user-select: var(--left-user-select, text);
|
||||
user-select: var(--left-user-select, text);
|
||||
}
|
||||
.content.right {
|
||||
-webkit-user-select: var(--right-user-select, text);
|
||||
-moz-user-select: var(--right-user-select, text);
|
||||
-ms-user-select: var(--right-user-select, text);
|
||||
user-select: var(--right-user-select, text);
|
||||
}
|
||||
.content.add hl,
|
||||
.content.add.darkHighlight {
|
||||
background-color: var(--dark-add-highlight-color);
|
||||
}
|
||||
.content.add.lightHighlight {
|
||||
background-color: var(--light-add-highlight-color);
|
||||
}
|
||||
.content.remove hl,
|
||||
.content.remove.darkHighlight {
|
||||
background-color: var(--dark-remove-highlight-color);
|
||||
}
|
||||
.content.remove.lightHighlight {
|
||||
background-color: var(--light-remove.highlight-color);
|
||||
}
|
||||
.contextControl {
|
||||
color: #849;
|
||||
background-color: #fef;
|
||||
}
|
||||
.contextControl gr-button {
|
||||
display: block;
|
||||
font-family: var(--monospace-font-family);
|
||||
text-decoration: none;
|
||||
}
|
||||
.contextControl td:not(.lineNum) {
|
||||
text-align: center;
|
||||
}
|
||||
.br:after {
|
||||
/* Line feed */
|
||||
content: '\A';
|
||||
}
|
||||
.tab {
|
||||
display: inline-block;
|
||||
}
|
||||
.tab.withIndicator:before {
|
||||
color: #C62828;
|
||||
/* >> character */
|
||||
content: '\00BB';
|
||||
}
|
||||
</style>
|
||||
<div class="loading" hidden$="[[!_loading]]">Loading...</div>
|
||||
<div hidden$="[[_loading]]" hidden>
|
||||
<div class="header">
|
||||
<gr-patch-range-select
|
||||
path="[[path]]"
|
||||
change-num="[[changeNum]]"
|
||||
patch-range="[[patchRange]]"
|
||||
available-patches="[[availablePatches]]"></gr-patch-range-select>
|
||||
<gr-button link
|
||||
class="prefsButton"
|
||||
on-tap="_handlePrefsTap"
|
||||
hidden$="[[_computePrefsButtonHidden(_prefs, _loggedIn)]]"
|
||||
hidden>Diff View Preferences</gr-button>
|
||||
</div>
|
||||
<gr-overlay id="prefsOverlay" with-backdrop>
|
||||
<gr-diff-preferences
|
||||
prefs="{{_prefs}}"
|
||||
on-save="_handlePrefsSave"
|
||||
on-cancel="_handlePrefsCancel"></gr-diff-preferences>
|
||||
</gr-overlay>
|
||||
|
||||
<div class$="[[_computeContainerClass(_loggedIn, _viewMode)]]"
|
||||
on-tap="_handleTap"
|
||||
on-mousedown="_handleMouseDown"
|
||||
on-copy="_handleCopy">
|
||||
<table id="diffTable"></table>
|
||||
</div>
|
||||
</div>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-diff-line.js"></script>
|
||||
<script src="gr-diff-group.js"></script>
|
||||
<script src="gr-diff-builder.js"></script>
|
||||
<script src="gr-diff-builder-side-by-side.js"></script>
|
||||
<script src="gr-diff-builder-unified.js"></script>
|
||||
<script src="gr-new-diff.js"></script>
|
||||
</dom-module>
|
@@ -1,516 +0,0 @@
|
||||
// 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-new-diff',
|
||||
|
||||
/**
|
||||
* Fired when the diff is rendered.
|
||||
*
|
||||
* @event render
|
||||
*/
|
||||
|
||||
properties: {
|
||||
availablePatches: Array,
|
||||
changeNum: String,
|
||||
patchRange: Object,
|
||||
path: String,
|
||||
|
||||
projectConfig: {
|
||||
type: Object,
|
||||
observer: '_projectConfigChanged',
|
||||
},
|
||||
|
||||
_loggedIn: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
_viewMode: {
|
||||
type: String,
|
||||
value: DiffViewMode.SIDE_BY_SIDE,
|
||||
},
|
||||
_diff: Object,
|
||||
_diffBuilder: Object,
|
||||
_prefs: Object,
|
||||
_selectionSide: {
|
||||
type: String,
|
||||
observer: '_selectionSideChanged',
|
||||
},
|
||||
_comments: Object,
|
||||
_focusedSection: {
|
||||
type: Number,
|
||||
value: -1,
|
||||
},
|
||||
_focusedThread: {
|
||||
type: Number,
|
||||
value: -1,
|
||||
},
|
||||
},
|
||||
|
||||
observers: [
|
||||
'_prefsChanged(_prefs.*)',
|
||||
],
|
||||
|
||||
attached: function() {
|
||||
this._getLoggedIn().then(function(loggedIn) {
|
||||
this._loggedIn = loggedIn;
|
||||
}.bind(this));
|
||||
|
||||
this.addEventListener('thread-discard',
|
||||
this._handleThreadDiscard.bind(this));
|
||||
this.addEventListener('comment-discard',
|
||||
this._handleCommentDiscard.bind(this));
|
||||
},
|
||||
|
||||
reload: function() {
|
||||
this._clearDiffContent();
|
||||
this._loading = true;
|
||||
|
||||
var promises = [];
|
||||
|
||||
promises.push(this._getDiff().then(function(diff) {
|
||||
this._diff = diff;
|
||||
this._loading = false;
|
||||
}.bind(this)));
|
||||
|
||||
promises.push(this._getDiffCommentsAndDrafts().then(function(comments) {
|
||||
this._comments = comments;
|
||||
}.bind(this)));
|
||||
|
||||
promises.push(this._getDiffPreferences().then(function(prefs) {
|
||||
this._prefs = prefs;
|
||||
}.bind(this)));
|
||||
|
||||
return Promise.all(promises).then(function() {
|
||||
this._render();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
scrollToLine: function(lineNum) {
|
||||
if (isNaN(lineNum) || lineNum < 1) { return; }
|
||||
|
||||
var lineEls = Polymer.dom(this.root).querySelectorAll(
|
||||
'.lineNum[data-value="' + lineNum + '"]');
|
||||
|
||||
// Always choose the right side.
|
||||
var el = lineEls.length === 2 ? lineEls[1] : lineEls[0];
|
||||
this._scrollToElement(el);
|
||||
},
|
||||
|
||||
scrollToNextDiffChunk: function() {
|
||||
this._focusedSection = this._advanceElementWithinNodeList(
|
||||
this._getDeltaSections(), this._focusedSection, 1);
|
||||
},
|
||||
|
||||
scrollToPreviousDiffChunk: function() {
|
||||
this._focusedSection = this._advanceElementWithinNodeList(
|
||||
this._getDeltaSections(), this._focusedSection, -1);
|
||||
},
|
||||
|
||||
scrollToNextCommentThread: function() {
|
||||
this._focusedThread = this._advanceElementWithinNodeList(
|
||||
this._getCommentThreads(), this._focusedThread, 1);
|
||||
},
|
||||
|
||||
scrollToPreviousCommentThread: function() {
|
||||
this._focusedThread = this._advanceElementWithinNodeList(
|
||||
this._getCommentThreads(), this._focusedThread, -1);
|
||||
},
|
||||
|
||||
_advanceElementWithinNodeList: function(els, curIndex, direction) {
|
||||
var idx = Math.max(0, Math.min(els.length - 1, curIndex + direction));
|
||||
if (curIndex !== idx) {
|
||||
this._scrollToElement(els[idx]);
|
||||
return idx;
|
||||
}
|
||||
return curIndex;
|
||||
},
|
||||
|
||||
_getCommentThreads: function() {
|
||||
return Polymer.dom(this.root).querySelectorAll('gr-diff-comment-thread');
|
||||
},
|
||||
|
||||
_getDeltaSections: function() {
|
||||
return Polymer.dom(this.root).querySelectorAll('.section.delta');
|
||||
},
|
||||
|
||||
_scrollToElement: function(el) {
|
||||
if (!el) { return; }
|
||||
|
||||
// Calculate where the element 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 / 2));
|
||||
},
|
||||
|
||||
_computeContainerClass: function(loggedIn, viewMode) {
|
||||
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');
|
||||
}
|
||||
return classes.join(' ');
|
||||
},
|
||||
|
||||
_computePrefsButtonHidden: function(prefs, loggedIn) {
|
||||
return !loggedIn || !prefs;
|
||||
},
|
||||
|
||||
_handlePrefsTap: function(e) {
|
||||
e.preventDefault();
|
||||
this.$.prefsOverlay.open();
|
||||
},
|
||||
|
||||
_handlePrefsSave: function(e) {
|
||||
e.stopPropagation();
|
||||
var el = Polymer.dom(e).rootTarget;
|
||||
el.disabled = true;
|
||||
this._saveDiffPreferences().then(function() {
|
||||
this.$.prefsOverlay.close();
|
||||
el.disabled = false;
|
||||
}.bind(this)).catch(function(err) {
|
||||
el.disabled = false;
|
||||
alert('Oops. Something went wrong. Check the console and bug the ' +
|
||||
'PolyGerrit team for assistance.');
|
||||
throw err;
|
||||
});
|
||||
},
|
||||
|
||||
_saveDiffPreferences: function() {
|
||||
return this.$.restAPI.saveDiffPreferences(this._prefs);
|
||||
},
|
||||
|
||||
_handlePrefsCancel: function(e) {
|
||||
e.stopPropagation();
|
||||
this.$.prefsOverlay.close();
|
||||
},
|
||||
|
||||
_handleTap: function(e) {
|
||||
var el = Polymer.dom(e).rootTarget;
|
||||
|
||||
if (el.classList.contains('showContext')) {
|
||||
this._showContext(e.detail.group, e.detail.section);
|
||||
} else if (el.classList.contains('lineNum')) {
|
||||
this._handleLineTap(el);
|
||||
}
|
||||
},
|
||||
|
||||
_handleLineTap: function(el) {
|
||||
this._getLoggedIn().then(function(loggedIn) {
|
||||
if (!loggedIn) { 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));
|
||||
},
|
||||
|
||||
_addDraft: function(lineEl, opt_lineNum) {
|
||||
var threadEl;
|
||||
|
||||
// Does a thread already exist at this line?
|
||||
var contentEl = lineEl.nextSibling;
|
||||
while (contentEl && !contentEl.classList.contains('content')) {
|
||||
contentEl = contentEl.nextSibling;
|
||||
}
|
||||
if (contentEl.childNodes.length > 0 &&
|
||||
contentEl.lastChild.nodeName === 'GR-DIFF-COMMENT-THREAD') {
|
||||
threadEl = contentEl.lastChild;
|
||||
} else {
|
||||
var patchNum = this.patchRange.patchNum;
|
||||
var side = 'REVISION';
|
||||
if (contentEl.classList.contains(DiffSide.LEFT) ||
|
||||
contentEl.classList.contains('remove')) {
|
||||
if (this.patchRange.basePatchNum === 'PARENT') {
|
||||
side = 'PARENT';
|
||||
} else {
|
||||
patchNum = this.patchRange.basePatchNum;
|
||||
}
|
||||
}
|
||||
threadEl = this._builder.createCommentThread(this.changeNum, patchNum,
|
||||
this.path, side, this.projectConfig);
|
||||
contentEl.appendChild(threadEl);
|
||||
}
|
||||
threadEl.addDraft(opt_lineNum);
|
||||
},
|
||||
|
||||
_handleThreadDiscard: function(e) {
|
||||
var el = Polymer.dom(e).rootTarget;
|
||||
el.parentNode.removeChild(el);
|
||||
},
|
||||
|
||||
_handleCommentDiscard: function(e) {
|
||||
var comment = Polymer.dom(e).rootTarget.comment;
|
||||
this._removeComment(comment);
|
||||
},
|
||||
|
||||
_removeComment: function(comment) {
|
||||
if (!comment.id) { return; }
|
||||
this._removeCommentFromSide(comment, DiffSide.LEFT) ||
|
||||
this._removeCommentFromSide(comment, DiffSide.RIGHT);
|
||||
},
|
||||
|
||||
_removeCommentFromSide: function(comment, side) {
|
||||
var idx = -1;
|
||||
for (var i = 0; i < this._comments[side].length; i++) {
|
||||
if (this._comments[side][i].id === comment.id) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (idx !== -1) {
|
||||
this.splice('_comments.' + side, idx, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_handleMouseDown: function(e) {
|
||||
var el = Polymer.dom(e).rootTarget;
|
||||
var side;
|
||||
for (var node = el; node != null; node = node.parentNode) {
|
||||
if (!node.classList) { continue; }
|
||||
|
||||
if (node.classList.contains(DiffSide.LEFT)) {
|
||||
side = DiffSide.LEFT;
|
||||
break;
|
||||
} else if (node.classList.contains(DiffSide.RIGHT)) {
|
||||
side = DiffSide.RIGHT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this._selectionSide = side;
|
||||
},
|
||||
|
||||
_selectionSideChanged: function(side) {
|
||||
if (side) {
|
||||
var oppositeSide = side === DiffSide.RIGHT ?
|
||||
DiffSide.LEFT : DiffSide.RIGHT;
|
||||
this.customStyle['--' + side + '-user-select'] = 'text';
|
||||
this.customStyle['--' + oppositeSide + '-user-select'] = 'none';
|
||||
} else {
|
||||
this.customStyle['--left-user-select'] = 'text';
|
||||
this.customStyle['--right-user-select'] = 'text';
|
||||
}
|
||||
this.updateStyles();
|
||||
},
|
||||
|
||||
_handleCopy: function(e) {
|
||||
var text = this._getSelectedText(this._selectionSide);
|
||||
e.clipboardData.setData('Text', text);
|
||||
e.preventDefault();
|
||||
},
|
||||
|
||||
_getSelectedText: function(opt_side) {
|
||||
var sel = window.getSelection();
|
||||
var range = sel.getRangeAt(0);
|
||||
var doc = range.cloneContents();
|
||||
var selector = '.content';
|
||||
if (opt_side) {
|
||||
selector += '.' + opt_side;
|
||||
}
|
||||
var contentEls = Polymer.dom(doc).querySelectorAll(selector);
|
||||
|
||||
if (contentEls.length === 0) {
|
||||
return doc.textContent;
|
||||
}
|
||||
|
||||
var text = '';
|
||||
for (var i = 0; i < contentEls.length; i++) {
|
||||
text += contentEls[i].textContent + '\n';
|
||||
}
|
||||
return text;
|
||||
},
|
||||
|
||||
_showContext: function(group, sectionEl) {
|
||||
this._builder.emitGroup(group, sectionEl);
|
||||
sectionEl.parentNode.removeChild(sectionEl);
|
||||
},
|
||||
|
||||
_prefsChanged: function(prefsChangeRecord) {
|
||||
var prefs = prefsChangeRecord.base;
|
||||
this.customStyle['--content-width'] = prefs.line_length + 'ch';
|
||||
this.updateStyles();
|
||||
|
||||
if (this._diff && this._comments) {
|
||||
this._render();
|
||||
}
|
||||
},
|
||||
|
||||
_render: function() {
|
||||
this._clearDiffContent();
|
||||
this._builder = this._getDiffBuilder(this._diff, this._comments,
|
||||
this._prefs);
|
||||
this._builder.emitDiff(this._diff.content);
|
||||
|
||||
this.async(function() {
|
||||
this.fire('render', null, {bubbles: false});
|
||||
}.bind(this), 1);
|
||||
},
|
||||
|
||||
_clearDiffContent: function() {
|
||||
this.$.diffTable.innerHTML = null;
|
||||
},
|
||||
|
||||
_getDiff: function() {
|
||||
return this.$.restAPI.getDiff(
|
||||
this.changeNum,
|
||||
this.patchRange.basePatchNum,
|
||||
this.patchRange.patchNum,
|
||||
this.path);
|
||||
},
|
||||
|
||||
_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));
|
||||
},
|
||||
|
||||
_getDiffCommentsAndDrafts: function() {
|
||||
var promises = [];
|
||||
promises.push(this._getDiffComments());
|
||||
promises.push(this._getDiffDrafts());
|
||||
return Promise.all(promises).then(function(results) {
|
||||
return Promise.resolve({
|
||||
comments: results[0],
|
||||
drafts: results[1],
|
||||
});
|
||||
}).then(this._normalizeDiffCommentsAndDrafts.bind(this));
|
||||
},
|
||||
|
||||
_getDiffPreferences: function() {
|
||||
return this._getLoggedIn().then(function(loggedIn) {
|
||||
if (!loggedIn) {
|
||||
// These defaults should match the defaults in
|
||||
// gerrit-extension-api/src/main/jcg/gerrit/extensions/client/DiffPreferencesInfo.java
|
||||
// NOTE: There are some settings that don't apply to PolyGerrit
|
||||
// (Render mode being at least one of them).
|
||||
return Promise.resolve({
|
||||
auto_hide_diff_table_header: true,
|
||||
context: 10,
|
||||
cursor_blink_rate: 0,
|
||||
ignore_whitespace: 'IGNORE_NONE',
|
||||
intraline_difference: true,
|
||||
line_length: 100,
|
||||
show_line_endings: true,
|
||||
show_tabs: true,
|
||||
show_whitespace_errors: true,
|
||||
syntax_highlighting: true,
|
||||
tab_size: 8,
|
||||
theme: 'DEFAULT',
|
||||
});
|
||||
}
|
||||
return this.$.restAPI.getDiffPreferences();
|
||||
}.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);
|
||||
return Promise.resolve({
|
||||
meta: {
|
||||
path: this.path,
|
||||
changeNum: this.changeNum,
|
||||
patchRange: this.patchRange,
|
||||
projectConfig: this.projectConfig,
|
||||
},
|
||||
left: results.comments.baseComments.concat(baseDrafts),
|
||||
right: results.comments.comments.concat(drafts),
|
||||
});
|
||||
},
|
||||
|
||||
_getLoggedIn: function() {
|
||||
return this.$.restAPI.getLoggedIn();
|
||||
},
|
||||
|
||||
_getDiffBuilder: function(diff, comments, prefs) {
|
||||
if (this._viewMode === DiffViewMode.SIDE_BY_SIDE) {
|
||||
return new GrDiffBuilderSideBySide(diff, comments, prefs,
|
||||
this.$.diffTable);
|
||||
} else if (this._viewMode === DiffViewMode.UNIFIED) {
|
||||
return new GrDiffBuilderUnified(diff, comments, prefs,
|
||||
this.$.diffTable);
|
||||
}
|
||||
throw Error('Unsupported diff view mode: ' + this._viewMode);
|
||||
},
|
||||
|
||||
_projectConfigChanged: function(projectConfig) {
|
||||
var threadEls = this._getCommentThreads();
|
||||
for (var i = 0; i < threadEls.length; i++) {
|
||||
threadEls[i].projectConfig = projectConfig;
|
||||
}
|
||||
},
|
||||
});
|
||||
})();
|
@@ -1,244 +0,0 @@
|
||||
<!DOCTYPE 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.
|
||||
-->
|
||||
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-new-diff</title>
|
||||
|
||||
<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
|
||||
<script src="../../../bower_components/web-component-tester/browser.js"></script>
|
||||
|
||||
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
|
||||
<link rel="import" href="gr-new-diff.html">
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
<gr-new-diff></gr-new-diff>
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-new-diff tests', function() {
|
||||
var element;
|
||||
|
||||
setup(function() {
|
||||
stub('gr-rest-api-interface', {
|
||||
getLoggedIn: function() { return Promise.resolve(false); },
|
||||
})
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
test('get drafts logged out', function(done) {
|
||||
element.patchRange = {basePatchNum: 0, patchNum: 0};
|
||||
|
||||
var getDraftsStub = sinon.stub(element.$.restAPI, 'getDiffDrafts');
|
||||
var loggedInStub = sinon.stub(element, '_getLoggedIn',
|
||||
function() { return Promise.resolve(false); });
|
||||
element._getDiffDrafts().then(function(result) {
|
||||
assert.deepEqual(result, {baseComments: [], comments: []});
|
||||
sinon.assert.notCalled(getDraftsStub);
|
||||
loggedInStub.restore();
|
||||
getDraftsStub.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('get drafts logged in', function(done) {
|
||||
element.patchRange = {basePatchNum: 0, patchNum: 0};
|
||||
var draftsResponse = {
|
||||
baseComments: [{id: 'foo'}],
|
||||
comments: [{id: 'bar'}],
|
||||
};
|
||||
var getDraftsStub = sinon.stub(element.$.restAPI, 'getDiffDrafts',
|
||||
function() { return Promise.resolve(draftsResponse); });
|
||||
var loggedInStub = sinon.stub(element, '_getLoggedIn',
|
||||
function() { return Promise.resolve(true); });
|
||||
element._getDiffDrafts().then(function(result) {
|
||||
assert.deepEqual(result, draftsResponse);
|
||||
loggedInStub.restore();
|
||||
getDraftsStub.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('get comments and drafts', function(done) {
|
||||
var loggedInStub = sinon.stub(element, '_getLoggedIn',
|
||||
function() { return Promise.resolve(true); });
|
||||
var comments = {
|
||||
baseComments: [
|
||||
{id: 'bc1'},
|
||||
{id: 'bc2'},
|
||||
],
|
||||
comments: [
|
||||
{id: 'c1'},
|
||||
{id: 'c2'},
|
||||
],
|
||||
};
|
||||
var diffCommentsStub = sinon.stub(element, '_getDiffComments',
|
||||
function() { return Promise.resolve(comments); });
|
||||
|
||||
var drafts = {
|
||||
baseComments: [
|
||||
{id: 'bd1'},
|
||||
{id: 'bd2'},
|
||||
],
|
||||
comments: [
|
||||
{id: 'd1'},
|
||||
{id: 'd2'},
|
||||
],
|
||||
};
|
||||
var diffDraftsStub = sinon.stub(element, '_getDiffDrafts',
|
||||
function() { return Promise.resolve(drafts); });
|
||||
|
||||
element.changeNum = '42';
|
||||
element.patchRange = {
|
||||
basePatchNum: 'PARENT',
|
||||
patchNum: 3,
|
||||
};
|
||||
element.path = '/path/to/foo';
|
||||
element.projectConfig = {foo: 'bar'};
|
||||
|
||||
element._getDiffCommentsAndDrafts().then(function(result) {
|
||||
assert.deepEqual(result, {
|
||||
meta: {
|
||||
changeNum: '42',
|
||||
patchRange: {
|
||||
basePatchNum: 'PARENT',
|
||||
patchNum: 3,
|
||||
},
|
||||
path: '/path/to/foo',
|
||||
projectConfig: {foo: 'bar'},
|
||||
},
|
||||
left: [
|
||||
{id: 'bc1'},
|
||||
{id: 'bc2'},
|
||||
{id: 'bd1', __draft: true},
|
||||
{id: 'bd2', __draft: true},
|
||||
],
|
||||
right: [
|
||||
{id: 'c1'},
|
||||
{id: 'c2'},
|
||||
{id: 'd1', __draft: true},
|
||||
{id: 'd2', __draft: true},
|
||||
],
|
||||
});
|
||||
|
||||
diffCommentsStub.restore();
|
||||
diffDraftsStub.restore();
|
||||
loggedInStub.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('remove comment', function() {
|
||||
element._comments = {
|
||||
meta: {
|
||||
changeNum: '42',
|
||||
patchRange: {
|
||||
basePatchNum: 'PARENT',
|
||||
patchNum: 3,
|
||||
},
|
||||
path: '/path/to/foo',
|
||||
projectConfig: {foo: 'bar'},
|
||||
},
|
||||
left: [
|
||||
{id: 'bc1'},
|
||||
{id: 'bc2'},
|
||||
{id: 'bd1', __draft: true},
|
||||
{id: 'bd2', __draft: true},
|
||||
],
|
||||
right: [
|
||||
{id: 'c1'},
|
||||
{id: 'c2'},
|
||||
{id: 'd1', __draft: true},
|
||||
{id: 'd2', __draft: true},
|
||||
],
|
||||
};
|
||||
|
||||
element._removeComment({});
|
||||
assert.deepEqual(element._comments, {
|
||||
meta: {
|
||||
changeNum: '42',
|
||||
patchRange: {
|
||||
basePatchNum: 'PARENT',
|
||||
patchNum: 3,
|
||||
},
|
||||
path: '/path/to/foo',
|
||||
projectConfig: {foo: 'bar'},
|
||||
},
|
||||
left: [
|
||||
{id: 'bc1'},
|
||||
{id: 'bc2'},
|
||||
{id: 'bd1', __draft: true},
|
||||
{id: 'bd2', __draft: true},
|
||||
],
|
||||
right: [
|
||||
{id: 'c1'},
|
||||
{id: 'c2'},
|
||||
{id: 'd1', __draft: true},
|
||||
{id: 'd2', __draft: true},
|
||||
],
|
||||
});
|
||||
|
||||
element._removeComment({id: 'bc2'});
|
||||
assert.deepEqual(element._comments, {
|
||||
meta: {
|
||||
changeNum: '42',
|
||||
patchRange: {
|
||||
basePatchNum: 'PARENT',
|
||||
patchNum: 3,
|
||||
},
|
||||
path: '/path/to/foo',
|
||||
projectConfig: {foo: 'bar'},
|
||||
},
|
||||
left: [
|
||||
{id: 'bc1'},
|
||||
{id: 'bd1', __draft: true},
|
||||
{id: 'bd2', __draft: true},
|
||||
],
|
||||
right: [
|
||||
{id: 'c1'},
|
||||
{id: 'c2'},
|
||||
{id: 'd1', __draft: true},
|
||||
{id: 'd2', __draft: true},
|
||||
],
|
||||
});
|
||||
|
||||
element._removeComment({id: 'd2'});
|
||||
assert.deepEqual(element._comments, {
|
||||
meta: {
|
||||
changeNum: '42',
|
||||
patchRange: {
|
||||
basePatchNum: 'PARENT',
|
||||
patchNum: 3,
|
||||
},
|
||||
path: '/path/to/foo',
|
||||
projectConfig: {foo: 'bar'},
|
||||
},
|
||||
left: [
|
||||
{id: 'bc1'},
|
||||
{id: 'bd1', __draft: true},
|
||||
{id: 'bd2', __draft: true},
|
||||
],
|
||||
right: [
|
||||
{id: 'c1'},
|
||||
{id: 'c2'},
|
||||
{id: 'd1', __draft: true},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
@@ -39,15 +39,13 @@ limitations under the License.
|
||||
'../elements/change-list/gr-change-list-item/gr-change-list-item_test.html',
|
||||
'../elements/core/gr-account-dropdown/gr-account-dropdown_test.html',
|
||||
'../elements/core/gr-search-bar/gr-search-bar_test.html',
|
||||
'../elements/diff/gr-diff/gr-diff-builder_test.html',
|
||||
'../elements/diff/gr-diff/gr-diff-group_test.html',
|
||||
'../elements/diff/gr-diff/gr-diff_test.html',
|
||||
'../elements/diff/gr-diff-comment/gr-diff-comment_test.html',
|
||||
'../elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html',
|
||||
'../elements/diff/gr-diff-preferences/gr-diff-preferences_test.html',
|
||||
'../elements/diff/gr-diff-side/gr-diff-side_test.html',
|
||||
'../elements/diff/gr-diff-view/gr-diff-view_test.html',
|
||||
'../elements/diff/gr-new-diff/gr-diff-builder_test.html',
|
||||
'../elements/diff/gr-new-diff/gr-diff-group_test.html',
|
||||
'../elements/diff/gr-new-diff/gr-new-diff_test.html',
|
||||
'../elements/diff/gr-patch-range-select/gr-patch-range-select_test.html',
|
||||
'../elements/shared/gr-account-label/gr-account-label_test.html',
|
||||
'../elements/shared/gr-account-link/gr-account-link_test.html',
|
||||
|
Reference in New Issue
Block a user