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><span class="key">a</span></td>
|
||||||
<td>Review and publish comments</td>
|
<td>Review and publish comments</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="key">,</span></td>
|
||||||
|
<td>Show diff preferences</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</main>
|
</main>
|
||||||
|
@@ -44,7 +44,6 @@ limitations under the License.
|
|||||||
draft="[[comment.__draft]]"
|
draft="[[comment.__draft]]"
|
||||||
show-actions="[[_showActions]]"
|
show-actions="[[_showActions]]"
|
||||||
project-config="[[projectConfig]]"
|
project-config="[[projectConfig]]"
|
||||||
on-height-change="_handleCommentHeightChange"
|
|
||||||
on-reply="_handleCommentReply"
|
on-reply="_handleCommentReply"
|
||||||
on-comment-discard="_handleCommentDiscard"
|
on-comment-discard="_handleCommentDiscard"
|
||||||
on-done="_handleCommentDone"></gr-diff-comment>
|
on-done="_handleCommentDone"></gr-diff-comment>
|
||||||
|
@@ -17,12 +17,6 @@
|
|||||||
Polymer({
|
Polymer({
|
||||||
is: 'gr-diff-comment-thread',
|
is: 'gr-diff-comment-thread',
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when the height of the thread changes.
|
|
||||||
*
|
|
||||||
* @event height-change
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired when the thread should be discarded.
|
* Fired when the thread should be discarded.
|
||||||
*
|
*
|
||||||
@@ -44,11 +38,6 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
_showActions: Boolean,
|
_showActions: Boolean,
|
||||||
_boundWindowResizeHandler: {
|
|
||||||
type: Function,
|
|
||||||
value: function() { return this._handleWindowResize.bind(this); }
|
|
||||||
},
|
|
||||||
_lastHeight: Number,
|
|
||||||
_orderedComments: Array,
|
_orderedComments: Array,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -64,12 +53,6 @@
|
|||||||
this._getLoggedIn().then(function(loggedIn) {
|
this._getLoggedIn().then(function(loggedIn) {
|
||||||
this._showActions = loggedIn;
|
this._showActions = loggedIn;
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
window.addEventListener('resize', this._boundWindowResizeHandler);
|
|
||||||
},
|
|
||||||
|
|
||||||
detached: function() {
|
|
||||||
window.removeEventListener('resize', this._boundWindowResizeHandler);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addDraft: function(opt_lineNum) {
|
addDraft: function(opt_lineNum) {
|
||||||
@@ -88,10 +71,6 @@
|
|||||||
return this.$.restAPI.getLoggedIn();
|
return this.$.restAPI.getLoggedIn();
|
||||||
},
|
},
|
||||||
|
|
||||||
_handleWindowResize: function(e) {
|
|
||||||
this._heightChanged();
|
|
||||||
},
|
|
||||||
|
|
||||||
_commentsChanged: function(changeRecord) {
|
_commentsChanged: function(changeRecord) {
|
||||||
this._orderedComments = this._sortedComments(this.comments);
|
this._orderedComments = this._sortedComments(this.comments);
|
||||||
},
|
},
|
||||||
@@ -133,11 +112,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_handleCommentHeightChange: function(e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
this._heightChanged();
|
|
||||||
},
|
|
||||||
|
|
||||||
_handleCommentReply: function(e) {
|
_handleCommentReply: function(e) {
|
||||||
var comment = e.detail.comment;
|
var comment = e.detail.comment;
|
||||||
var quoteStr;
|
var quoteStr;
|
||||||
@@ -153,7 +127,6 @@
|
|||||||
this.async(function() {
|
this.async(function() {
|
||||||
var commentEl = this._commentElWithDraftID(reply.__draftID);
|
var commentEl = this._commentElWithDraftID(reply.__draftID);
|
||||||
commentEl.editing = true;
|
commentEl.editing = true;
|
||||||
this.async(this._heightChanged.bind(this), 1);
|
|
||||||
}.bind(this), 1);
|
}.bind(this), 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -166,7 +139,6 @@
|
|||||||
this.async(function() {
|
this.async(function() {
|
||||||
var commentEl = this._commentElWithDraftID(reply.__draftID);
|
var commentEl = this._commentElWithDraftID(reply.__draftID);
|
||||||
commentEl.save();
|
commentEl.save();
|
||||||
this.async(this._heightChanged.bind(this), 1);
|
|
||||||
}.bind(this), 1);
|
}.bind(this), 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -215,15 +187,6 @@
|
|||||||
this.fire('thread-discard');
|
this.fire('thread-discard');
|
||||||
return;
|
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) {
|
_indexOf: function(comment, arr) {
|
||||||
|
@@ -129,7 +129,6 @@ limitations under the License.
|
|||||||
disabled="{{disabled}}"
|
disabled="{{disabled}}"
|
||||||
rows="4"
|
rows="4"
|
||||||
bind-value="{{_editDraft}}"
|
bind-value="{{_editDraft}}"
|
||||||
on-keyup="_handleTextareaKeyup"
|
|
||||||
on-keydown="_handleTextareaKeydown"></iron-autogrow-textarea>
|
on-keydown="_handleTextareaKeydown"></iron-autogrow-textarea>
|
||||||
<gr-linked-text class="message"
|
<gr-linked-text class="message"
|
||||||
pre
|
pre
|
||||||
|
@@ -17,12 +17,6 @@
|
|||||||
Polymer({
|
Polymer({
|
||||||
is: 'gr-diff-comment',
|
is: 'gr-diff-comment',
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when the height of the comment changes.
|
|
||||||
*
|
|
||||||
* @event height-change
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired when the Reply action is triggered.
|
* Fired when the Reply action is triggered.
|
||||||
*
|
*
|
||||||
@@ -75,10 +69,6 @@
|
|||||||
this.editing = this._editDraft.length == 0;
|
this.editing = this._editDraft.length == 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
attached: function() {
|
|
||||||
this._heightChanged();
|
|
||||||
},
|
|
||||||
|
|
||||||
save: function() {
|
save: function() {
|
||||||
this.comment.message = this._editDraft;
|
this.comment.message = this._editDraft;
|
||||||
this.disabled = true;
|
this.disabled = true;
|
||||||
@@ -101,13 +91,6 @@
|
|||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
_heightChanged: function() {
|
|
||||||
this.async(function() {
|
|
||||||
this.fire('height-change', {height: this.offsetHeight},
|
|
||||||
{bubbles: false});
|
|
||||||
}.bind(this));
|
|
||||||
},
|
|
||||||
|
|
||||||
_draftChanged: function(draft) {
|
_draftChanged: function(draft) {
|
||||||
this.$.container.classList.toggle('draft', draft);
|
this.$.container.classList.toggle('draft', draft);
|
||||||
},
|
},
|
||||||
@@ -126,7 +109,6 @@
|
|||||||
if (this.comment && this.comment.id) {
|
if (this.comment && this.comment.id) {
|
||||||
this.$$('.cancel').hidden = !editing;
|
this.$$('.cancel').hidden = !editing;
|
||||||
}
|
}
|
||||||
this._heightChanged();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_computeLinkToComment: function(comment) {
|
_computeLinkToComment: function(comment) {
|
||||||
@@ -137,12 +119,6 @@
|
|||||||
return draft == null || draft.trim() == '';
|
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) {
|
_handleTextareaKeydown: function(e) {
|
||||||
if (e.keyCode == 27) { // 'esc'
|
if (e.keyCode == 27) { // 'esc'
|
||||||
this._handleCancel(e);
|
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>
|
</h3>
|
||||||
<gr-diff id="diff"
|
<gr-diff id="diff"
|
||||||
change-num="[[_changeNum]]"
|
change-num="[[_changeNum]]"
|
||||||
prefs="{{prefs}}"
|
|
||||||
patch-range="[[_patchRange]]"
|
patch-range="[[_patchRange]]"
|
||||||
path="[[_path]]"
|
path="[[_path]]"
|
||||||
project-config="[[_projectConfig]]"
|
project-config="[[_projectConfig]]"
|
||||||
|
@@ -26,10 +26,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
properties: {
|
properties: {
|
||||||
prefs: {
|
|
||||||
type: Object,
|
|
||||||
notify: true,
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* URL params passed from the router.
|
* URL params passed from the router.
|
||||||
*/
|
*/
|
||||||
|
@@ -32,7 +32,7 @@
|
|||||||
GrDiffBuilder.TAB_REGEX = /\t/g;
|
GrDiffBuilder.TAB_REGEX = /\t/g;
|
||||||
|
|
||||||
GrDiffBuilder.LINE_FEED_HTML =
|
GrDiffBuilder.LINE_FEED_HTML =
|
||||||
'<span class="style-scope gr-new-diff br"></span>';
|
'<span class="style-scope gr-diff br"></span>';
|
||||||
|
|
||||||
GrDiffBuilder.GroupType = {
|
GrDiffBuilder.GroupType = {
|
||||||
ADDED: 'b',
|
ADDED: 'b',
|
||||||
@@ -509,7 +509,7 @@
|
|||||||
|
|
||||||
GrDiffBuilder.prototype._addIntralineHighlights = function(content, html,
|
GrDiffBuilder.prototype._addIntralineHighlights = function(content, html,
|
||||||
highlights) {
|
highlights) {
|
||||||
var START_TAG = '<hl class="style-scope gr-new-diff">';
|
var START_TAG = '<hl class="style-scope gr-diff">';
|
||||||
var END_TAG = '</hl>';
|
var END_TAG = '</hl>';
|
||||||
|
|
||||||
for (var i = 0; i < highlights.length; i++) {
|
for (var i = 0; i < highlights.length; i++) {
|
||||||
@@ -549,7 +549,7 @@
|
|||||||
throw Error('Invalid tab size from preferences.');
|
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) {
|
if (showTabs) {
|
||||||
str += 'withIndicator';
|
str += 'withIndicator';
|
||||||
}
|
}
|
||||||
@@ -569,7 +569,7 @@
|
|||||||
// Since the Polymer DOM utility functions (which would do this
|
// Since the Polymer DOM utility functions (which would do this
|
||||||
// automatically) are not being used for performance reasons, this is
|
// automatically) are not being used for performance reasons, this is
|
||||||
// done manually.
|
// done manually.
|
||||||
el.classList.add('style-scope', 'gr-new-diff');
|
el.classList.add('style-scope', 'gr-diff');
|
||||||
if (!!className) {
|
if (!!className) {
|
||||||
el.classList.add(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-request/gr-request.html">
|
||||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.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-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">
|
<link rel="import" href="../gr-patch-range-select/gr-patch-range-select.html">
|
||||||
|
|
||||||
<dom-module id="gr-diff">
|
<dom-module id="gr-diff">
|
||||||
<template>
|
<template>
|
||||||
<style>
|
<style>
|
||||||
|
:host {
|
||||||
|
--light-remove-highlight-color: #fee;
|
||||||
|
--dark-remove-highlight-color: #ffd4d4;
|
||||||
|
--light-add-highlight-color: #efe;
|
||||||
|
--dark-add-highlight-color: #d4ffd4;
|
||||||
|
}
|
||||||
.loading {
|
.loading {
|
||||||
padding: 0 var(--default-horizontal-margin) 1em;
|
padding: 0 var(--default-horizontal-margin) 1em;
|
||||||
color: #666;
|
color: #666;
|
||||||
@@ -45,16 +51,99 @@ limitations under the License.
|
|||||||
display: flex;
|
display: flex;
|
||||||
font: 12px var(--monospace-font-family);
|
font: 12px var(--monospace-font-family);
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
will-change: transform;
|
||||||
}
|
}
|
||||||
gr-diff-side:first-of-type {
|
table {
|
||||||
--light-highlight-color: #fee;
|
border-collapse: collapse;
|
||||||
--dark-highlight-color: #ffd4d4;
|
|
||||||
}
|
|
||||||
gr-diff-side:last-of-type {
|
|
||||||
--light-highlight-color: #efe;
|
|
||||||
--dark-highlight-color: #d4ffd4;
|
|
||||||
border-right: 1px solid #ddd;
|
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>
|
</style>
|
||||||
<div class="loading" hidden$="[[!_loading]]">Loading...</div>
|
<div class="loading" hidden$="[[!_loading]]">Loading...</div>
|
||||||
<div hidden$="[[_loading]]" hidden>
|
<div hidden$="[[_loading]]" hidden>
|
||||||
@@ -67,44 +156,29 @@ limitations under the License.
|
|||||||
<gr-button link
|
<gr-button link
|
||||||
class="prefsButton"
|
class="prefsButton"
|
||||||
on-tap="_handlePrefsTap"
|
on-tap="_handlePrefsTap"
|
||||||
hidden$="[[!prefs]]"
|
hidden$="[[_computePrefsButtonHidden(_prefs, _loggedIn)]]"
|
||||||
hidden>Diff View Preferences</gr-button>
|
hidden>Diff View Preferences</gr-button>
|
||||||
</div>
|
</div>
|
||||||
<gr-overlay id="prefsOverlay" with-backdrop>
|
<gr-overlay id="prefsOverlay" with-backdrop>
|
||||||
<gr-diff-preferences
|
<gr-diff-preferences
|
||||||
prefs="{{prefs}}"
|
prefs="{{_prefs}}"
|
||||||
on-save="_handlePrefsSave"
|
on-save="_handlePrefsSave"
|
||||||
on-cancel="_handlePrefsCancel"></gr-diff-preferences>
|
on-cancel="_handlePrefsCancel"></gr-diff-preferences>
|
||||||
</gr-overlay>
|
</gr-overlay>
|
||||||
|
|
||||||
<div class="diffContainer">
|
<div class$="[[_computeContainerClass(_loggedIn, _viewMode)]]"
|
||||||
<gr-diff-side id="leftDiff"
|
on-tap="_handleTap"
|
||||||
change-num="[[changeNum]]"
|
on-mousedown="_handleMouseDown"
|
||||||
patch-num="[[patchRange.basePatchNum]]"
|
on-copy="_handleCopy">
|
||||||
path="[[path]]"
|
<table id="diffTable"></table>
|
||||||
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||||
</template>
|
</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>
|
<script src="gr-diff.js"></script>
|
||||||
</dom-module>
|
</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/webcomponentsjs/webcomponents.min.js"></script>
|
||||||
<script src="../../../bower_components/web-component-tester/browser.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="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
|
||||||
<link rel="import" href="gr-diff.html">
|
<link rel="import" href="gr-diff.html">
|
||||||
@@ -35,458 +33,214 @@ limitations under the License.
|
|||||||
<script>
|
<script>
|
||||||
suite('gr-diff tests', function() {
|
suite('gr-diff tests', function() {
|
||||||
var element;
|
var element;
|
||||||
var server;
|
|
||||||
var getDiffStub;
|
|
||||||
var getCommentsStub;
|
|
||||||
|
|
||||||
setup(function() {
|
setup(function() {
|
||||||
|
stub('gr-rest-api-interface', {
|
||||||
|
getLoggedIn: function() { return Promise.resolve(false); },
|
||||||
|
})
|
||||||
element = fixture('basic');
|
element = fixture('basic');
|
||||||
element.changeNum = 42;
|
});
|
||||||
element.path = 'sieve.go';
|
|
||||||
element.prefs = {
|
test('get drafts logged out', function(done) {
|
||||||
context: 10,
|
element.patchRange = {basePatchNum: 0, patchNum: 0};
|
||||||
tab_size: 8,
|
|
||||||
|
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',
|
||||||
getDiffStub = sinon.stub(element.$.restAPI, 'getDiff', function() {
|
function() { return Promise.resolve(draftsResponse); });
|
||||||
return Promise.resolve({
|
var loggedInStub = sinon.stub(element, '_getLoggedIn',
|
||||||
change_type: 'MODIFIED',
|
function() { return Promise.resolve(true); });
|
||||||
content: [
|
element._getDiffDrafts().then(function(result) {
|
||||||
{
|
assert.deepEqual(result, draftsResponse);
|
||||||
ab: [
|
loggedInStub.restore();
|
||||||
'<!DOCTYPE html>',
|
getDraftsStub.restore();
|
||||||
'<meta charset="utf-8">',
|
done();
|
||||||
'<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',
|
test('get comments and drafts', function(done) {
|
||||||
function() {
|
var loggedInStub = sinon.stub(element, '_getLoggedIn',
|
||||||
return Promise.resolve({
|
function() { return Promise.resolve(true); });
|
||||||
|
var comments = {
|
||||||
baseComments: [
|
baseComments: [
|
||||||
{
|
{id: 'bc1'},
|
||||||
author: {
|
{id: 'bc2'},
|
||||||
_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: [
|
comments: [
|
||||||
{
|
{id: 'c1'},
|
||||||
author: {
|
{id: 'c2'},
|
||||||
_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',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
});
|
};
|
||||||
}
|
var diffCommentsStub = sinon.stub(element, '_getDiffComments',
|
||||||
);
|
function() { return Promise.resolve(comments); });
|
||||||
|
|
||||||
server = sinon.fakeServer.create();
|
var drafts = {
|
||||||
server.respondWith(
|
baseComments: [
|
||||||
'PUT',
|
{id: 'bd1'},
|
||||||
'/accounts/self/preferences.diff',
|
{id: 'bd2'},
|
||||||
[
|
],
|
||||||
200,
|
comments: [
|
||||||
{'Content-Type': 'application/json'},
|
{id: 'd1'},
|
||||||
')]}\'\n' +
|
{id: 'd2'},
|
||||||
JSON.stringify({context: 25}),
|
],
|
||||||
]
|
};
|
||||||
);
|
var diffDraftsStub = sinon.stub(element, '_getDiffDrafts',
|
||||||
|
function() { return Promise.resolve(drafts); });
|
||||||
|
|
||||||
});
|
element.changeNum = '42';
|
||||||
|
|
||||||
teardown(function() {
|
|
||||||
getDiffStub.restore();
|
|
||||||
getCommentsStub.restore();
|
|
||||||
server.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('comment rendering', function(done) {
|
|
||||||
element.prefs.context = -1;
|
|
||||||
element._loggedIn = true;
|
|
||||||
element.patchRange = {
|
element.patchRange = {
|
||||||
basePatchNum: 1,
|
basePatchNum: 'PARENT',
|
||||||
patchNum: 2,
|
patchNum: 3,
|
||||||
};
|
};
|
||||||
|
element.path = '/path/to/foo';
|
||||||
|
element.projectConfig = {foo: 'bar'};
|
||||||
|
|
||||||
element.reload().then(function() {
|
element._getDiffCommentsAndDrafts().then(function(result) {
|
||||||
flush(function() {
|
assert.deepEqual(result, {
|
||||||
var leftThreadEls =
|
meta: {
|
||||||
Polymer.dom(element.$.leftDiff.root).querySelectorAll(
|
changeNum: '42',
|
||||||
'gr-diff-comment-thread');
|
patchRange: {
|
||||||
assert.equal(leftThreadEls.length, 1);
|
basePatchNum: 'PARENT',
|
||||||
assert.equal(leftThreadEls[0].comments.length, 1);
|
patchNum: 3,
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
{
|
path: '/path/to/foo',
|
||||||
contentIndex: 1,
|
projectConfig: {foo: 'bar'},
|
||||||
startIndex: 0,
|
|
||||||
endIndex: 33,
|
|
||||||
},
|
},
|
||||||
{
|
left: [
|
||||||
contentIndex: 1,
|
{id: 'bc1'},
|
||||||
startIndex: 75,
|
{id: 'bc2'},
|
||||||
},
|
{id: 'bd1', __draft: true},
|
||||||
{
|
{id: 'bd2', __draft: true},
|
||||||
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: [
|
right: [
|
||||||
' Hello, world!',
|
{id: 'c1'},
|
||||||
|
{id: 'c2'},
|
||||||
|
{id: 'd1', __draft: true},
|
||||||
|
{id: 'd2', __draft: true},
|
||||||
],
|
],
|
||||||
},
|
|
||||||
{
|
|
||||||
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) {
|
diffCommentsStub.restore();
|
||||||
element._loggedIn = false;
|
diffDraftsStub.restore();
|
||||||
|
loggedInStub.restore();
|
||||||
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));
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('visible line length', function() {
|
test('remove comment', function() {
|
||||||
assert.equal(element._visibleLineLength('A'.repeat(5)), 5);
|
element._comments = {
|
||||||
assert.equal(
|
meta: {
|
||||||
element._visibleLineLength('A'.repeat(5) + '\t' + 'A'.repeat(5)), 18);
|
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},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
test('break up common diff chunks', function() {
|
element._removeComment({});
|
||||||
element._groupedBaseComments = {
|
// Using JSON.stringify because Safari 9.1 (11601.5.17.1) doesn’t seem to
|
||||||
1: {},
|
// believe that one object deepEquals another even when they do :-/.
|
||||||
};
|
assert.equal(JSON.stringify(element._comments), JSON.stringify({
|
||||||
element._groupedComments = {
|
meta: {
|
||||||
10: {},
|
changeNum: '42',
|
||||||
};
|
patchRange: {
|
||||||
var ctx = {
|
basePatchNum: 'PARENT',
|
||||||
left: {lineNum: 0},
|
patchNum: 3,
|
||||||
right: {lineNum: 0},
|
|
||||||
};
|
|
||||||
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'],
|
|
||||||
},
|
},
|
||||||
{
|
path: '/path/to/foo',
|
||||||
ab: [
|
projectConfig: {foo: 'bar'},
|
||||||
'',
|
|
||||||
'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: [
|
||||||
__noHighlight: true,
|
{id: 'bc1'},
|
||||||
a: ['software distributed under the License is distributed on an '],
|
{id: 'bc2'},
|
||||||
b: ['software distributed under the License is distributed on an ']
|
{id: 'bd1', __draft: true},
|
||||||
},
|
{id: 'bd2', __draft: true},
|
||||||
{
|
],
|
||||||
ab: [
|
right: [
|
||||||
'"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, ',
|
{id: 'c1'},
|
||||||
'either express or implied. See the License for the specific ',
|
{id: 'c2'},
|
||||||
'language governing permissions and limitations under the License.',
|
{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'},
|
||||||
|
},
|
||||||
|
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>
|
</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/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-account-dropdown/gr-account-dropdown_test.html',
|
||||||
'../elements/core/gr-search-bar/gr-search-bar_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/gr-diff_test.html',
|
||||||
'../elements/diff/gr-diff-comment/gr-diff-comment_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-comment-thread/gr-diff-comment-thread_test.html',
|
||||||
'../elements/diff/gr-diff-preferences/gr-diff-preferences_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-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/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-label/gr-account-label_test.html',
|
||||||
'../elements/shared/gr-account-link/gr-account-link_test.html',
|
'../elements/shared/gr-account-link/gr-account-link_test.html',
|
||||||
|
Reference in New Issue
Block a user