Merge "Add shift+m shortcut to diff view"
diff --git a/java/com/google/gerrit/server/restapi/change/Rebase.java b/java/com/google/gerrit/server/restapi/change/Rebase.java
index 2e2f565..eed0896 100644
--- a/java/com/google/gerrit/server/restapi/change/Rebase.java
+++ b/java/com/google/gerrit/server/restapi/change/Rebase.java
@@ -161,7 +161,8 @@
Base base = rebaseUtil.parseBase(rsrc, str);
if (base == null) {
- throw new ResourceConflictException("base revision is missing: " + str);
+ throw new ResourceConflictException(
+ "base revision is missing from the destination branch: " + str);
}
PatchSet.Id baseId = base.patchSet().getId();
if (change.getId().equals(baseId.getParentKey())) {
diff --git a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
index c7951c4..af982cf 100644
--- a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
+++ b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
@@ -142,6 +142,7 @@
UP_TO_CHANGE: 'UP_TO_CHANGE',
TOGGLE_DIFF_MODE: 'TOGGLE_DIFF_MODE',
REFRESH_CHANGE: 'REFRESH_CHANGE',
+ EDIT_TOPIC: 'EDIT_TOPIC',
NEXT_LINE: 'NEXT_LINE',
PREV_LINE: 'PREV_LINE',
@@ -224,6 +225,8 @@
'Refresh list of changes');
_describe(Shortcut.TOGGLE_CHANGE_STAR, ShortcutSection.ACTIONS,
'Star/unstar change');
+ _describe(Shortcut.EDIT_TOPIC, ShortcutSection.ACTIONS,
+ 'Add a change topic');
_describe(Shortcut.NEXT_LINE, ShortcutSection.DIFFS, 'Go to next line');
_describe(Shortcut.PREV_LINE, ShortcutSection.DIFFS, 'Go to previous line');
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html
index ad12a44..987b63d 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html
@@ -55,7 +55,7 @@
padding: 0 .15em;
}
}
- .hideBranch {
+ .hide {
display: none;
}
</style>
@@ -108,7 +108,7 @@
</iron-autogrow-textarea>
</span>
</section>
- <section>
+ <section class$="[[_computePrivateSectionClass(_privateChangesEnabled)]]">
<label
class="title"
for="privateChangeCheckBox">Private change</label>
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js
index 826a6dc..8e15755 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js
@@ -44,6 +44,7 @@
notify: true,
value: false,
},
+ _privateChangesEnabled: Boolean,
},
behaviors: [
@@ -52,10 +53,23 @@
],
attached() {
- if (!this.repoName) { return; }
- this.$.restAPI.getProjectConfig(this.repoName).then(config => {
- this.privateByDefault = config.private_by_default;
- });
+ if (!this.repoName) { return Promise.resolve(); }
+
+ const promises = [];
+
+ promises.push(this.$.restAPI.getProjectConfig(this.repoName)
+ .then(config => {
+ this.privateByDefault = config.private_by_default;
+ }));
+
+ promises.push(this.$.restAPI.getConfig().then(config => {
+ if (!config) { return; }
+
+ this._privateConfig = config && config.change &&
+ config.change.disable_private_changes;
+ }));
+
+ return Promise.all(promises);
},
observers: [
@@ -63,7 +77,7 @@
],
_computeBranchClass(baseChange) {
- return baseChange ? 'hideBranch' : '';
+ return baseChange ? 'hide' : '';
},
_allowCreate(branch, subject) {
@@ -120,5 +134,9 @@
return false;
}
},
+
+ _computePrivateSectionClass(config) {
+ return config ? 'hide' : '';
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html
index 08c569c..aa4da68 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html
@@ -158,5 +158,15 @@
done();
});
});
+
+ test('_computeBranchClass', () => {
+ assert.equal(element._computeBranchClass(true), 'hide');
+ assert.equal(element._computeBranchClass(false), '');
+ });
+
+ test('_computePrivateSectionClass', () => {
+ assert.equal(element._computePrivateSectionClass(true), 'hide');
+ assert.equal(element._computePrivateSectionClass(false), '');
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
index 799b831..fe043d5 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
@@ -133,6 +133,8 @@
* @return {!Promise}
*/
_repoChanged(repo) {
+ this._loading = true;
+
if (!repo) { return Promise.resolve(); }
return this._reload(repo);
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.html b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.html
index 36a7d76..1d49db9 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.html
@@ -53,7 +53,7 @@
</tr>
<template is="dom-repeat" items="[[item.dashboards]]">
<tr class="table">
- <td class="name"><a href$="[[_getUrl(item.project, item.sections)]]">[[item.path]]</a></td>
+ <td class="name"><a href$="[[_getUrl(item.project, item.id)]]">[[item.path]]</a></td>
<td class="title">[[item.title]]</td>
<td class="desc">[[item.description]]</td>
<td class="inherited">[[_computeInheritedFrom(item.project, item.defining_project)]]</td>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js
index c0fc0cb..7dea7f4 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js
@@ -43,24 +43,24 @@
this.$.restAPI.getRepoDashboards(this.repo, errFn).then(res => {
if (!res) { return Promise.resolve(); }
- // Flatten 2 dimenional array, and sort by id.
+ // Group by ref and sort by id.
const dashboards = res.concat.apply([], res).sort((a, b) =>
- a.id > b.id);
- const customList = dashboards.filter(a => a.ref === 'custom');
- const defaultList = dashboards.filter(a => a.ref === 'default');
+ a.id < b.id ? -1 : 1);
+ const dashboardsByRef = {};
+ dashboards.forEach(d => {
+ if (!dashboardsByRef[d.ref]) {
+ dashboardsByRef[d.ref] = [];
+ }
+ dashboardsByRef[d.ref].push(d);
+ });
+
const dashboardBuilder = [];
- if (customList.length) {
+ Object.keys(dashboardsByRef).sort().forEach(ref => {
dashboardBuilder.push({
- section: 'Custom',
- dashboards: customList,
+ section: ref,
+ dashboards: dashboardsByRef[ref],
});
- }
- if (defaultList.length) {
- dashboardBuilder.push({
- section: 'Default',
- dashboards: defaultList,
- });
- }
+ });
this._dashboards = dashboardBuilder;
this._loading = false;
@@ -68,10 +68,10 @@
});
},
- _getUrl(project, sections) {
- if (!project || !sections) { return ''; }
+ _getUrl(project, id) {
+ if (!project || !id) { return ''; }
- return Gerrit.Nav.getUrlForCustomDashboard(project, sections);
+ return Gerrit.Nav.getUrlForRepoDashboard(project, id);
},
_computeLoadingClass(loading) {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html
index 4d86a0c..94bf5e0 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html
@@ -46,54 +46,61 @@
sandbox.restore();
});
- suite('with default only', () => {
+ suite('dashboard table', () => {
setup(() => {
sandbox.stub(element.$.restAPI, 'getRepoDashboards').returns(
Promise.resolve([
- [
- {
- id: 'default:contributor',
- project: 'gerrit',
- defining_project: 'gerrit',
- ref: 'default',
- path: 'contributor',
- description: 'Own contributions.',
- foreach: 'owner:self',
- url: '/dashboard/?params',
- title: 'Contributor Dashboard',
- sections: [
- {
- name: 'Mine To Rebase',
- query: 'is:open -is:mergeable',
- },
- {
- name: 'My Recently Merged',
- query: 'is:merged limit:10',
- },
- ],
- },
- ],
- [
- {
- id: 'default:open',
- project: 'gerrit',
- defining_project: 'Public-Projects',
- ref: 'default',
- path: 'open',
- description: 'Recent open changes.',
- url: '/dashboard/?params',
- title: 'Open Changes',
- sections: [
- {
- name: 'Open Changes',
- query: 'status:open project:${project} -age:7w',
- },
- ],
- },
- ],
+ {
+ id: 'default:contributor',
+ project: 'gerrit',
+ defining_project: 'gerrit',
+ ref: 'default',
+ path: 'contributor',
+ description: 'Own contributions.',
+ foreach: 'owner:self',
+ url: '/dashboard/?params',
+ title: 'Contributor Dashboard',
+ sections: [
+ {
+ name: 'Mine To Rebase',
+ query: 'is:open -is:mergeable',
+ },
+ {
+ name: 'My Recently Merged',
+ query: 'is:merged limit:10',
+ },
+ ],
+ },
+ {
+ id: 'custom:custom2',
+ project: 'gerrit',
+ defining_project: 'Public-Projects',
+ ref: 'custom',
+ path: 'open',
+ description: 'Recent open changes.',
+ url: '/dashboard/?params',
+ title: 'Open Changes',
+ sections: [
+ {
+ name: 'Open Changes',
+ query: 'status:open project:${project} -age:7w',
+ },
+ ],
+ },
+ {
+ id: 'default:abc',
+ project: 'gerrit',
+ ref: 'default',
+ },
+ {
+ id: 'custom:custom1',
+ project: 'gerrit',
+ ref: 'custom',
+ },
]));
});
- test('loading', done => {
+
+ test('loading, sections, and ordering', done => {
assert.isTrue(element._loading);
assert.notEqual(getComputedStyle(element.$.loadingContainer).display,
'none');
@@ -101,143 +108,20 @@
'none');
element.repo = 'test';
flush(() => {
- assert.equal(element._dashboards.length, 1);
- assert.equal(element._dashboards[0].section, 'Default');
- assert.equal(element._dashboards[0].dashboards.length, 2);
assert.equal(getComputedStyle(element.$.loadingContainer).display,
'none');
assert.notEqual(getComputedStyle(element.$.dashboards).display,
'none');
- done();
- });
- });
- test('dispatched command-tap on button tap', done => {
- element.repo = 'test';
- flush(() => {
- assert.equal(element._dashboards.length, 1);
- assert.equal(element._dashboards[0].section, 'Default');
- assert.equal(element._dashboards[0].dashboards.length, 2);
- done();
- });
- });
- });
-
- suite('with custom only', () => {
- setup(() => {
- sandbox.stub(element.$.restAPI, 'getRepoDashboards').returns(
- Promise.resolve([
- [
- {
- id: 'custom:custom1',
- project: 'gerrit',
- defining_project: 'gerrit',
- ref: 'custom',
- path: 'contributor',
- description: 'Own contributions.',
- foreach: 'owner:self',
- url: '/dashboard/?params',
- title: 'Contributor Dashboard',
- sections: [
- {
- name: 'Mine To Rebase',
- query: 'is:open -is:mergeable',
- },
- {
- name: 'My Recently Merged',
- query: 'is:merged limit:10',
- },
- ],
- },
- ],
- [
- {
- id: 'custom:custom2',
- project: 'gerrit',
- defining_project: 'Public-Projects',
- ref: 'custom',
- path: 'open',
- description: 'Recent open changes.',
- url: '/dashboard/?params',
- title: 'Open Changes',
- sections: [
- {
- name: 'Open Changes',
- query: 'status:open project:${project} -age:7w',
- },
- ],
- },
- ],
- ]));
- });
-
- test('dispatched command-tap on button tap', done => {
- element.repo = 'test';
- flush(() => {
- assert.equal(element._dashboards.length, 1);
- assert.equal(element._dashboards[0].section, 'Custom');
- assert.equal(element._dashboards[0].dashboards.length, 2);
- done();
- });
- });
- });
-
- suite('with custom and default', () => {
- setup(() => {
- sandbox.stub(element.$.restAPI, 'getRepoDashboards').returns(
- Promise.resolve([
- [
- {
- id: 'default:contributor',
- project: 'gerrit',
- defining_project: 'gerrit',
- ref: 'default',
- path: 'contributor',
- description: 'Own contributions.',
- foreach: 'owner:self',
- url: '/dashboard/?params',
- title: 'Contributor Dashboard',
- sections: [
- {
- name: 'Mine To Rebase',
- query: 'is:open -is:mergeable',
- },
- {
- name: 'My Recently Merged',
- query: 'is:merged limit:10',
- },
- ],
- },
- ],
- [
- {
- id: 'custom:custom2',
- project: 'gerrit',
- defining_project: 'Public-Projects',
- ref: 'custom',
- path: 'open',
- description: 'Recent open changes.',
- url: '/dashboard/?params',
- title: 'Open Changes',
- sections: [
- {
- name: 'Open Changes',
- query: 'status:open project:${project} -age:7w',
- },
- ],
- },
- ],
- ]));
- });
-
- test('dispatched command-tap on button tap', done => {
- element.repo = 'test';
- flush(() => {
assert.equal(element._dashboards.length, 2);
- assert.equal(element._dashboards[0].section, 'Custom');
- assert.equal(element._dashboards[1].section, 'Default');
- assert.equal(element._dashboards[0].dashboards.length, 1);
- assert.equal(element._dashboards[1].dashboards.length, 1);
+ assert.equal(element._dashboards[0].section, 'custom');
+ assert.equal(element._dashboards[1].section, 'default');
+
+ const dashboards = element._dashboards[0].dashboards;
+ assert.equal(dashboards.length, 2);
+ assert.equal(dashboards[0].id, 'custom:custom1');
+ assert.equal(dashboards[1].id, 'custom:custom2');
+
done();
});
});
@@ -245,7 +129,7 @@
suite('test url', () => {
test('_getUrl', () => {
- sandbox.stub(Gerrit.Nav, 'getUrlForCustomDashboard',
+ sandbox.stub(Gerrit.Nav, 'getUrlForRepoDashboard',
() => '/r/dashboard/test');
assert.equal(element._getUrl('/dashboard/test', {}), '/r/dashboard/test');
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
index 99aa265..b0ba8a2b 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
@@ -87,7 +87,7 @@
<div hidden$="[[_loading]]" hidden>
<gr-user-header
user-id="[[params.user]]"
- class$="[[_computeUserHeaderClass(params.user)]]"></gr-user-header>
+ class$="[[_computeUserHeaderClass(params)]]"></gr-user-header>
<gr-change-list
show-star
show-reviewed-state
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
index 3fc4089..0625bbe 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
@@ -37,7 +37,7 @@
/** @type {{ selectedChangeIndex: number }} */
viewState: Object,
- /** @type {{ user: string }} */
+ /** @type {{ project: string, user: string }} */
params: {
type: Object,
},
@@ -217,8 +217,12 @@
});
},
- _computeUserHeaderClass(userParam) {
- return userParam === 'self' ? 'hide' : '';
+ _computeUserHeaderClass(params) {
+ if (!params || !!params.project || !params.user
+ || params.user === 'self') {
+ return 'hide';
+ }
+ return '';
},
_handleToggleStar(e) {
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
index 78fdee6..618ec65 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
@@ -314,10 +314,13 @@
});
test('_computeUserHeaderClass', () => {
- assert.equal(element._computeUserHeaderClass(undefined), '');
- assert.equal(element._computeUserHeaderClass(''), '');
- assert.equal(element._computeUserHeaderClass('self'), 'hide');
- assert.equal(element._computeUserHeaderClass('user'), '');
+ assert.equal(element._computeUserHeaderClass(undefined), 'hide');
+ assert.equal(element._computeUserHeaderClass({}), 'hide');
+ assert.equal(element._computeUserHeaderClass({user: 'self'}), 'hide');
+ assert.equal(element._computeUserHeaderClass({user: 'user'}), '');
+ assert.equal(
+ element._computeUserHeaderClass({project: 'p', user: 'user'}),
+ 'hide');
});
test('404 page', done => {
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index e521576..6509bb1 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -244,6 +244,7 @@
is="dom-if"
if="[[_showAddTopic(change.*, _settingTopic)]]">
<gr-editable-label
+ class="topicEditableLabel"
label-text="Add a topic"
value="[[change.topic]]"
max-length="1024"
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index 8b119d8..8d1546b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -343,5 +343,12 @@
_computeIsMutable(account) {
return !!Object.keys(account).length;
},
+
+ editTopic() {
+ if (this._topicReadOnly || this.change.topic) { return; }
+ // Cannot use `this.$.ID` syntax because the element exists inside of a
+ // dom-if.
+ this.$$('.topicEditableLabel').open();
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
index 17c3d70..af25d91 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
@@ -587,6 +587,20 @@
});
});
+ test('editTopic', () => {
+ element.account = {test: true};
+ element.change = {actions: {topic: {enabled: true}}};
+ flushAsynchronousOperations();
+
+ const label = element.$$('.topicEditableLabel');
+ assert.ok(label);
+ sandbox.stub(label, 'open');
+ element.editTopic();
+ flushAsynchronousOperations();
+
+ assert.isTrue(label.open.called);
+ });
+
suite('plugin endpoints', () => {
test('endpoint params', done => {
element.change = {labels: {}};
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 24453be..bb51fcc 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -291,6 +291,7 @@
[this.Shortcut.EXPAND_ALL_MESSAGES]: '_handleExpandAllMessages',
[this.Shortcut.COLLAPSE_ALL_MESSAGES]: '_handleCollapseAllMessages',
[this.Shortcut.OPEN_DIFF_PREFS]: '_handleOpenDiffPrefsShortcut',
+ [this.Shortcut.EDIT_TOPIC]: '_handleEditTopic',
};
},
@@ -461,9 +462,9 @@
},
_handleCommentSave(e) {
- if (!e.target.comment.__draft) { return; }
+ const draft = e.detail.comment;
+ if (!draft.__draft) { return; }
- const draft = e.target.comment;
draft.patch_set = draft.patch_set || this._patchRange.patchNum;
// The use of path-based notification helpers (set, push) can’t be used
@@ -493,9 +494,9 @@
},
_handleCommentDiscard(e) {
- if (!e.target.comment.__draft) { return; }
+ const draft = e.detail.comment;
+ if (!draft.__draft) { return; }
- const draft = e.target.comment;
if (!this._diffDrafts[draft.path]) {
return;
}
@@ -944,6 +945,14 @@
this.$.downloadOverlay.open();
},
+ _handleEditTopic(e) {
+ if (this.shouldSuppressKeyboardShortcut(e) ||
+ this.modifierPressed(e)) { return; }
+
+ e.preventDefault();
+ this.$.metadata.editTopic();
+ },
+
_handleRefreshChange(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
e.preventDefault();
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index df06e55..f9745b8 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -54,6 +54,7 @@
kb.bindShortcut(kb.Shortcut.EXPAND_ALL_MESSAGES, 'x');
kb.bindShortcut(kb.Shortcut.COLLAPSE_ALL_MESSAGES, 'z');
kb.bindShortcut(kb.Shortcut.OPEN_DIFF_PREFS, ',');
+ kb.bindShortcut(kb.Shortcut.EDIT_TOPIC, 't');
let element;
let sandbox;
@@ -109,6 +110,12 @@
});
suite('keyboard shortcuts', () => {
+ test('t to add topic', () => {
+ const editStub = sandbox.stub(element.$.metadata, 'editTopic');
+ MockInteractions.pressAndReleaseKeyOn(element, 83, null, 't');
+ assert(editStub.called);
+ });
+
test('S should toggle the CL star', () => {
const starStub = sandbox.stub(element.$.changeStar, 'toggleStar');
MockInteractions.pressAndReleaseKeyOn(element, 83, null, 's');
@@ -658,12 +665,12 @@
path: '/foo/bar.txt',
text: 'hello',
};
- element._handleCommentSave({target: {comment: draft}});
+ element._handleCommentSave({detail: {comment: draft}});
draft.patch_set = 2;
assert.deepEqual(element._diffDrafts, {'/foo/bar.txt': [draft]});
draft.patch_set = null;
draft.text = 'hello, there';
- element._handleCommentSave({target: {comment: draft}});
+ element._handleCommentSave({detail: {comment: draft}});
draft.patch_set = 2;
assert.deepEqual(element._diffDrafts, {'/foo/bar.txt': [draft]});
const draft2 = {
@@ -672,14 +679,14 @@
path: '/foo/bar.txt',
text: 'hola',
};
- element._handleCommentSave({target: {comment: draft2}});
+ element._handleCommentSave({detail: {comment: draft2}});
draft2.patch_set = 2;
assert.deepEqual(element._diffDrafts, {'/foo/bar.txt': [draft, draft2]});
draft.patch_set = null;
- element._handleCommentDiscard({target: {comment: draft}});
+ element._handleCommentDiscard({detail: {comment: draft}});
draft.patch_set = 2;
assert.deepEqual(element._diffDrafts, {'/foo/bar.txt': [draft2]});
- element._handleCommentDiscard({target: {comment: draft2}});
+ element._handleCommentDiscard({detail: {comment: draft2}});
assert.deepEqual(element._diffDrafts, {});
});
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
index 7278c5a..b1433a3 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
@@ -522,14 +522,15 @@
/**
* @param {string} repo The name of the repo.
- * @param {!Array} sections The sections to display in the dashboard
+ * @param {string} dashboard The ID of the dashboard, in the form of
+ * '<ref>:<path>'.
* @return {string}
*/
- getUrlForCustomDashboard(repo, sections) {
+ getUrlForRepoDashboard(repo, dashboard) {
return this._getUrlFor({
- repo,
view: Gerrit.Nav.View.DASHBOARD,
- sections,
+ repo,
+ dashboard,
});
},
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index 0f2bbff..f4f36b3 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -427,7 +427,8 @@
return `/dashboard/${user}?${queryParams.join('&')}`;
} else if (repoName) {
// Project dashboard.
- return `/p/${repoName}/+/dashboard/${params.dashboard}`;
+ const encodedRepo = this.encodeURL(repoName, true);
+ return `/p/${encodedRepo}/+/dashboard/${params.dashboard}`;
} else {
// User dashboard.
return `/dashboard/${params.user || 'self'}`;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
index e26201a..e77eb57 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
@@ -29,7 +29,7 @@
</div>
<gr-ranged-comment-layer
id="rangeLayer"
- comments="[[comments]]"></gr-ranged-comment-layer>
+ comment-ranges="[[commentRanges]]"></gr-ranged-comment-layer>
<gr-syntax-layer
id="syntaxLayer"
diff="[[diff]]"></gr-syntax-layer>
@@ -109,7 +109,6 @@
changeNum: String,
patchNum: String,
viewMode: String,
- comments: Object,
isImageDiff: Boolean,
baseImage: Object,
revisionImage: Object,
@@ -125,6 +124,10 @@
_groups: Array,
_layers: Array,
_showTabs: Boolean,
+ /** @type {!Array<!Gerrit.HoveredRange>} */
+ commentRanges: {
+ type: Array,
+ },
},
get diffElement() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
index a855833..c277f34 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
@@ -389,7 +389,7 @@
test('_handlePreferenceError called with invalid preference', () => {
sandbox.stub(element, '_handlePreferenceError');
const prefs = {tab_size: 0};
- element._getDiffBuilder(element.diff, element.comments, prefs);
+ element._getDiffBuilder(element.diff, undefined, prefs);
assert.isTrue(element._handlePreferenceError.lastCall
.calledWithExactly('tab size'));
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
index f3e3249..a2439d7 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
@@ -48,11 +48,12 @@
* version is the one whose line number column is further to the left.
*
* range:
- * The range of text that the comment refers to (startLine, startChar,
- * endLine, endChar), serialized as JSON. If set, range's startLine
- * will have the same value as line-num. Line numbers are 1-based,
- * char numbers are 0-based. The start position (startLine, startChar)
- * is inclusive, and the end position (endLine, endChar) is exclusive.
+ * The range of text that the comment refers to (start_line,
+ * start_character, end_line, end_character), serialized as JSON. If
+ * set, range's end_line will have the same value as line-num. Line
+ * numbers are 1-based, char numbers are 0-based. The start position
+ * (start_line, start_character) is inclusive, and the end position
+ * (end_line, end_character) is exclusive.
*/
properties: {
changeNum: String,
@@ -61,8 +62,8 @@
value() { return []; },
},
/**
- * @type {?{startLine: number, startChar: number, endLine: number,
- * endChar: number}}
+ * @type {?{start_line: number, start_character: number, end_line: number,
+ * end_character: number}}
*/
range: {
type: Object,
@@ -390,12 +391,7 @@
d.line = opt_lineNum;
}
if (opt_range) {
- d.range = {
- start_line: opt_range.startLine,
- start_character: opt_range.startChar,
- end_line: opt_range.endLine,
- end_character: opt_range.endChar,
- };
+ d.range = opt_range;
}
if (this.parentIndex) {
d.parent = this.parentIndex;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
index 58648bf..1881497 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
@@ -724,15 +724,25 @@
});
test('reflects range to JSON serialized attribute if set', () => {
- element.range = {startLine: 4, endLine: 5, startChar: 6, endChar: 7};
+ element.range = {
+ start_line: 4,
+ end_line: 5,
+ start_character: 6,
+ end_character: 7,
+ };
assert.deepEqual(
JSON.parse(element.getAttribute('range')),
- {startLine: 4, endLine: 5, startChar: 6, endChar: 7});
+ {start_line: 4, end_line: 5, start_character: 6, end_character: 7});
});
test('removes range attribute if range is unset', () => {
- element.range = {startLine: 4, endLine: 5, startChar: 6, endChar: 7};
+ element.range = {
+ start_line: 4,
+ end_line: 5,
+ start_character: 6,
+ end_character: 7,
+ };
element.range = undefined;
assert.notOk(element.hasAttribute('range'));
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
index 577eec6..85ba202 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
@@ -21,7 +21,11 @@
is: 'gr-diff-highlight',
properties: {
- comments: Object,
+ /** @type {!Array<!Gerrit.HoveredRange>} */
+ commentRanges: {
+ type: Array,
+ notify: true,
+ },
loggedIn: Boolean,
/**
* querySelector can return null, so needs to be nullable.
@@ -71,35 +75,44 @@
},
_handleCommentMouseOver(e) {
- const comment = e.detail.comment;
- if (!comment.range) { return; }
- const lineEl = this.diffBuilder.getLineElByChild(e.target);
- const side = this.diffBuilder.getSideByLineEl(lineEl);
- const index = this._indexOfComment(side, comment);
+ const threadEl = Polymer.dom(e).localTarget;
+ const index = this._indexForThreadEl(threadEl);
+
if (index !== undefined) {
- this.set(['comments', side, index, '__hovering'], true);
+ this.set(['commentRanges', index, 'hovering'], true);
}
},
_handleCommentMouseOut(e) {
- const comment = e.detail.comment;
- if (!comment.range) { return; }
- const lineEl = this.diffBuilder.getLineElByChild(e.target);
- const side = this.diffBuilder.getSideByLineEl(lineEl);
- const index = this._indexOfComment(side, comment);
+ const threadEl = Polymer.dom(e).localTarget;
+ const index = this._indexForThreadEl(threadEl);
+
if (index !== undefined) {
- this.set(['comments', side, index, '__hovering'], false);
+ this.set(['commentRanges', index, 'hovering'], false);
}
},
- _indexOfComment(side, comment) {
- const idProp = comment.id ? 'id' : '__draftID';
- for (let i = 0; i < this.comments[side].length; i++) {
- if (comment[idProp] &&
- this.comments[side][i][idProp] === comment[idProp]) {
- return i;
- }
+ _indexForThreadEl(threadEl) {
+ const side = threadEl.getAttribute('comment-side');
+ const range = JSON.parse(threadEl.getAttribute('range'));
+
+ if (!range) return undefined;
+
+ return this._indexOfCommentRange(side, range);
+ },
+
+ _indexOfCommentRange(side, range) {
+ function rangesEqual(a, b) {
+ if (!a && !b) { return true; }
+ if (!a || !b) { return false; }
+ return a.start_line === b.start_line &&
+ a.start_character === b.start_character &&
+ a.end_line === b.end_line &&
+ a.end_character === b.end_character;
}
+
+ return this.commentRanges.findIndex(commentRange =>
+ commentRange.side === side && rangesEqual(commentRange.range, range));
},
/**
@@ -295,10 +308,10 @@
const root = Polymer.dom(this.root);
root.insertBefore(actionBox, root.firstElementChild);
actionBox.range = {
- startLine: start.line,
- startChar: start.column,
- endLine: end.line,
- endChar: end.column,
+ start_line: start.line,
+ start_character: start.column,
+ end_line: end.line,
+ end_character: end.column,
};
actionBox.side = start.side;
if (start.line === end.line) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
index 98d55c0..23de407 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
@@ -205,7 +205,7 @@
test('comment-mouse-over from ranged comment causes set', () => {
sandbox.stub(element, 'set');
- sandbox.stub(element, '_indexOfComment').returns(0);
+ sandbox.stub(element, '_indexForThreadEl').returns(0);
element.fire('comment-mouse-over', {comment: {range: {}}});
assert.isTrue(element.set.called);
});
@@ -318,10 +318,10 @@
const actionBox = element.$$('gr-selection-action-box');
assert.isTrue(element.isRangeSelected());
assert.deepEqual(getActionRange(), {
- startLine: 138,
- startChar: 5,
- endLine: 138,
- endChar: 12,
+ start_line: 138,
+ start_character: 5,
+ end_line: 138,
+ end_character: 12,
});
assert.equal(getActionSide(), 'left');
assert.notOk(actionBox.positionBelow);
@@ -337,10 +337,10 @@
const actionBox = element.$$('gr-selection-action-box');
assert.deepEqual(getActionRange(), {
- startLine: 119,
- startChar: 10,
- endLine: 120,
- endChar: 36,
+ start_line: 119,
+ start_character: 10,
+ end_line: 120,
+ end_character: 36,
});
assert.equal(getActionSide(), 'right');
assert.notOk(actionBox.positionBelow);
@@ -370,10 +370,10 @@
element._handleSelection();
assert.isTrue(element.isRangeSelected());
assert.deepEqual(getActionRange(), {
- startLine: 119,
- startChar: 10,
- endLine: 120,
- endChar: 36,
+ start_line: 119,
+ start_character: 10,
+ end_line: 120,
+ end_character: 36,
});
});
@@ -383,10 +383,10 @@
emulateSelection(startContent.firstChild, 10, endContent.firstChild, 2);
assert.isTrue(element.isRangeSelected());
assert.deepEqual(getActionRange(), {
- startLine: 119,
- startChar: 10,
- endLine: 120,
- endChar: 2,
+ start_line: 119,
+ start_character: 10,
+ end_line: 120,
+ end_character: 2,
});
assert.equal(getActionSide(), 'right');
});
@@ -404,10 +404,10 @@
emulateSelection(hl.firstChild, 2, hl.nextSibling, 7);
assert.isTrue(element.isRangeSelected());
assert.deepEqual(getActionRange(), {
- startLine: 140,
- startChar: 8,
- endLine: 140,
- endChar: 23,
+ start_line: 140,
+ start_character: 8,
+ end_line: 140,
+ end_character: 23,
});
assert.equal(getActionSide(), 'left');
});
@@ -418,10 +418,10 @@
emulateSelection(hl.previousSibling, 2, hl.firstChild, 3);
assert.isTrue(element.isRangeSelected());
assert.deepEqual(getActionRange(), {
- startLine: 140,
- startChar: 18,
- endLine: 140,
- endChar: 27,
+ start_line: 140,
+ start_character: 18,
+ end_line: 140,
+ end_character: 27,
});
});
@@ -431,10 +431,10 @@
emulateSelection(content.firstChild, 2, hl.firstChild, 2);
assert.isTrue(element.isRangeSelected());
assert.deepEqual(getActionRange(), {
- startLine: 140,
- startChar: 2,
- endLine: 140,
- endChar: 61,
+ start_line: 140,
+ start_character: 2,
+ end_line: 140,
+ end_character: 61,
});
assert.equal(getActionSide(), 'left');
});
@@ -470,10 +470,10 @@
emulateSelection(comment.firstChild, 2, endContent.firstChild, 4);
assert.isTrue(element.isRangeSelected());
assert.deepEqual(getActionRange(), {
- startLine: 140,
- startChar: 83,
- endLine: 141,
- endChar: 4,
+ start_line: 140,
+ start_character: 83,
+ end_line: 141,
+ end_character: 4,
});
assert.equal(getActionSide(), 'left');
});
@@ -485,10 +485,10 @@
emulateSelection(content.firstChild, 4, comment.firstChild, 1);
assert.isTrue(element.isRangeSelected());
assert.deepEqual(getActionRange(), {
- startLine: 140,
- startChar: 4,
- endLine: 140,
- endChar: 83,
+ start_line: 140,
+ start_character: 4,
+ end_line: 140,
+ end_character: 83,
});
assert.equal(getActionSide(), 'left');
});
@@ -517,10 +517,10 @@
emulateSelection(startContent.firstChild, 3, endContent.firstChild, 14);
assert.isTrue(element.isRangeSelected());
assert.deepEqual(getActionRange(), {
- startLine: 130,
- startChar: 3,
- endLine: 146,
- endChar: 14,
+ start_line: 130,
+ start_character: 3,
+ end_line: 146,
+ end_character: 14,
});
assert.equal(getActionSide(), 'right');
});
@@ -531,10 +531,10 @@
content.firstChild, 1, content.querySelector('span'), 0);
assert.isTrue(element.isRangeSelected());
assert.deepEqual(getActionRange(), {
- startLine: 140,
- startChar: 1,
- endLine: 140,
- endChar: 51,
+ start_line: 140,
+ start_character: 1,
+ end_line: 140,
+ end_character: 51,
});
assert.equal(getActionSide(), 'left');
});
@@ -546,10 +546,10 @@
content.querySelectorAll('span')[1].nextSibling, 1);
assert.isTrue(element.isRangeSelected());
assert.deepEqual(getActionRange(), {
- startLine: 140,
- startChar: 51,
- endLine: 140,
- endChar: 71,
+ start_line: 140,
+ start_character: 51,
+ end_line: 140,
+ end_character: 71,
});
assert.equal(getActionSide(), 'left');
});
@@ -582,10 +582,10 @@
emulateSelection(startContent.firstChild, 0, endContent.firstChild, 0);
assert.isTrue(element.isRangeSelected());
assert.deepEqual(getActionRange(), {
- startLine: 119,
- startChar: 0,
- endLine: 119,
- endChar: element._getLength(startContent),
+ start_line: 119,
+ start_character: 0,
+ end_line: 119,
+ end_character: element._getLength(startContent),
});
assert.equal(getActionSide(), 'right');
});
@@ -597,10 +597,10 @@
endContent.parentElement.previousElementSibling, 0);
assert.isTrue(element.isRangeSelected());
assert.deepEqual(getActionRange(), {
- startLine: 146,
- startChar: 0,
- endLine: 146,
- endChar: 84,
+ start_line: 146,
+ start_character: 0,
+ end_line: 146,
+ end_character: 84,
});
assert.equal(getActionSide(), 'right');
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
index 5e4a3fd..814c7268 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
@@ -45,11 +45,6 @@
return !!(diff.binary && (isA || isB));
}
- /** @typedef {{startLine: number, startChar: number,
- * endLine: number, endChar: number}} */
- Gerrit.Range;
-
-
/**
* Compare two ranges. Either argument may be falsy, but will only return
* true if both are falsy or if neither are falsy and have the same position
@@ -62,10 +57,10 @@
function rangesEqual(a, b) {
if (!a && !b) { return true; }
if (!a || !b) { return false; }
- return a.startLine === b.startLine &&
- a.startChar === b.startChar &&
- a.endLine === b.endLine &&
- a.endChar === b.endChar;
+ return a.start_line === b.start_line &&
+ a.start_character === b.start_character &&
+ a.end_line === b.end_line &&
+ a.end_character === b.end_character;
}
/**
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
index c7ee1c2..423bdc6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
@@ -966,10 +966,10 @@
// Try to fetch a thread with a different range.
range = {
- startLine: 1,
- startChar: 1,
- endLine: 1,
- endChar: 3,
+ start_line: 1,
+ start_character: 1,
+ end_line: 1,
+ end_character: 3,
};
assert.isOk(element._getOrCreateThread(
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index 7f7ec72..862db10 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -280,10 +280,10 @@
<gr-diff-highlight
id="highlights"
logged-in="[[loggedIn]]"
- comments="{{comments}}">
+ comment-ranges="{{_commentRanges}}">
<gr-diff-builder
id="diffBuilder"
- comments="[[comments]]"
+ comment-ranges="[[_commentRanges]]"
project-name="[[projectName]]"
diff="[[diff]]"
diff-path="[[path]]"
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index 76a62b8..f87e46f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -39,6 +39,15 @@
const FULL_CONTEXT = -1;
const LIMITED_CONTEXT = 10;
+ /** @typedef {{start_line: number, start_character: number,
+ * end_line: number, end_character: number}} */
+ Gerrit.Range;
+
+ function isThreadEl(node) {
+ return node.nodeType === Node.ELEMENT_NODE &&
+ node.classList.contains('comment-thread');
+ }
+
Polymer({
is: 'gr-diff',
@@ -96,6 +105,11 @@
type: Object,
value: {left: [], right: []},
},
+ /** @type {!Array<!Gerrit.HoveredRange>} */
+ _commentRanges: {
+ type: Array,
+ value: [],
+ },
lineWrapping: {
type: Boolean,
value: false,
@@ -181,7 +195,19 @@
_diffLength: Number,
- /** @type {?PolymerDomApi.ObserveHandle} */
+ /**
+ * Observes comment nodes added or removed after the initial render.
+ * Can be used to unregister when the entire diff is (re-)rendered or upon
+ * detachment.
+ * @type {?PolymerDomApi.ObserveHandle}
+ */
+ _incrementalNodeObserver: Object,
+
+ /**
+ * Observes comment nodes added or removed at any point.
+ * Can be used to unregister upon detachment.
+ * @type {?PolymerDomApi.ObserveHandle}
+ */
_nodeObserver: Object,
},
@@ -197,10 +223,36 @@
'render-content': '_handleRenderContent',
},
+ attached() {
+ this._updateRangesWhenNodesChange();
+ },
+
detached() {
+ this._unobserveIncrementalNodes();
this._unobserveNodes();
},
+ _updateRangesWhenNodesChange() {
+ function commentRangeFromThreadEl(threadEl) {
+ const side = threadEl.getAttribute('comment-side');
+ const range = JSON.parse(threadEl.getAttribute('range'));
+ return {side, range, hovering: false};
+ }
+
+ this._nodeObserver = Polymer.dom(this).observeNodes(info => {
+ const addedThreadEls = info.addedNodes.filter(isThreadEl);
+ const addedCommentRanges = addedThreadEls
+ .map(commentRangeFromThreadEl)
+ .filter(({range}) => range);
+ this.push('_commentRanges', ...addedCommentRanges);
+ // In principal we should also handle removed nodes, but I have not
+ // figured out how to do that yet without also catching all the removals
+ // caused by further redistribution. Right now, comments are never
+ // removed by no longer slotting them in, so I decided to not handle
+ // this situation until it occurs.
+ });
+ },
+
/** Cancel any remaining diff builder rendering work. */
cancel() {
this.$.diffBuilder.cancel();
@@ -305,7 +357,7 @@
_handleCreateRangeComment(e) {
const range = e.detail.range;
const side = e.detail.side;
- const lineNum = range.endLine;
+ const lineNum = range.end_line;
const lineEl = this.$.diffBuilder.getLineElByNumber(lineNum, side);
if (this._isValidElForComment(lineEl)) {
@@ -573,7 +625,7 @@
},
_renderDiffTable() {
- this._unobserveNodes();
+ this._unobserveIncrementalNodes();
if (!this.prefs) {
this.dispatchEvent(new CustomEvent('render', {bubbles: true}));
return;
@@ -591,19 +643,18 @@
},
_handleRenderContent() {
- this._nodeObserver = Polymer.dom(this).observeNodes(info => {
- const addedThreadEls = info.addedNodes.filter(
- node => node.nodeType === Node.ELEMENT_NODE);
+ this._incrementalNodeObserver = Polymer.dom(this).observeNodes(info => {
+ const addedThreadEls = info.addedNodes.filter(isThreadEl);
// In principal we should also handle removed nodes, but I have not
// figured out how to do that yet without also catching all the removals
// caused by further redistribution. Right now, comments are never
// removed by no longer slotting them in, so I decided to not handle
// this situation until it occurs.
for (const threadEl of addedThreadEls) {
- const lineNum = Number(threadEl.getAttribute('line-num'));
+ const lineNumString = threadEl.getAttribute('line-num') || 'FILE';
const commentSide = threadEl.getAttribute('comment-side');
const lineEl = this.$.diffBuilder.getLineElByNumber(
- lineNum, commentSide);
+ lineNumString, commentSide);
const contentText = this.$.diffBuilder.getContentByLineEl(lineEl);
const contentEl = contentText.parentElement;
const threadGroupEl = this._getOrCreateThreadGroup(contentEl);
@@ -612,6 +663,12 @@
});
},
+ _unobserveIncrementalNodes() {
+ if (this._incrementalNodeObserver) {
+ Polymer.dom(this).unobserveNodes(this._incrementalNodeObserver);
+ }
+ },
+
_unobserveNodes() {
if (this._nodeObserver) {
Polymer.dom(this).unobserveNodes(this._nodeObserver);
@@ -629,7 +686,7 @@
},
clearDiffContent() {
- this._unobserveNodes();
+ this._unobserveIncrementalNodes();
this.$.diffTable.innerHTML = null;
},
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js
index db14fc8..fa488f0 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js
@@ -17,31 +17,34 @@
(function() {
'use strict';
- const HOVER_PATH_PATTERN = /^comments\.(left|right)\.\#(\d+)\.__hovering$/;
- const SPLICE_PATH_PATTERN = /^comments\.(left|right)\.splices$/;
+ const HOVER_PATH_PATTERN = /^commentRanges\.\#(\d+)\.hovering$/;
const RANGE_HIGHLIGHT = 'range';
const HOVER_HIGHLIGHT = 'rangeHighlight';
const NORMALIZE_RANGE_EVENT = 'normalize-range';
+ /** @typedef {{side: string, range: Gerrit.Range, hovering: boolean}} */
+ Gerrit.HoveredRange;
+
Polymer({
is: 'gr-ranged-comment-layer',
properties: {
- comments: Object,
+ /** @type {!Array<!Gerrit.HoveredRange>} */
+ commentRanges: Array,
_listeners: {
type: Array,
value() { return []; },
},
- _commentMap: {
+ _rangesMap: {
type: Object,
- value() { return {left: [], right: []}; },
+ value() { return {left: {}, right: {}}; },
},
},
observers: [
- '_handleCommentChange(comments.*)',
+ '_handleCommentRangesChange(commentRanges.*)',
],
/**
@@ -93,97 +96,78 @@
},
/**
- * Handle change in the comments by updating the comment maps and by
+ * Handle change in the ranges by updating the ranges maps and by
* emitting appropriate update notifications.
* @param {Object} record The change record.
*/
- _handleCommentChange(record) {
- if (!record.path) { return; }
+ _handleCommentRangesChange(record) {
+ if (!record) return;
// If the entire set of comments was changed.
- if (record.path === 'comments') {
- this._commentMap.left = this._computeCommentMap(this.comments.left);
- this._commentMap.right = this._computeCommentMap(this.comments.right);
- return;
+ if (record.path === 'commentRanges') {
+ this._rangesMap = {left: {}, right: {}};
+ for (const {side, range, hovering} of record.value) {
+ this._updateRangesMap(
+ side, range, hovering, (forLine, start, end, hovering) => {
+ forLine.push({start, end, hovering});
+ });
+ }
}
// If the change only changed the `hovering` property of a comment.
- let match = record.path.match(HOVER_PATH_PATTERN);
- let side;
-
+ const match = record.path.match(HOVER_PATH_PATTERN);
if (match) {
- side = match[1];
- const index = match[2];
- const comment = this.comments[side][index];
- if (comment && comment.range) {
- this._commentMap[side] = this._computeCommentMap(this.comments[side]);
- this._notifyUpdateRange(
- comment.range.start_line, comment.range.end_line, side);
- }
- return;
+ const commentRangesIndex = match[1];
+ const {side, range, hovering} = this.commentRanges[commentRangesIndex];
+ this._updateRangesMap(
+ side, range, hovering, (forLine, start, end, hovering) => {
+ const index = forLine.findIndex(lineRange =>
+ lineRange.start === start && lineRange.end === end);
+ forLine[index].hovering = hovering;
+ });
}
// If comments were spliced in or out.
- match = record.path.match(SPLICE_PATH_PATTERN);
- if (match) {
- side = match[1];
- this._commentMap[side] = this._computeCommentMap(this.comments[side]);
- this._handleCommentSplice(record.value, side);
+ if (record.path === 'commentRanges.splices') {
+ for (const indexSplice of record.value.indexSplices) {
+ const removed = indexSplice.removed;
+ for (const {side, range, hovering} of removed) {
+ this._updateRangesMap(
+ side, range, hovering, (forLine, start, end) => {
+ const index = forLine.findIndex(lineRange =>
+ lineRange.start === start && lineRange.end === end);
+ forLine.splice(index, 1);
+ });
+ }
+ const added = indexSplice.object.slice(
+ indexSplice.index, indexSplice.index + indexSplice.addedCount);
+ for (const {side, range, hovering} of added) {
+ this._updateRangesMap(
+ side, range, hovering, (forLine, start, end, hovering) => {
+ forLine.push({start, end, hovering});
+ });
+ }
+ }
}
},
- /**
- * Take a list of comments and return a sparse list mapping line numbers to
- * partial ranges. Uses an end-character-index of -1 to indicate the end of
- * the line.
- * @param {?} commentList The list of comments.
- * Getting this param to match closure requirements caused problems.
- * @return {!Object} The sparse list.
- */
- _computeCommentMap(commentList) {
- const result = {};
- for (const comment of commentList) {
- if (!comment.range) { continue; }
- const range = comment.range;
- for (let line = range.start_line; line <= range.end_line; line++) {
- if (!result[line]) { result[line] = []; }
- result[line].push({
- comment,
- start: line === range.start_line ? range.start_character : 0,
- end: line === range.end_line ? range.end_character : -1,
- });
- }
+ _updateRangesMap(side, range, hovering, operation) {
+ const forSide = this._rangesMap[side] || (this._rangesMap[side] = {});
+ for (let line = range.start_line; line <= range.end_line; line++) {
+ const forLine = forSide[line] || (forSide[line] = []);
+ const start = line === range.start_line ? range.start_character : 0;
+ const end = line === range.end_line ? range.end_character : -1;
+ operation(forLine, start, end, hovering);
}
- return result;
- },
-
- /**
- * Translate a splice record into range update notifications.
- */
- _handleCommentSplice(record, side) {
- if (!record || !record.indexSplices) { return; }
-
- for (const splice of record.indexSplices) {
- const ranges = splice.removed.length ?
- splice.removed.map(c => { return c.range; }) :
- [splice.object[splice.index].range];
- for (const range of ranges) {
- if (!range) { continue; }
- this._notifyUpdateRange(range.start_line, range.end_line, side);
- }
- }
+ this._notifyUpdateRange(range.start_line, range.end_line, side);
},
_getRangesForLine(line, side) {
const lineNum = side === 'left' ? line.beforeNumber : line.afterNumber;
- const ranges = this.get(['_commentMap', side, lineNum]) || [];
+ const ranges = this.get(['_rangesMap', side, lineNum]) || [];
return ranges
.map(range => {
- range = {
- start: range.start,
- end: range.end === -1 ? line.text.length : range.end,
- hovering: !!range.comment.__hovering,
- };
+ range.end = range.end === -1 ? line.text.length : range.end;
// Normalize invalid ranges where the start is after the end but the
// start still makes sense. Set the end to the end of the line.
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
index c541e26..c198ace 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
@@ -40,62 +40,48 @@
let sandbox;
setup(() => {
- const initialComments = {
- left: [
- {
- id: '12345',
- line: 39,
- message: 'range comment',
- range: {
- end_character: 9,
- end_line: 39,
- start_character: 6,
- start_line: 36,
- },
- }, {
- id: '23456',
- line: 100,
- message: 'non range comment',
+ const initialCommentRanges = [
+ {
+ side: 'left',
+ range: {
+ end_character: 9,
+ end_line: 39,
+ start_character: 6,
+ start_line: 36,
},
- ],
- right: [
- {
- id: '34567',
- line: 10,
- message: 'range comment',
- range: {
- end_character: 22,
- end_line: 12,
- start_character: 10,
- start_line: 10,
- },
- }, {
- id: '45678',
- line: 100,
- message: 'single line range comment',
- range: {
- end_character: 15,
- end_line: 100,
- start_character: 5,
- start_line: 100,
- },
- }, {
- id: '8675309',
- line: 55,
- message: 'nonsense range',
- range: {
- end_character: 2,
- end_line: 55,
- start_character: 32,
- start_line: 55,
- },
+ },
+ {
+ side: 'right',
+ range: {
+ end_character: 22,
+ end_line: 12,
+ start_character: 10,
+ start_line: 10,
},
- ],
- };
+ },
+ {
+ side: 'right',
+ range: {
+ end_character: 15,
+ end_line: 100,
+ start_character: 5,
+ start_line: 100,
+ },
+ },
+ {
+ side: 'right',
+ range: {
+ end_character: 2,
+ end_line: 55,
+ start_character: 32,
+ start_line: 55,
+ },
+ },
+ ];
sandbox = sinon.sandbox.create();
element = fixture('basic');
- element.comments = initialComments;
+ element.commentRanges = initialCommentRanges;
});
teardown(() => {
@@ -149,7 +135,7 @@
test('type=Remove has-comment hovering', () => {
line.type = GrDiffLine.Type.REMOVE;
line.beforeNumber = 36;
- element.set(['comments', 'left', 0, '__hovering'], true);
+ element.set(['commentRanges', 0, 'hovering'], true);
const expectedStart = 6;
const expectedLength = line.text.length - expectedStart;
@@ -210,29 +196,18 @@
});
});
- test('_handleCommentChange overwrite', () => {
- const handlerSpy = sandbox.spy(element, '_handleCommentChange');
- const mapSpy = sandbox.spy(element, '_computeCommentMap');
+ test('_handleCommentRangesChange overwrite', () => {
+ element.set('commentRanges', []);
- element.set('comments', {left: [], right: []});
-
- assert.isTrue(handlerSpy.called);
- assert.equal(mapSpy.callCount, 2);
-
- assert.equal(Object.keys(element._commentMap.left).length, 0);
- assert.equal(Object.keys(element._commentMap.right).length, 0);
+ assert.equal(Object.keys(element._rangesMap.left).length, 0);
+ assert.equal(Object.keys(element._rangesMap.right).length, 0);
});
- test('_handleCommentChange hovering', () => {
- const handlerSpy = sandbox.spy(element, '_handleCommentChange');
- const mapSpy = sandbox.spy(element, '_computeCommentMap');
+ test('_handleCommentRangesChange hovering', () => {
const notifyStub = sinon.stub();
element.addListener(notifyStub);
- element.set(['comments', 'right', 0, '__hovering'], true);
-
- assert.isTrue(handlerSpy.called);
- assert.isTrue(mapSpy.called);
+ element.set(['commentRanges', 1, 'hovering'], true);
assert.isTrue(notifyStub.called);
const lastCall = notifyStub.lastCall;
@@ -241,16 +216,11 @@
assert.equal(lastCall.args[2], 'right');
});
- test('_handleCommentChange splice out', () => {
- const handlerSpy = sandbox.spy(element, '_handleCommentChange');
- const mapSpy = sandbox.spy(element, '_computeCommentMap');
+ test('_handleCommentRangesChange splice out', () => {
const notifyStub = sinon.stub();
element.addListener(notifyStub);
- element.splice('comments.right', 0, 1);
-
- assert.isTrue(handlerSpy.called);
- assert.isTrue(mapSpy.called);
+ element.splice('commentRanges', 1, 1);
assert.isTrue(notifyStub.called);
const lastCall = notifyStub.lastCall;
@@ -259,16 +229,12 @@
assert.equal(lastCall.args[2], 'right');
});
- test('_handleCommentChange splice in', () => {
- const handlerSpy = sandbox.spy(element, '_handleCommentChange');
- const mapSpy = sandbox.spy(element, '_computeCommentMap');
+ test('_handleCommentRangesChange splice in', () => {
const notifyStub = sinon.stub();
element.addListener(notifyStub);
- element.splice('comments.left', element.comments.left.length, 0, {
- id: '56123',
- line: 250,
- message: 'new range comment',
+ element.splice('commentRanges', 1, 0, {
+ side: 'left',
range: {
end_character: 15,
end_line: 275,
@@ -277,9 +243,6 @@
},
});
- assert.isTrue(handlerSpy.called);
- assert.isTrue(mapSpy.called);
-
assert.isTrue(notifyStub.called);
const lastCall = notifyStub.lastCall;
assert.equal(lastCall.args[0], 250);
@@ -291,48 +254,48 @@
// There is only one ranged comment on the left, but it spans ll.36-39.
const leftKeys = [];
for (let i = 36; i <= 39; i++) { leftKeys.push('' + i); }
- assert.deepEqual(Object.keys(element._commentMap.left).sort(),
+ assert.deepEqual(Object.keys(element._rangesMap.left).sort(),
leftKeys.sort());
- assert.equal(element._commentMap.left[36].length, 1);
- assert.equal(element._commentMap.left[36][0].start, 6);
- assert.equal(element._commentMap.left[36][0].end, -1);
+ assert.equal(element._rangesMap.left[36].length, 1);
+ assert.equal(element._rangesMap.left[36][0].start, 6);
+ assert.equal(element._rangesMap.left[36][0].end, -1);
- assert.equal(element._commentMap.left[37].length, 1);
- assert.equal(element._commentMap.left[37][0].start, 0);
- assert.equal(element._commentMap.left[37][0].end, -1);
+ assert.equal(element._rangesMap.left[37].length, 1);
+ assert.equal(element._rangesMap.left[37][0].start, 0);
+ assert.equal(element._rangesMap.left[37][0].end, -1);
- assert.equal(element._commentMap.left[38].length, 1);
- assert.equal(element._commentMap.left[38][0].start, 0);
- assert.equal(element._commentMap.left[38][0].end, -1);
+ assert.equal(element._rangesMap.left[38].length, 1);
+ assert.equal(element._rangesMap.left[38][0].start, 0);
+ assert.equal(element._rangesMap.left[38][0].end, -1);
- assert.equal(element._commentMap.left[39].length, 1);
- assert.equal(element._commentMap.left[39][0].start, 0);
- assert.equal(element._commentMap.left[39][0].end, 9);
+ assert.equal(element._rangesMap.left[39].length, 1);
+ assert.equal(element._rangesMap.left[39][0].start, 0);
+ assert.equal(element._rangesMap.left[39][0].end, 9);
// The right has two ranged comments, one spanning ll.10-12 and the other
// on line 100.
const rightKeys = [];
for (let i = 10; i <= 12; i++) { rightKeys.push('' + i); }
rightKeys.push('55', '100');
- assert.deepEqual(Object.keys(element._commentMap.right).sort(),
+ assert.deepEqual(Object.keys(element._rangesMap.right).sort(),
rightKeys.sort());
- assert.equal(element._commentMap.right[10].length, 1);
- assert.equal(element._commentMap.right[10][0].start, 10);
- assert.equal(element._commentMap.right[10][0].end, -1);
+ assert.equal(element._rangesMap.right[10].length, 1);
+ assert.equal(element._rangesMap.right[10][0].start, 10);
+ assert.equal(element._rangesMap.right[10][0].end, -1);
- assert.equal(element._commentMap.right[11].length, 1);
- assert.equal(element._commentMap.right[11][0].start, 0);
- assert.equal(element._commentMap.right[11][0].end, -1);
+ assert.equal(element._rangesMap.right[11].length, 1);
+ assert.equal(element._rangesMap.right[11][0].start, 0);
+ assert.equal(element._rangesMap.right[11][0].end, -1);
- assert.equal(element._commentMap.right[12].length, 1);
- assert.equal(element._commentMap.right[12][0].start, 0);
- assert.equal(element._commentMap.right[12][0].end, 22);
+ assert.equal(element._rangesMap.right[12].length, 1);
+ assert.equal(element._rangesMap.right[12][0].start, 0);
+ assert.equal(element._rangesMap.right[12][0].end, 22);
- assert.equal(element._commentMap.right[100].length, 1);
- assert.equal(element._commentMap.right[100][0].start, 5);
- assert.equal(element._commentMap.right[100][0].end, 15);
+ assert.equal(element._rangesMap.right[100].length, 1);
+ assert.equal(element._rangesMap.right[100][0].start, 5);
+ assert.equal(element._rangesMap.right[100][0].end, 15);
});
test('_getRangesForLine normalizes invalid ranges', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
index 0f84877..fa5c810 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
@@ -34,10 +34,10 @@
range: {
type: Object,
value: {
- startLine: NaN,
- startChar: NaN,
- endLine: NaN,
- endChar: NaN,
+ start_line: NaN,
+ start_character: NaN,
+ end_line: NaN,
+ end_character: NaN,
},
},
positionBelow: Boolean,
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
index 4f1065a..dece366 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
@@ -89,10 +89,10 @@
test('event fired contains playload', () => {
const side = 'left';
const range = {
- startLine: 1,
- startChar: 11,
- endLine: 2,
- endChar: 42,
+ start_line: 1,
+ start_character: 11,
+ end_line: 2,
+ end_character: 42,
};
element.side = 'left';
element.range = range;
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index c347cac..321dc58 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -203,6 +203,8 @@
this.Shortcut.TOGGLE_CHANGE_STAR, 's');
this.bindShortcut(
this.Shortcut.REFRESH_CHANGE_LIST, 'shift+r');
+ this.bindShortcut(
+ this.Shortcut.EDIT_TOPIC, 't');
this.bindShortcut(
this.Shortcut.OPEN_REPLY_DIALOG, 'a');
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js
index b7d65d3..3514492 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js
@@ -99,6 +99,12 @@
});
},
+ open() {
+ return this._open().then(() => {
+ this.$.input.$.input.focus();
+ });
+ },
+
_open(...args) {
this.$.dropdown.open();
this._inputText = this.value;