Merge changes from topics "gr-file-list-to-ts", "gr-smart-search-to-ts"
* changes: Convert files to typescript Rename files to preserve history Convert files to typescript Rename files to preserve history Convert gr-smart-search to typescript Rename files to preserve history
This commit is contained in:
@@ -277,6 +277,12 @@ module.exports = {
|
|||||||
"@typescript-eslint/restrict-plus-operands": "error",
|
"@typescript-eslint/restrict-plus-operands": "error",
|
||||||
// https://github.com/mysticatea/eslint-plugin-node/blob/master/docs/rules/no-unsupported-features/node-builtins.md
|
// https://github.com/mysticatea/eslint-plugin-node/blob/master/docs/rules/no-unsupported-features/node-builtins.md
|
||||||
"node/no-unsupported-features/node-builtins": "off",
|
"node/no-unsupported-features/node-builtins": "off",
|
||||||
|
// Disable no-invalid-this for ts files, because it incorrectly reports
|
||||||
|
// errors in some cases (see https://github.com/typescript-eslint/typescript-eslint/issues/491)
|
||||||
|
// At the same time, we are using typescript in a strict mode and
|
||||||
|
// it catches almost all errors related to invalid usage of this.
|
||||||
|
"no-invalid-this": "off",
|
||||||
|
|
||||||
"jsdoc/no-types": 2,
|
"jsdoc/no-types": 2,
|
||||||
},
|
},
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
|
|||||||
@@ -341,3 +341,20 @@ export enum NotifyType {
|
|||||||
OWNER_REVIEWERS = 'OWNER_REVIEWERS',
|
OWNER_REVIEWERS = 'OWNER_REVIEWERS',
|
||||||
ALL = 'ALL',
|
ALL = 'ALL',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authentication type that is configured on the server.
|
||||||
|
* https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#auth-info
|
||||||
|
*/
|
||||||
|
export enum AuthType {
|
||||||
|
OPENID = 'OPENID',
|
||||||
|
OPENID_SSO = 'OPENID_SSO',
|
||||||
|
OAUTH = 'OAUTH',
|
||||||
|
HTTP = 'HTTP',
|
||||||
|
HTTP_LDAP = 'HTTP_LDAP',
|
||||||
|
CLIENT_SSL_CERT_LDAP = 'CLIENT_SSL_CERT_LDAP',
|
||||||
|
LDAP = 'LDAP',
|
||||||
|
LDAP_BIND = 'LDAP_BIND',
|
||||||
|
CUSTOM_EXTENSION = 'CUSTOM_EXTENSION',
|
||||||
|
DEVELOPMENT_BECOME_ANY_ACCOUNT = 'DEVELOPMENT_BECOME_ANY_ACCOUNT',
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import '../../shared/gr-icons/gr-icons.js';
|
|||||||
import '../gr-commit-info/gr-commit-info.js';
|
import '../gr-commit-info/gr-commit-info.js';
|
||||||
import '../gr-download-dialog/gr-download-dialog.js';
|
import '../gr-download-dialog/gr-download-dialog.js';
|
||||||
import '../gr-file-list-header/gr-file-list-header.js';
|
import '../gr-file-list-header/gr-file-list-header.js';
|
||||||
import '../gr-file-list/gr-file-list.js';
|
|
||||||
import '../gr-included-in-dialog/gr-included-in-dialog.js';
|
import '../gr-included-in-dialog/gr-included-in-dialog.js';
|
||||||
import '../gr-messages-list/gr-messages-list.js';
|
import '../gr-messages-list/gr-messages-list.js';
|
||||||
import '../gr-related-changes-list/gr-related-changes-list.js';
|
import '../gr-related-changes-list/gr-related-changes-list.js';
|
||||||
@@ -73,6 +72,7 @@ import {
|
|||||||
} from '../../../utils/patch-set-util.js';
|
} from '../../../utils/patch-set-util.js';
|
||||||
import {changeStatuses, changeStatusString} from '../../../utils/change-util.js';
|
import {changeStatuses, changeStatusString} from '../../../utils/change-util.js';
|
||||||
import {EventType} from '../../plugins/gr-plugin-types.js';
|
import {EventType} from '../../plugins/gr-plugin-types.js';
|
||||||
|
import {DEFAULT_NUM_FILES_SHOWN} from '../gr-file-list/gr-file-list.js';
|
||||||
|
|
||||||
const CHANGE_ID_ERROR = {
|
const CHANGE_ID_ERROR = {
|
||||||
MISMATCH: 'mismatch',
|
MISMATCH: 'mismatch',
|
||||||
@@ -82,7 +82,6 @@ const CHANGE_ID_REGEX_PATTERN =
|
|||||||
/^(Change-Id\:\s|Link:.*\/id\/)(I[0-9a-f]{8,40})/gm;
|
/^(Change-Id\:\s|Link:.*\/id\/)(I[0-9a-f]{8,40})/gm;
|
||||||
|
|
||||||
const MIN_LINES_FOR_COMMIT_COLLAPSE = 30;
|
const MIN_LINES_FOR_COMMIT_COLLAPSE = 30;
|
||||||
const DEFAULT_NUM_FILES_SHOWN = 200;
|
|
||||||
|
|
||||||
const REVIEWERS_REGEX = /^(R|CC)=/gm;
|
const REVIEWERS_REGEX = /^(R|CC)=/gm;
|
||||||
const MIN_CHECK_INTERVAL_SECS = 0;
|
const MIN_CHECK_INTERVAL_SECS = 0;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
1901
polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
Normal file
1901
polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -375,7 +375,7 @@ export const htmlTemplate = html`
|
|||||||
<div class="stickyArea">
|
<div class="stickyArea">
|
||||||
<div
|
<div
|
||||||
class$="file-row row [[_computePathClass(file.__path, _expandedFiles.*)]]"
|
class$="file-row row [[_computePathClass(file.__path, _expandedFiles.*)]]"
|
||||||
data-file$="[[_computeFileRange(file)]]"
|
data-file$="[[_computePatchSetFile(file)]]"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
role="row"
|
role="row"
|
||||||
>
|
>
|
||||||
@@ -657,7 +657,7 @@ export const htmlTemplate = html`
|
|||||||
hidden="[[!_isFileExpanded(file.__path, _expandedFiles.*)]]"
|
hidden="[[!_isFileExpanded(file.__path, _expandedFiles.*)]]"
|
||||||
change-num="[[changeNum]]"
|
change-num="[[changeNum]]"
|
||||||
patch-range="[[patchRange]]"
|
patch-range="[[patchRange]]"
|
||||||
file="[[_computeFileRange(file)]]"
|
file="[[_computePatchSetFile(file)]]"
|
||||||
path="[[file.__path]]"
|
path="[[file.__path]]"
|
||||||
prefs="[[diffPrefs]]"
|
prefs="[[diffPrefs]]"
|
||||||
project-name="[[change.project]]"
|
project-name="[[change.project]]"
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ suite('gr-file-list tests', () => {
|
|||||||
|
|
||||||
for (const bytes in table) {
|
for (const bytes in table) {
|
||||||
if (table.hasOwnProperty(bytes)) {
|
if (table.hasOwnProperty(bytes)) {
|
||||||
assert.equal(element._formatBytes(bytes), table[bytes]);
|
assert.equal(element._formatBytes(Number(bytes)), table[bytes]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1329,15 +1329,23 @@ suite('gr-file-list tests', () => {
|
|||||||
|
|
||||||
suite('size bars', () => {
|
suite('size bars', () => {
|
||||||
test('_computeSizeBarLayout', () => {
|
test('_computeSizeBarLayout', () => {
|
||||||
assert.isUndefined(element._computeSizeBarLayout(null));
|
const defaultSizeBarLayout = {
|
||||||
assert.isUndefined(element._computeSizeBarLayout({}));
|
|
||||||
assert.deepEqual(element._computeSizeBarLayout({base: []}), {
|
|
||||||
maxInserted: 0,
|
maxInserted: 0,
|
||||||
maxDeleted: 0,
|
maxDeleted: 0,
|
||||||
maxAdditionWidth: 0,
|
maxAdditionWidth: 0,
|
||||||
maxDeletionWidth: 0,
|
maxDeletionWidth: 0,
|
||||||
deletionOffset: 0,
|
deletionOffset: 0,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
element._computeSizeBarLayout(null),
|
||||||
|
defaultSizeBarLayout);
|
||||||
|
assert.deepEqual(
|
||||||
|
element._computeSizeBarLayout({}),
|
||||||
|
defaultSizeBarLayout);
|
||||||
|
assert.deepEqual(
|
||||||
|
element._computeSizeBarLayout({base: []}),
|
||||||
|
defaultSizeBarLayout);
|
||||||
|
|
||||||
const files = [
|
const files = [
|
||||||
{__path: '/COMMIT_MSG', lines_inserted: 10000},
|
{__path: '/COMMIT_MSG', lines_inserted: 10000},
|
||||||
|
|||||||
@@ -1,363 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.js';
|
|
||||||
import '../../shared/gr-dropdown/gr-dropdown.js';
|
|
||||||
import '../../shared/gr-icons/gr-icons.js';
|
|
||||||
import '../../shared/gr-js-api-interface/gr-js-api-interface.js';
|
|
||||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
|
||||||
import '../gr-account-dropdown/gr-account-dropdown.js';
|
|
||||||
import '../gr-smart-search/gr-smart-search.js';
|
|
||||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
|
||||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
|
||||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
|
||||||
import {htmlTemplate} from './gr-main-header_html.js';
|
|
||||||
import {getBaseUrl, getDocsBaseUrl} from '../../../utils/url-util.js';
|
|
||||||
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
|
|
||||||
import {getAdminLinks} from '../../../utils/admin-nav-util.js';
|
|
||||||
|
|
||||||
const DEFAULT_LINKS = [{
|
|
||||||
title: 'Changes',
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
url: '/q/status:open+-is:wip',
|
|
||||||
name: 'Open',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/q/status:merged',
|
|
||||||
name: 'Merged',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/q/status:abandoned',
|
|
||||||
name: 'Abandoned',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}];
|
|
||||||
|
|
||||||
const DOCUMENTATION_LINKS = [
|
|
||||||
{
|
|
||||||
url: '/index.html',
|
|
||||||
name: 'Table of Contents',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/user-search.html',
|
|
||||||
name: 'Searching',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/user-upload.html',
|
|
||||||
name: 'Uploading',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/access-control.html',
|
|
||||||
name: 'Access Control',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/rest-api.html',
|
|
||||||
name: 'REST API',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/intro-project-owner.html',
|
|
||||||
name: 'Project Owner Guide',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Set of authentication methods that can provide custom registration page.
|
|
||||||
const AUTH_TYPES_WITH_REGISTER_URL = new Set([
|
|
||||||
'LDAP',
|
|
||||||
'LDAP_BIND',
|
|
||||||
'CUSTOM_EXTENSION',
|
|
||||||
]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends PolymerElement
|
|
||||||
*/
|
|
||||||
class GrMainHeader extends GestureEventListeners(
|
|
||||||
LegacyElementMixin(
|
|
||||||
PolymerElement)) {
|
|
||||||
static get template() { return htmlTemplate; }
|
|
||||||
|
|
||||||
static get is() { return 'gr-main-header'; }
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
searchQuery: {
|
|
||||||
type: String,
|
|
||||||
notify: true,
|
|
||||||
},
|
|
||||||
loggedIn: {
|
|
||||||
type: Boolean,
|
|
||||||
reflectToAttribute: true,
|
|
||||||
},
|
|
||||||
loading: {
|
|
||||||
type: Boolean,
|
|
||||||
reflectToAttribute: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @type {?Object} */
|
|
||||||
_account: Object,
|
|
||||||
_adminLinks: {
|
|
||||||
type: Array,
|
|
||||||
value() { return []; },
|
|
||||||
},
|
|
||||||
_defaultLinks: {
|
|
||||||
type: Array,
|
|
||||||
value() {
|
|
||||||
return DEFAULT_LINKS;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_docBaseUrl: {
|
|
||||||
type: String,
|
|
||||||
value: null,
|
|
||||||
},
|
|
||||||
_links: {
|
|
||||||
type: Array,
|
|
||||||
computed: '_computeLinks(_defaultLinks, _userLinks, _adminLinks, ' +
|
|
||||||
'_topMenus, _docBaseUrl)',
|
|
||||||
},
|
|
||||||
loginUrl: {
|
|
||||||
type: String,
|
|
||||||
value: '/login',
|
|
||||||
},
|
|
||||||
_userLinks: {
|
|
||||||
type: Array,
|
|
||||||
value() { return []; },
|
|
||||||
},
|
|
||||||
_topMenus: {
|
|
||||||
type: Array,
|
|
||||||
value() { return []; },
|
|
||||||
},
|
|
||||||
_registerText: {
|
|
||||||
type: String,
|
|
||||||
value: 'Sign up',
|
|
||||||
},
|
|
||||||
_registerURL: {
|
|
||||||
type: String,
|
|
||||||
value: null,
|
|
||||||
},
|
|
||||||
mobileSearchHidden: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static get observers() {
|
|
||||||
return [
|
|
||||||
'_accountLoaded(_account)',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
ready() {
|
|
||||||
super.ready();
|
|
||||||
this._ensureAttribute('role', 'banner');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
attached() {
|
|
||||||
super.attached();
|
|
||||||
this._loadAccount();
|
|
||||||
this._loadConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
detached() {
|
|
||||||
super.detached();
|
|
||||||
}
|
|
||||||
|
|
||||||
reload() {
|
|
||||||
this._loadAccount();
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeRelativeURL(path) {
|
|
||||||
return '//' + window.location.host + getBaseUrl() + path;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeLinks(defaultLinks, userLinks, adminLinks, topMenus, docBaseUrl) {
|
|
||||||
// Polymer 2: check for undefined
|
|
||||||
if ([
|
|
||||||
defaultLinks,
|
|
||||||
userLinks,
|
|
||||||
adminLinks,
|
|
||||||
topMenus,
|
|
||||||
docBaseUrl,
|
|
||||||
].includes(undefined)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const links = defaultLinks.map(menu => {
|
|
||||||
return {
|
|
||||||
title: menu.title,
|
|
||||||
links: menu.links.slice(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
if (userLinks && userLinks.length > 0) {
|
|
||||||
links.push({
|
|
||||||
title: 'Your',
|
|
||||||
links: userLinks.slice(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const docLinks = this._getDocLinks(docBaseUrl, DOCUMENTATION_LINKS);
|
|
||||||
if (docLinks.length) {
|
|
||||||
links.push({
|
|
||||||
title: 'Documentation',
|
|
||||||
links: docLinks,
|
|
||||||
class: 'hideOnMobile',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
links.push({
|
|
||||||
title: 'Browse',
|
|
||||||
links: adminLinks.slice(),
|
|
||||||
});
|
|
||||||
const topMenuLinks = [];
|
|
||||||
links.forEach(link => { topMenuLinks[link.title] = link.links; });
|
|
||||||
for (const m of topMenus) {
|
|
||||||
const items = m.items.map(this._fixCustomMenuItem).filter(link =>
|
|
||||||
// Ignore GWT project links
|
|
||||||
!link.url.includes('${projectName}')
|
|
||||||
);
|
|
||||||
if (m.name in topMenuLinks) {
|
|
||||||
items.forEach(link => { topMenuLinks[m.name].push(link); });
|
|
||||||
} else {
|
|
||||||
links.push({
|
|
||||||
title: m.name,
|
|
||||||
links: topMenuLinks[m.name] = items,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return links;
|
|
||||||
}
|
|
||||||
|
|
||||||
_getDocLinks(docBaseUrl, docLinks) {
|
|
||||||
if (!docBaseUrl || !docLinks) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return docLinks.map(link => {
|
|
||||||
let url = docBaseUrl;
|
|
||||||
if (url && url[url.length - 1] === '/') {
|
|
||||||
url = url.substring(0, url.length - 1);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
url: url + link.url,
|
|
||||||
name: link.name,
|
|
||||||
target: '_blank',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_loadAccount() {
|
|
||||||
this.loading = true;
|
|
||||||
const promises = [
|
|
||||||
this.$.restAPI.getAccount(),
|
|
||||||
this.$.restAPI.getTopMenus(),
|
|
||||||
getPluginLoader().awaitPluginsLoaded(),
|
|
||||||
];
|
|
||||||
|
|
||||||
return Promise.all(promises).then(result => {
|
|
||||||
const account = result[0];
|
|
||||||
this._account = account;
|
|
||||||
this.loggedIn = !!account;
|
|
||||||
this.loading = false;
|
|
||||||
this._topMenus = result[1];
|
|
||||||
|
|
||||||
return getAdminLinks(account,
|
|
||||||
params => this.$.restAPI.getAccountCapabilities(params),
|
|
||||||
() => this.$.jsAPI.getAdminMenuLinks())
|
|
||||||
.then(res => {
|
|
||||||
this._adminLinks = res.links;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_loadConfig() {
|
|
||||||
this.$.restAPI.getConfig()
|
|
||||||
.then(config => {
|
|
||||||
this._retrieveRegisterURL(config);
|
|
||||||
return getDocsBaseUrl(config, this.$.restAPI);
|
|
||||||
})
|
|
||||||
.then(docBaseUrl => { this._docBaseUrl = docBaseUrl; });
|
|
||||||
}
|
|
||||||
|
|
||||||
_accountLoaded(account) {
|
|
||||||
if (!account) { return; }
|
|
||||||
|
|
||||||
this.$.restAPI.getPreferences().then(prefs => {
|
|
||||||
this._userLinks = prefs && prefs.my ?
|
|
||||||
prefs.my.map(this._fixCustomMenuItem) : [];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_retrieveRegisterURL(config) {
|
|
||||||
if (AUTH_TYPES_WITH_REGISTER_URL.has(config.auth.auth_type)) {
|
|
||||||
this._registerURL = config.auth.register_url;
|
|
||||||
if (config.auth.register_text) {
|
|
||||||
this._registerText = config.auth.register_text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeIsInvisible(registerURL) {
|
|
||||||
return registerURL ? '' : 'invisible';
|
|
||||||
}
|
|
||||||
|
|
||||||
_fixCustomMenuItem(linkObj) {
|
|
||||||
// Normalize all urls to PolyGerrit style.
|
|
||||||
if (linkObj.url.startsWith('#')) {
|
|
||||||
linkObj.url = linkObj.url.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete target property due to complications of
|
|
||||||
// https://bugs.chromium.org/p/gerrit/issues/detail?id=5888
|
|
||||||
//
|
|
||||||
// The server tries to guess whether URL is a view within the UI.
|
|
||||||
// If not, it sets target='_blank' on the menu item. The server
|
|
||||||
// makes assumptions that work for the GWT UI, but not PolyGerrit,
|
|
||||||
// so we'll just disable it altogether for now.
|
|
||||||
delete linkObj.target;
|
|
||||||
|
|
||||||
return linkObj;
|
|
||||||
}
|
|
||||||
|
|
||||||
_generateSettingsLink() {
|
|
||||||
return getBaseUrl() + '/settings/';
|
|
||||||
}
|
|
||||||
|
|
||||||
_onMobileSearchTap(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
this.dispatchEvent(new CustomEvent('mobile-search', {
|
|
||||||
composed: true, bubbles: false,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeLinkGroupClass(linkGroup) {
|
|
||||||
if (linkGroup && linkGroup.class) {
|
|
||||||
return linkGroup.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeShowHideAriaLabel(mobileSearchHidden) {
|
|
||||||
if (mobileSearchHidden) {
|
|
||||||
return 'Show Searchbar';
|
|
||||||
} else {
|
|
||||||
return 'Hide Searchbar';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define(GrMainHeader.is, GrMainHeader);
|
|
||||||
396
polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
Normal file
396
polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
|
||||||
|
import '../../shared/gr-dropdown/gr-dropdown';
|
||||||
|
import '../../shared/gr-icons/gr-icons';
|
||||||
|
import '../../shared/gr-js-api-interface/gr-js-api-interface';
|
||||||
|
import '../../shared/gr-rest-api-interface/gr-rest-api-interface';
|
||||||
|
import '../gr-account-dropdown/gr-account-dropdown';
|
||||||
|
import '../gr-smart-search/gr-smart-search';
|
||||||
|
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
|
||||||
|
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
|
||||||
|
import {PolymerElement} from '@polymer/polymer/polymer-element';
|
||||||
|
import {htmlTemplate} from './gr-main-header_html';
|
||||||
|
import {getBaseUrl, getDocsBaseUrl} from '../../../utils/url-util';
|
||||||
|
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
|
||||||
|
import {getAdminLinks, NavLink} from '../../../utils/admin-nav-util';
|
||||||
|
import {customElement, property, observe} from '@polymer/decorators';
|
||||||
|
import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api';
|
||||||
|
import {
|
||||||
|
AccountDetailInfo,
|
||||||
|
ServerInfo,
|
||||||
|
TopMenuEntryInfo,
|
||||||
|
TopMenuItemInfo,
|
||||||
|
} from '../../../types/common';
|
||||||
|
import {JsApiService} from '../../shared/gr-js-api-interface/gr-js-api-types';
|
||||||
|
import {AuthType} from '../../../constants/constants';
|
||||||
|
|
||||||
|
interface FixedTopMenuItemInfo extends Omit<TopMenuItemInfo, 'target'> {
|
||||||
|
target?: never;
|
||||||
|
}
|
||||||
|
interface MainHeaderLink {
|
||||||
|
url: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
interface MainHeaderLinkGroup {
|
||||||
|
title: string;
|
||||||
|
links: MainHeaderLink[];
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_LINKS: MainHeaderLinkGroup[] = [
|
||||||
|
{
|
||||||
|
title: 'Changes',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
url: '/q/status:open+-is:wip',
|
||||||
|
name: 'Open',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/q/status:merged',
|
||||||
|
name: 'Merged',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/q/status:abandoned',
|
||||||
|
name: 'Abandoned',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const DOCUMENTATION_LINKS: MainHeaderLink[] = [
|
||||||
|
{
|
||||||
|
url: '/index.html',
|
||||||
|
name: 'Table of Contents',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/user-search.html',
|
||||||
|
name: 'Searching',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/user-upload.html',
|
||||||
|
name: 'Uploading',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/access-control.html',
|
||||||
|
name: 'Access Control',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/rest-api.html',
|
||||||
|
name: 'REST API',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/intro-project-owner.html',
|
||||||
|
name: 'Project Owner Guide',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Set of authentication methods that can provide custom registration page.
|
||||||
|
const AUTH_TYPES_WITH_REGISTER_URL: Set<AuthType> = new Set([
|
||||||
|
AuthType.LDAP,
|
||||||
|
AuthType.LDAP_BIND,
|
||||||
|
AuthType.CUSTOM_EXTENSION,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export interface GrMainHeader {
|
||||||
|
$: {
|
||||||
|
restAPI: RestApiService & Element;
|
||||||
|
jsAPI: JsApiService & Element;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement('gr-main-header')
|
||||||
|
export class GrMainHeader extends GestureEventListeners(
|
||||||
|
LegacyElementMixin(PolymerElement)
|
||||||
|
) {
|
||||||
|
static get template() {
|
||||||
|
return htmlTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property({type: String, notify: true})
|
||||||
|
searchQuery?: string;
|
||||||
|
|
||||||
|
@property({type: Boolean, reflectToAttribute: true})
|
||||||
|
loggedIn?: boolean;
|
||||||
|
|
||||||
|
@property({type: Boolean, reflectToAttribute: true})
|
||||||
|
loading?: boolean;
|
||||||
|
|
||||||
|
@property({type: Object})
|
||||||
|
_account?: AccountDetailInfo;
|
||||||
|
|
||||||
|
@property({type: Array})
|
||||||
|
_adminLinks: NavLink[] = [];
|
||||||
|
|
||||||
|
@property({type: String})
|
||||||
|
_docBaseUrl: string | null = null;
|
||||||
|
|
||||||
|
@property({
|
||||||
|
type: Array,
|
||||||
|
computed: '_computeLinks(_userLinks, _adminLinks, _topMenus, _docBaseUrl)',
|
||||||
|
})
|
||||||
|
_links?: MainHeaderLinkGroup[];
|
||||||
|
|
||||||
|
@property({type: String})
|
||||||
|
loginUrl = '/login';
|
||||||
|
|
||||||
|
@property({type: Array})
|
||||||
|
_userLinks: FixedTopMenuItemInfo[] = [];
|
||||||
|
|
||||||
|
@property({type: Array})
|
||||||
|
_topMenus?: TopMenuEntryInfo[] = [];
|
||||||
|
|
||||||
|
@property({type: String})
|
||||||
|
_registerText = 'Sign up';
|
||||||
|
|
||||||
|
@property({type: String})
|
||||||
|
_registerURL?: string;
|
||||||
|
|
||||||
|
@property({type: Boolean})
|
||||||
|
mobileSearchHidden = false;
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
ready() {
|
||||||
|
super.ready();
|
||||||
|
this._ensureAttribute('role', 'banner');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
attached() {
|
||||||
|
super.attached();
|
||||||
|
this._loadAccount();
|
||||||
|
this._loadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
detached() {
|
||||||
|
super.detached();
|
||||||
|
}
|
||||||
|
|
||||||
|
reload() {
|
||||||
|
this._loadAccount();
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeRelativeURL(path: string) {
|
||||||
|
return '//' + window.location.host + getBaseUrl() + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeLinks(
|
||||||
|
userLinks?: FixedTopMenuItemInfo[],
|
||||||
|
adminLinks?: NavLink[],
|
||||||
|
topMenus?: TopMenuEntryInfo[],
|
||||||
|
docBaseUrl?: string | null,
|
||||||
|
// defaultLinks parameter is used in tests only
|
||||||
|
defaultLinks = DEFAULT_LINKS
|
||||||
|
) {
|
||||||
|
// Polymer 2: check for undefined
|
||||||
|
if (
|
||||||
|
userLinks === undefined ||
|
||||||
|
adminLinks === undefined ||
|
||||||
|
topMenus === undefined ||
|
||||||
|
docBaseUrl === undefined
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const links: MainHeaderLinkGroup[] = defaultLinks.map(menu => {
|
||||||
|
return {
|
||||||
|
title: menu.title,
|
||||||
|
links: menu.links.slice(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (userLinks && userLinks.length > 0) {
|
||||||
|
links.push({
|
||||||
|
title: 'Your',
|
||||||
|
links: userLinks.slice(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const docLinks = this._getDocLinks(docBaseUrl, DOCUMENTATION_LINKS);
|
||||||
|
if (docLinks.length) {
|
||||||
|
links.push({
|
||||||
|
title: 'Documentation',
|
||||||
|
links: docLinks,
|
||||||
|
class: 'hideOnMobile',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
links.push({
|
||||||
|
title: 'Browse',
|
||||||
|
links: adminLinks.slice(),
|
||||||
|
});
|
||||||
|
const topMenuLinks: {[name: string]: MainHeaderLink[]} = {};
|
||||||
|
links.forEach(link => {
|
||||||
|
topMenuLinks[link.title] = link.links;
|
||||||
|
});
|
||||||
|
for (const m of topMenus) {
|
||||||
|
const items = m.items.map(this._fixCustomMenuItem).filter(
|
||||||
|
link =>
|
||||||
|
// Ignore GWT project links
|
||||||
|
!link.url.includes('${projectName}')
|
||||||
|
);
|
||||||
|
if (m.name in topMenuLinks) {
|
||||||
|
items.forEach(link => {
|
||||||
|
topMenuLinks[m.name].push(link);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
links.push({
|
||||||
|
title: m.name,
|
||||||
|
links: topMenuLinks[m.name] = items,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getDocLinks(docBaseUrl: string | null, docLinks: MainHeaderLink[]) {
|
||||||
|
if (!docBaseUrl) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return docLinks.map(link => {
|
||||||
|
let url = docBaseUrl;
|
||||||
|
if (url && url[url.length - 1] === '/') {
|
||||||
|
url = url.substring(0, url.length - 1);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
url: url + link.url,
|
||||||
|
name: link.name,
|
||||||
|
target: '_blank',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadAccount() {
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
return Promise.all([
|
||||||
|
this.$.restAPI.getAccount(),
|
||||||
|
this.$.restAPI.getTopMenus(),
|
||||||
|
getPluginLoader().awaitPluginsLoaded(),
|
||||||
|
]).then(result => {
|
||||||
|
const account = result[0];
|
||||||
|
this._account = account;
|
||||||
|
this.loggedIn = !!account;
|
||||||
|
this.loading = false;
|
||||||
|
this._topMenus = result[1];
|
||||||
|
|
||||||
|
return getAdminLinks(
|
||||||
|
account,
|
||||||
|
() =>
|
||||||
|
this.$.restAPI.getAccountCapabilities().then(capabilities => {
|
||||||
|
if (!capabilities) {
|
||||||
|
throw new Error('getAccountCapabilities returns undefined');
|
||||||
|
}
|
||||||
|
return capabilities;
|
||||||
|
}),
|
||||||
|
() => this.$.jsAPI.getAdminMenuLinks()
|
||||||
|
).then(res => {
|
||||||
|
this._adminLinks = res.links;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadConfig() {
|
||||||
|
this.$.restAPI
|
||||||
|
.getConfig()
|
||||||
|
.then(config => {
|
||||||
|
if (!config) {
|
||||||
|
throw new Error('getConfig returned undefined');
|
||||||
|
}
|
||||||
|
this._retrieveRegisterURL(config);
|
||||||
|
return getDocsBaseUrl(config, this.$.restAPI);
|
||||||
|
})
|
||||||
|
.then(docBaseUrl => {
|
||||||
|
this._docBaseUrl = docBaseUrl;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@observe('_account')
|
||||||
|
_accountLoaded(account?: AccountDetailInfo) {
|
||||||
|
if (!account) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$.restAPI.getPreferences().then(prefs => {
|
||||||
|
this._userLinks =
|
||||||
|
prefs && prefs.my ? prefs.my.map(this._fixCustomMenuItem) : [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_retrieveRegisterURL(config: ServerInfo) {
|
||||||
|
if (AUTH_TYPES_WITH_REGISTER_URL.has(config.auth.auth_type)) {
|
||||||
|
this._registerURL = config.auth.register_url;
|
||||||
|
if (config.auth.register_text) {
|
||||||
|
this._registerText = config.auth.register_text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeIsInvisible(registerURL?: string) {
|
||||||
|
return registerURL ? '' : 'invisible';
|
||||||
|
}
|
||||||
|
|
||||||
|
_fixCustomMenuItem(linkObj: TopMenuItemInfo): FixedTopMenuItemInfo {
|
||||||
|
// TODO(TS): make a copy of linkObj instead of modifying the existing one
|
||||||
|
// Normalize all urls to PolyGerrit style.
|
||||||
|
if (linkObj.url.startsWith('#')) {
|
||||||
|
linkObj.url = linkObj.url.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete target property due to complications of
|
||||||
|
// https://bugs.chromium.org/p/gerrit/issues/detail?id=5888
|
||||||
|
//
|
||||||
|
// The server tries to guess whether URL is a view within the UI.
|
||||||
|
// If not, it sets target='_blank' on the menu item. The server
|
||||||
|
// makes assumptions that work for the GWT UI, but not PolyGerrit,
|
||||||
|
// so we'll just disable it altogether for now.
|
||||||
|
delete linkObj.target;
|
||||||
|
|
||||||
|
return (linkObj as unknown) as FixedTopMenuItemInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
_generateSettingsLink() {
|
||||||
|
return getBaseUrl() + '/settings/';
|
||||||
|
}
|
||||||
|
|
||||||
|
_onMobileSearchTap(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('mobile-search', {
|
||||||
|
composed: true,
|
||||||
|
bubbles: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeLinkGroupClass(linkGroup: MainHeaderLinkGroup) {
|
||||||
|
return linkGroup.class ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeShowHideAriaLabel(mobileSearchHidden: boolean) {
|
||||||
|
if (mobileSearchHidden) {
|
||||||
|
return 'Show Searchbar';
|
||||||
|
} else {
|
||||||
|
return 'Hide Searchbar';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'gr-main-header': GrMainHeader;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -103,22 +103,22 @@ suite('gr-main-header tests', () => {
|
|||||||
|
|
||||||
// When no admin links are passed, it should use the default.
|
// When no admin links are passed, it should use the default.
|
||||||
assert.deepEqual(element._computeLinks(
|
assert.deepEqual(element._computeLinks(
|
||||||
defaultLinks,
|
|
||||||
/* userLinks= */[],
|
/* userLinks= */[],
|
||||||
adminLinks,
|
adminLinks,
|
||||||
/* topMenus= */[],
|
/* topMenus= */[],
|
||||||
/* docBaseUrl= */ ''
|
/* docBaseUrl= */ '',
|
||||||
|
defaultLinks
|
||||||
),
|
),
|
||||||
defaultLinks.concat({
|
defaultLinks.concat({
|
||||||
title: 'Browse',
|
title: 'Browse',
|
||||||
links: adminLinks,
|
links: adminLinks,
|
||||||
}));
|
}));
|
||||||
assert.deepEqual(element._computeLinks(
|
assert.deepEqual(element._computeLinks(
|
||||||
defaultLinks,
|
|
||||||
userLinks,
|
userLinks,
|
||||||
adminLinks,
|
adminLinks,
|
||||||
/* topMenus= */[],
|
/* topMenus= */[],
|
||||||
/* docBaseUrl= */ ''
|
/* docBaseUrl= */ '',
|
||||||
|
defaultLinks
|
||||||
),
|
),
|
||||||
defaultLinks.concat([
|
defaultLinks.concat([
|
||||||
{
|
{
|
||||||
@@ -142,7 +142,6 @@ suite('gr-main-header tests', () => {
|
|||||||
|
|
||||||
assert.deepEqual(element._getDocLinks(null, docLinks), []);
|
assert.deepEqual(element._getDocLinks(null, docLinks), []);
|
||||||
assert.deepEqual(element._getDocLinks('', docLinks), []);
|
assert.deepEqual(element._getDocLinks('', docLinks), []);
|
||||||
assert.deepEqual(element._getDocLinks('base', null), []);
|
|
||||||
assert.deepEqual(element._getDocLinks('base', []), []);
|
assert.deepEqual(element._getDocLinks('base', []), []);
|
||||||
|
|
||||||
assert.deepEqual(element._getDocLinks('base', docLinks), [{
|
assert.deepEqual(element._getDocLinks('base', docLinks), [{
|
||||||
@@ -172,11 +171,11 @@ suite('gr-main-header tests', () => {
|
|||||||
}],
|
}],
|
||||||
}];
|
}];
|
||||||
assert.deepEqual(element._computeLinks(
|
assert.deepEqual(element._computeLinks(
|
||||||
/* defaultLinks= */ [],
|
|
||||||
/* userLinks= */ [],
|
/* userLinks= */ [],
|
||||||
adminLinks,
|
adminLinks,
|
||||||
topMenus,
|
topMenus,
|
||||||
/* baseDocUrl= */ ''
|
/* baseDocUrl= */ '',
|
||||||
|
/* defaultLinks= */ []
|
||||||
), [{
|
), [{
|
||||||
title: 'Browse',
|
title: 'Browse',
|
||||||
links: adminLinks,
|
links: adminLinks,
|
||||||
@@ -208,11 +207,11 @@ suite('gr-main-header tests', () => {
|
|||||||
}],
|
}],
|
||||||
}];
|
}];
|
||||||
assert.deepEqual(element._computeLinks(
|
assert.deepEqual(element._computeLinks(
|
||||||
/* defaultLinks= */ [],
|
|
||||||
/* userLinks= */ [],
|
/* userLinks= */ [],
|
||||||
adminLinks,
|
adminLinks,
|
||||||
topMenus,
|
topMenus,
|
||||||
/* baseDocUrl= */ ''
|
/* baseDocUrl= */ '',
|
||||||
|
/* defaultLinks= */ []
|
||||||
), [{
|
), [{
|
||||||
title: 'Browse',
|
title: 'Browse',
|
||||||
links: adminLinks,
|
links: adminLinks,
|
||||||
@@ -247,11 +246,11 @@ suite('gr-main-header tests', () => {
|
|||||||
}],
|
}],
|
||||||
}];
|
}];
|
||||||
assert.deepEqual(element._computeLinks(
|
assert.deepEqual(element._computeLinks(
|
||||||
/* defaultLinks= */ [],
|
|
||||||
/* userLinks= */ [],
|
/* userLinks= */ [],
|
||||||
adminLinks,
|
adminLinks,
|
||||||
topMenus,
|
topMenus,
|
||||||
/* baseDocUrl= */ ''
|
/* baseDocUrl= */ '',
|
||||||
|
/* defaultLinks= */ []
|
||||||
), [{
|
), [{
|
||||||
title: 'Browse',
|
title: 'Browse',
|
||||||
links: adminLinks,
|
links: adminLinks,
|
||||||
@@ -284,11 +283,11 @@ suite('gr-main-header tests', () => {
|
|||||||
}],
|
}],
|
||||||
}];
|
}];
|
||||||
assert.deepEqual(element._computeLinks(
|
assert.deepEqual(element._computeLinks(
|
||||||
defaultLinks,
|
|
||||||
/* userLinks= */ [],
|
/* userLinks= */ [],
|
||||||
/* adminLinks= */ [],
|
/* adminLinks= */ [],
|
||||||
topMenus,
|
topMenus,
|
||||||
/* baseDocUrl= */ ''
|
/* baseDocUrl= */ '',
|
||||||
|
defaultLinks
|
||||||
), [{
|
), [{
|
||||||
title: 'Faves',
|
title: 'Faves',
|
||||||
links: defaultLinks[0].links.concat([{
|
links: defaultLinks[0].links.concat([{
|
||||||
@@ -315,11 +314,11 @@ suite('gr-main-header tests', () => {
|
|||||||
}],
|
}],
|
||||||
}];
|
}];
|
||||||
assert.deepEqual(element._computeLinks(
|
assert.deepEqual(element._computeLinks(
|
||||||
/* defaultLinks= */ [],
|
|
||||||
userLinks,
|
userLinks,
|
||||||
/* adminLinks= */ [],
|
/* adminLinks= */ [],
|
||||||
topMenus,
|
topMenus,
|
||||||
/* baseDocUrl= */ ''
|
/* baseDocUrl= */ '',
|
||||||
|
/* defaultLinks= */ []
|
||||||
), [{
|
), [{
|
||||||
title: 'Your',
|
title: 'Your',
|
||||||
links: userLinks.concat([{
|
links: userLinks.concat([{
|
||||||
@@ -346,11 +345,11 @@ suite('gr-main-header tests', () => {
|
|||||||
}],
|
}],
|
||||||
}];
|
}];
|
||||||
assert.deepEqual(element._computeLinks(
|
assert.deepEqual(element._computeLinks(
|
||||||
/* defaultLinks= */ [],
|
|
||||||
/* userLinks= */ [],
|
/* userLinks= */ [],
|
||||||
adminLinks,
|
adminLinks,
|
||||||
topMenus,
|
topMenus,
|
||||||
/* baseDocUrl= */ ''
|
/* baseDocUrl= */ '',
|
||||||
|
/* defaultLinks= */ []
|
||||||
), [{
|
), [{
|
||||||
title: 'Browse',
|
title: 'Browse',
|
||||||
links: adminLinks.concat([{
|
links: adminLinks.concat([{
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
ParentPatchSetNum,
|
ParentPatchSetNum,
|
||||||
ServerInfo,
|
ServerInfo,
|
||||||
} from '../../../types/common';
|
} from '../../../types/common';
|
||||||
|
import {ParsedChangeInfo} from '../../shared/gr-rest-api-interface/gr-reviewer-updates-parser';
|
||||||
|
|
||||||
// Navigation parameters object format:
|
// Navigation parameters object format:
|
||||||
//
|
//
|
||||||
@@ -664,7 +665,7 @@ export const GerritNav = {
|
|||||||
* @param basePatchNum The string 'PARENT' can be used for none.
|
* @param basePatchNum The string 'PARENT' can be used for none.
|
||||||
*/
|
*/
|
||||||
getUrlForDiff(
|
getUrlForDiff(
|
||||||
change: ChangeInfo,
|
change: ChangeInfo | ParsedChangeInfo,
|
||||||
filePath: string,
|
filePath: string,
|
||||||
patchNum?: PatchSetNum,
|
patchNum?: PatchSetNum,
|
||||||
basePatchNum?: PatchSetNum,
|
basePatchNum?: PatchSetNum,
|
||||||
@@ -723,7 +724,7 @@ export const GerritNav = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getEditUrlForDiff(
|
getEditUrlForDiff(
|
||||||
change: ChangeInfo,
|
change: ChangeInfo | ParsedChangeInfo,
|
||||||
filePath: string,
|
filePath: string,
|
||||||
patchNum?: PatchSetNum,
|
patchNum?: PatchSetNum,
|
||||||
lineNum?: number
|
lineNum?: number
|
||||||
@@ -763,7 +764,7 @@ export const GerritNav = {
|
|||||||
* @param basePatchNum The string 'PARENT' can be used for none.
|
* @param basePatchNum The string 'PARENT' can be used for none.
|
||||||
*/
|
*/
|
||||||
navigateToDiff(
|
navigateToDiff(
|
||||||
change: ChangeInfo,
|
change: ChangeInfo | ParsedChangeInfo,
|
||||||
filePath: string,
|
filePath: string,
|
||||||
patchNum?: PatchSetNum,
|
patchNum?: PatchSetNum,
|
||||||
basePatchNum?: PatchSetNum,
|
basePatchNum?: PatchSetNum,
|
||||||
|
|||||||
@@ -124,11 +124,15 @@ const MAX_AUTOCOMPLETE_RESULTS = 10;
|
|||||||
|
|
||||||
const TOKENIZE_REGEX = /(?:[^\s"]+|"[^"]*")+\s*/g;
|
const TOKENIZE_REGEX = /(?:[^\s"]+|"[^"]*")+\s*/g;
|
||||||
|
|
||||||
type SuggestionProvider = (
|
export type SuggestionProvider = (
|
||||||
predicate: string,
|
predicate: string,
|
||||||
expression: string
|
expression: string
|
||||||
) => Promise<AutocompleteSuggestion[]>;
|
) => Promise<AutocompleteSuggestion[]>;
|
||||||
|
|
||||||
|
export interface SearchBarHandleSearchDetail {
|
||||||
|
inputVal: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GrSearchBar {
|
export interface GrSearchBar {
|
||||||
$: {
|
$: {
|
||||||
restAPI: RestApiService & Element;
|
restAPI: RestApiService & Element;
|
||||||
@@ -254,7 +258,8 @@ export class GrSearchBar extends KeyboardShortcutMixin(
|
|||||||
} else {
|
} else {
|
||||||
target.blur();
|
target.blur();
|
||||||
}
|
}
|
||||||
const trimmedInput = this._inputVal && this._inputVal.trim();
|
if (!this._inputVal) return;
|
||||||
|
const trimmedInput = this._inputVal.trim();
|
||||||
if (trimmedInput) {
|
if (trimmedInput) {
|
||||||
const predefinedOpOnlyQuery = [
|
const predefinedOpOnlyQuery = [
|
||||||
...SEARCH_OPERATORS_WITH_NEGATIONS_SET,
|
...SEARCH_OPERATORS_WITH_NEGATIONS_SET,
|
||||||
@@ -262,9 +267,12 @@ export class GrSearchBar extends KeyboardShortcutMixin(
|
|||||||
if (predefinedOpOnlyQuery) {
|
if (predefinedOpOnlyQuery) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const detail: SearchBarHandleSearchDetail = {
|
||||||
|
inputVal: this._inputVal,
|
||||||
|
};
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent('handle-search', {
|
new CustomEvent('handle-search', {
|
||||||
detail: {inputVal: this._inputVal},
|
detail,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,180 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
|
||||||
import '../gr-search-bar/gr-search-bar.js';
|
|
||||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
|
||||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
|
||||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
|
||||||
import {htmlTemplate} from './gr-smart-search_html.js';
|
|
||||||
import {GerritNav} from '../gr-navigation/gr-navigation.js';
|
|
||||||
import {getUserName} from '../../../utils/display-name-util.js';
|
|
||||||
|
|
||||||
const MAX_AUTOCOMPLETE_RESULTS = 10;
|
|
||||||
const SELF_EXPRESSION = 'self';
|
|
||||||
const ME_EXPRESSION = 'me';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends PolymerElement
|
|
||||||
*/
|
|
||||||
class GrSmartSearch extends GestureEventListeners(
|
|
||||||
LegacyElementMixin(
|
|
||||||
PolymerElement)) {
|
|
||||||
static get template() { return htmlTemplate; }
|
|
||||||
|
|
||||||
static get is() { return 'gr-smart-search'; }
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
searchQuery: String,
|
|
||||||
_config: Object,
|
|
||||||
_projectSuggestions: {
|
|
||||||
type: Function,
|
|
||||||
value() {
|
|
||||||
return (predicate, expression) =>
|
|
||||||
this._fetchProjects(predicate, expression);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_groupSuggestions: {
|
|
||||||
type: Function,
|
|
||||||
value() {
|
|
||||||
return (predicate, expression) =>
|
|
||||||
this._fetchGroups(predicate, expression);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_accountSuggestions: {
|
|
||||||
type: Function,
|
|
||||||
value() {
|
|
||||||
return (predicate, expression) =>
|
|
||||||
this._fetchAccounts(predicate, expression);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Invisible label for input element. This label is exposed to
|
|
||||||
* screen readers by nested element
|
|
||||||
*/
|
|
||||||
label: {
|
|
||||||
type: String,
|
|
||||||
value: '',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
attached() {
|
|
||||||
super.attached();
|
|
||||||
this.$.restAPI.getConfig().then(cfg => {
|
|
||||||
this._config = cfg;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleSearch(e) {
|
|
||||||
const input = e.detail.inputVal;
|
|
||||||
if (input) {
|
|
||||||
GerritNav.navigateToSearchQuery(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch from the API the predicted projects.
|
|
||||||
*
|
|
||||||
* @param {string} predicate - The first part of the search term, e.g.
|
|
||||||
* 'project'
|
|
||||||
* @param {string} expression - The second part of the search term, e.g.
|
|
||||||
* 'gerr'
|
|
||||||
* @return {!Promise} This returns a promise that resolves to an array of
|
|
||||||
* strings.
|
|
||||||
*/
|
|
||||||
_fetchProjects(predicate, expression) {
|
|
||||||
return this.$.restAPI.getSuggestedProjects(
|
|
||||||
expression,
|
|
||||||
MAX_AUTOCOMPLETE_RESULTS)
|
|
||||||
.then(projects => {
|
|
||||||
if (!projects) { return []; }
|
|
||||||
const keys = Object.keys(projects);
|
|
||||||
return keys.map(key => { return {text: predicate + ':' + key}; });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch from the API the predicted groups.
|
|
||||||
*
|
|
||||||
* @param {string} predicate - The first part of the search term, e.g.
|
|
||||||
* 'ownerin'
|
|
||||||
* @param {string} expression - The second part of the search term, e.g.
|
|
||||||
* 'polyger'
|
|
||||||
* @return {!Promise} This returns a promise that resolves to an array of
|
|
||||||
* strings.
|
|
||||||
*/
|
|
||||||
_fetchGroups(predicate, expression) {
|
|
||||||
if (expression.length === 0) { return Promise.resolve([]); }
|
|
||||||
return this.$.restAPI.getSuggestedGroups(
|
|
||||||
expression,
|
|
||||||
MAX_AUTOCOMPLETE_RESULTS)
|
|
||||||
.then(groups => {
|
|
||||||
if (!groups) { return []; }
|
|
||||||
const keys = Object.keys(groups);
|
|
||||||
return keys.map(key => { return {text: predicate + ':' + key}; });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch from the API the predicted accounts.
|
|
||||||
*
|
|
||||||
* @param {string} predicate - The first part of the search term, e.g.
|
|
||||||
* 'owner'
|
|
||||||
* @param {string} expression - The second part of the search term, e.g.
|
|
||||||
* 'kasp'
|
|
||||||
* @return {!Promise} This returns a promise that resolves to an array of
|
|
||||||
* strings.
|
|
||||||
*/
|
|
||||||
_fetchAccounts(predicate, expression) {
|
|
||||||
if (expression.length === 0) { return Promise.resolve([]); }
|
|
||||||
return this.$.restAPI.getSuggestedAccounts(
|
|
||||||
expression,
|
|
||||||
MAX_AUTOCOMPLETE_RESULTS)
|
|
||||||
.then(accounts => {
|
|
||||||
if (!accounts) { return []; }
|
|
||||||
return this._mapAccountsHelper(accounts, predicate);
|
|
||||||
})
|
|
||||||
.then(accounts => {
|
|
||||||
// When the expression supplied is a beginning substring of 'self',
|
|
||||||
// add it as an autocomplete option.
|
|
||||||
if (SELF_EXPRESSION.startsWith(expression)) {
|
|
||||||
return accounts.concat(
|
|
||||||
[{text: predicate + ':' + SELF_EXPRESSION}]);
|
|
||||||
} else if (ME_EXPRESSION.startsWith(expression)) {
|
|
||||||
return accounts.concat([{text: predicate + ':' + ME_EXPRESSION}]);
|
|
||||||
} else {
|
|
||||||
return accounts;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_mapAccountsHelper(accounts, predicate) {
|
|
||||||
return accounts.map(account => {
|
|
||||||
const userName = getUserName(this._serverConfig, account);
|
|
||||||
return {
|
|
||||||
label: account.name || '',
|
|
||||||
text: account.email ?
|
|
||||||
`${predicate}:${account.email}` :
|
|
||||||
`${predicate}:"${userName}"`,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define(GrSmartSearch.is, GrSmartSearch);
|
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
import '../../shared/gr-rest-api-interface/gr-rest-api-interface';
|
||||||
|
import '../gr-search-bar/gr-search-bar';
|
||||||
|
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
|
||||||
|
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
|
||||||
|
import {PolymerElement} from '@polymer/polymer/polymer-element';
|
||||||
|
import {htmlTemplate} from './gr-smart-search_html';
|
||||||
|
import {GerritNav} from '../gr-navigation/gr-navigation';
|
||||||
|
import {getUserName} from '../../../utils/display-name-util';
|
||||||
|
import {customElement, property} from '@polymer/decorators';
|
||||||
|
import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api';
|
||||||
|
import {AccountInfo, ServerInfo} from '../../../types/common';
|
||||||
|
import {
|
||||||
|
SearchBarHandleSearchDetail,
|
||||||
|
SuggestionProvider,
|
||||||
|
} from '../gr-search-bar/gr-search-bar';
|
||||||
|
import {AutocompleteSuggestion} from '../../shared/gr-autocomplete/gr-autocomplete';
|
||||||
|
|
||||||
|
const MAX_AUTOCOMPLETE_RESULTS = 10;
|
||||||
|
const SELF_EXPRESSION = 'self';
|
||||||
|
const ME_EXPRESSION = 'me';
|
||||||
|
|
||||||
|
export interface GrSmartSearch {
|
||||||
|
$: {
|
||||||
|
restAPI: RestApiService & Element;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement('gr-smart-search')
|
||||||
|
export class GrSmartSearch extends GestureEventListeners(
|
||||||
|
LegacyElementMixin(PolymerElement)
|
||||||
|
) {
|
||||||
|
static get template() {
|
||||||
|
return htmlTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property({type: String})
|
||||||
|
searchQuery?: string;
|
||||||
|
|
||||||
|
@property({type: Object})
|
||||||
|
_config?: ServerInfo;
|
||||||
|
|
||||||
|
@property({type: Object})
|
||||||
|
_projectSuggestions: SuggestionProvider = (predicate, expression) =>
|
||||||
|
this._fetchProjects(predicate, expression);
|
||||||
|
|
||||||
|
@property({type: Object})
|
||||||
|
_groupSuggestions: SuggestionProvider = (predicate, expression) =>
|
||||||
|
this._fetchGroups(predicate, expression);
|
||||||
|
|
||||||
|
@property({type: Object})
|
||||||
|
_accountSuggestions: SuggestionProvider = (predicate, expression) =>
|
||||||
|
this._fetchAccounts(predicate, expression);
|
||||||
|
|
||||||
|
@property({type: String})
|
||||||
|
label = '';
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
attached() {
|
||||||
|
super.attached();
|
||||||
|
this.$.restAPI.getConfig().then(cfg => {
|
||||||
|
this._config = cfg;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleSearch(e: CustomEvent<SearchBarHandleSearchDetail>) {
|
||||||
|
const input = e.detail.inputVal;
|
||||||
|
if (input) {
|
||||||
|
GerritNav.navigateToSearchQuery(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch from the API the predicted projects.
|
||||||
|
*
|
||||||
|
* @param predicate - The first part of the search term, e.g.
|
||||||
|
* 'project'
|
||||||
|
* @param expression - The second part of the search term, e.g.
|
||||||
|
* 'gerr'
|
||||||
|
*/
|
||||||
|
_fetchProjects(
|
||||||
|
predicate: string,
|
||||||
|
expression: string
|
||||||
|
): Promise<AutocompleteSuggestion[]> {
|
||||||
|
return this.$.restAPI
|
||||||
|
.getSuggestedProjects(expression, MAX_AUTOCOMPLETE_RESULTS)
|
||||||
|
.then(projects => {
|
||||||
|
if (!projects) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const keys = Object.keys(projects);
|
||||||
|
return keys.map(key => {
|
||||||
|
return {text: predicate + ':' + key};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch from the API the predicted groups.
|
||||||
|
*
|
||||||
|
* @param predicate - The first part of the search term, e.g.
|
||||||
|
* 'ownerin'
|
||||||
|
* @param expression - The second part of the search term, e.g.
|
||||||
|
* 'polyger'
|
||||||
|
*/
|
||||||
|
_fetchGroups(
|
||||||
|
predicate: string,
|
||||||
|
expression: string
|
||||||
|
): Promise<AutocompleteSuggestion[]> {
|
||||||
|
if (expression.length === 0) {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
return this.$.restAPI
|
||||||
|
.getSuggestedGroups(expression, MAX_AUTOCOMPLETE_RESULTS)
|
||||||
|
.then(groups => {
|
||||||
|
if (!groups) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const keys = Object.keys(groups);
|
||||||
|
return keys.map(key => {
|
||||||
|
return {text: predicate + ':' + key};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch from the API the predicted accounts.
|
||||||
|
*
|
||||||
|
* @param predicate - The first part of the search term, e.g.
|
||||||
|
* 'owner'
|
||||||
|
* @param expression - The second part of the search term, e.g.
|
||||||
|
* 'kasp'
|
||||||
|
*/
|
||||||
|
_fetchAccounts(
|
||||||
|
predicate: string,
|
||||||
|
expression: string
|
||||||
|
): Promise<AutocompleteSuggestion[]> {
|
||||||
|
if (expression.length === 0) {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
return this.$.restAPI
|
||||||
|
.getSuggestedAccounts(expression, MAX_AUTOCOMPLETE_RESULTS)
|
||||||
|
.then(accounts => {
|
||||||
|
if (!accounts) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this._mapAccountsHelper(accounts, predicate);
|
||||||
|
})
|
||||||
|
.then(accounts => {
|
||||||
|
// When the expression supplied is a beginning substring of 'self',
|
||||||
|
// add it as an autocomplete option.
|
||||||
|
if (SELF_EXPRESSION.startsWith(expression)) {
|
||||||
|
return accounts.concat([{text: predicate + ':' + SELF_EXPRESSION}]);
|
||||||
|
} else if (ME_EXPRESSION.startsWith(expression)) {
|
||||||
|
return accounts.concat([{text: predicate + ':' + ME_EXPRESSION}]);
|
||||||
|
} else {
|
||||||
|
return accounts;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_mapAccountsHelper(
|
||||||
|
accounts: AccountInfo[],
|
||||||
|
predicate: string
|
||||||
|
): AutocompleteSuggestion[] {
|
||||||
|
return accounts.map(account => {
|
||||||
|
const userName = getUserName(this._config, account);
|
||||||
|
return {
|
||||||
|
label: account.name || '',
|
||||||
|
text: account.email
|
||||||
|
? `${predicate}:${account.email}`
|
||||||
|
: `${predicate}:"${userName}"`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'gr-smart-search': GrSmartSearch;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -143,7 +143,7 @@ export class GrCommentThread extends KeyboardShortcutMixin(
|
|||||||
notify: true,
|
notify: true,
|
||||||
computed: '_computeRootId(comments.*)',
|
computed: '_computeRootId(comments.*)',
|
||||||
})
|
})
|
||||||
rootId?: string;
|
rootId?: UrlEncodedCommentId;
|
||||||
|
|
||||||
@property({type: Boolean})
|
@property({type: Boolean})
|
||||||
showFilePath = false;
|
showFilePath = false;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
RevisionInfo,
|
RevisionInfo,
|
||||||
} from '../../../types/common';
|
} from '../../../types/common';
|
||||||
import {GrAnnotationActionsInterface} from './gr-annotation-actions-js-api';
|
import {GrAnnotationActionsInterface} from './gr-annotation-actions-js-api';
|
||||||
import {GrAdminApi} from '../../plugins/gr-admin-api/gr-admin-api';
|
import {GrAdminApi, MenuLink} from '../../plugins/gr-admin-api/gr-admin-api';
|
||||||
import {
|
import {
|
||||||
JsApiService,
|
JsApiService,
|
||||||
EventCallback,
|
EventCallback,
|
||||||
@@ -294,8 +294,8 @@ export class GrJsApiInterface
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAdminMenuLinks() {
|
getAdminMenuLinks(): MenuLink[] {
|
||||||
const links = [];
|
const links: MenuLink[] = [];
|
||||||
for (const cb of this._getEventCallbacks(EventType.ADMIN_MENU_LINKS)) {
|
for (const cb of this._getEventCallbacks(EventType.ADMIN_MENU_LINKS)) {
|
||||||
const adminApi = (cb as unknown) as GrAdminApi;
|
const adminApi = (cb as unknown) as GrAdminApi;
|
||||||
links.push(...adminApi.getMenuLinks());
|
links.push(...adminApi.getMenuLinks());
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {ActionInfo, ChangeInfo, PatchSetNum} from '../../../types/common';
|
|||||||
import {EventType, TargetElement} from '../../plugins/gr-plugin-types';
|
import {EventType, TargetElement} from '../../plugins/gr-plugin-types';
|
||||||
import {DiffLayer} from '../../../types/types';
|
import {DiffLayer} from '../../../types/types';
|
||||||
import {GrAnnotationActionsInterface} from './gr-annotation-actions-js-api';
|
import {GrAnnotationActionsInterface} from './gr-annotation-actions-js-api';
|
||||||
|
import {MenuLink} from '../../plugins/gr-admin-api/gr-admin-api';
|
||||||
|
|
||||||
export interface ShowChangeDetail {
|
export interface ShowChangeDetail {
|
||||||
change: ChangeInfo;
|
change: ChangeInfo;
|
||||||
@@ -50,5 +51,6 @@ export interface JsApiService {
|
|||||||
getDiffLayers(path: string, changeNum: number): DiffLayer[];
|
getDiffLayers(path: string, changeNum: number): DiffLayer[];
|
||||||
disposeDiffLayers(path: string): void;
|
disposeDiffLayers(path: string): void;
|
||||||
getCoverageAnnotationApi(): Promise<GrAnnotationActionsInterface | undefined>;
|
getCoverageAnnotationApi(): Promise<GrAnnotationActionsInterface | undefined>;
|
||||||
|
getAdminMenuLinks(): MenuLink[];
|
||||||
// TODO(TS): Add more methods when needed for the TS conversion.
|
// TODO(TS): Add more methods when needed for the TS conversion.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ import {
|
|||||||
RevisionId,
|
RevisionId,
|
||||||
GroupName,
|
GroupName,
|
||||||
Hashtag,
|
Hashtag,
|
||||||
|
TopMenuEntryInfo,
|
||||||
} from '../../../types/common';
|
} from '../../../types/common';
|
||||||
import {
|
import {
|
||||||
CancelConditionCallback,
|
CancelConditionCallback,
|
||||||
@@ -1603,7 +1604,10 @@ export class GrRestApiInterface
|
|||||||
}) as Promise<string[] | undefined>;
|
}) as Promise<string[] | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
getChangeOrEditFiles(changeNum: NumericChangeId, patchRange: PatchRange) {
|
getChangeOrEditFiles(
|
||||||
|
changeNum: NumericChangeId,
|
||||||
|
patchRange: PatchRange
|
||||||
|
): Promise<FileNameToFileInfoMap | undefined> {
|
||||||
if (patchNumEquals(patchRange.patchNum, EditPatchSetNum)) {
|
if (patchNumEquals(patchRange.patchNum, EditPatchSetNum)) {
|
||||||
return this.getChangeEditFiles(changeNum, patchRange).then(
|
return this.getChangeEditFiles(changeNum, patchRange).then(
|
||||||
res => res && res.files
|
res => res && res.files
|
||||||
@@ -2086,13 +2090,16 @@ export class GrRestApiInterface
|
|||||||
}) as Promise<ChangeInfo[] | undefined>;
|
}) as Promise<ChangeInfo[] | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
getReviewedFiles(changeNum: NumericChangeId, patchNum: PatchSetNum) {
|
getReviewedFiles(
|
||||||
|
changeNum: NumericChangeId,
|
||||||
|
patchNum: PatchSetNum
|
||||||
|
): Promise<string[] | undefined> {
|
||||||
return this._getChangeURLAndFetch({
|
return this._getChangeURLAndFetch({
|
||||||
changeNum,
|
changeNum,
|
||||||
endpoint: '/files?reviewed',
|
endpoint: '/files?reviewed',
|
||||||
patchNum,
|
patchNum,
|
||||||
reportEndpointAsIs: true,
|
reportEndpointAsIs: true,
|
||||||
});
|
}) as Promise<string[] | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveFileReviewed(
|
saveFileReviewed(
|
||||||
@@ -3215,12 +3222,12 @@ export class GrRestApiInterface
|
|||||||
}) as Promise<CapabilityInfoMap | undefined>;
|
}) as Promise<CapabilityInfoMap | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTopMenus(errFn?: ErrorCallback) {
|
getTopMenus(errFn?: ErrorCallback): Promise<TopMenuEntryInfo[] | undefined> {
|
||||||
return this._fetchSharedCacheURL({
|
return this._fetchSharedCacheURL({
|
||||||
url: '/config/server/top-menus',
|
url: '/config/server/top-menus',
|
||||||
errFn,
|
errFn,
|
||||||
reportUrlAsIs: true,
|
reportUrlAsIs: true,
|
||||||
});
|
}) as Promise<TopMenuEntryInfo[] | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAssignee(
|
setAssignee(
|
||||||
|
|||||||
@@ -1082,7 +1082,7 @@ export interface KeyboardShortcutMixinInterface {
|
|||||||
_shortcut_v_key_last_pressed: number | null;
|
_shortcut_v_key_last_pressed: number | null;
|
||||||
_shortcut_go_table: Map<string, string>;
|
_shortcut_go_table: Map<string, string>;
|
||||||
_shortcut_v_table: Map<string, string>;
|
_shortcut_v_table: Map<string, string>;
|
||||||
keyboardShortcuts(): {[key: string]: string};
|
keyboardShortcuts(): {[key: string]: string | null};
|
||||||
createTitle(name: Shortcut, section: ShortcutSection): string;
|
createTitle(name: Shortcut, section: ShortcutSection): string;
|
||||||
bindShortcut(shortcut: Shortcut, ...bindings: string[]): void;
|
bindShortcut(shortcut: Shortcut, ...bindings: string[]): void;
|
||||||
shouldSuppressKeyboardShortcut(event: CustomKeyboardEvent): boolean;
|
shouldSuppressKeyboardShortcut(event: CustomKeyboardEvent): boolean;
|
||||||
|
|||||||
@@ -96,6 +96,8 @@ import {
|
|||||||
DashboardId,
|
DashboardId,
|
||||||
HashtagsInput,
|
HashtagsInput,
|
||||||
Hashtag,
|
Hashtag,
|
||||||
|
FileNameToFileInfoMap,
|
||||||
|
TopMenuEntryInfo,
|
||||||
} from '../../../types/common';
|
} from '../../../types/common';
|
||||||
import {ParsedChangeInfo} from '../../../elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser';
|
import {ParsedChangeInfo} from '../../../elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser';
|
||||||
import {HttpMethod, IgnoreWhitespaceType} from '../../../constants/constants';
|
import {HttpMethod, IgnoreWhitespaceType} from '../../../constants/constants';
|
||||||
@@ -815,4 +817,31 @@ export interface RestApiService {
|
|||||||
changeNum: NumericChangeId,
|
changeNum: NumericChangeId,
|
||||||
topic: string | null
|
topic: string | null
|
||||||
): Promise<string>;
|
): Promise<string>;
|
||||||
|
|
||||||
|
getChangeOrEditFiles(
|
||||||
|
changeNum: NumericChangeId,
|
||||||
|
patchRange: PatchRange
|
||||||
|
): Promise<FileNameToFileInfoMap | undefined>;
|
||||||
|
|
||||||
|
getReviewedFiles(
|
||||||
|
changeNum: NumericChangeId,
|
||||||
|
patchNum: PatchSetNum
|
||||||
|
): Promise<string[] | undefined>;
|
||||||
|
|
||||||
|
saveFileReviewed(
|
||||||
|
changeNum: NumericChangeId,
|
||||||
|
patchNum: PatchSetNum,
|
||||||
|
path: string,
|
||||||
|
reviewed: boolean
|
||||||
|
): Promise<Response>;
|
||||||
|
|
||||||
|
saveFileReviewed(
|
||||||
|
changeNum: NumericChangeId,
|
||||||
|
patchNum: PatchSetNum,
|
||||||
|
path: string,
|
||||||
|
reviewed: boolean,
|
||||||
|
errFn: ErrorCallback
|
||||||
|
): Promise<Response | undefined>;
|
||||||
|
|
||||||
|
getTopMenus(errFn?: ErrorCallback): Promise<TopMenuEntryInfo[] | undefined>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import {
|
|||||||
DraftsAction,
|
DraftsAction,
|
||||||
NotifyType,
|
NotifyType,
|
||||||
EmailFormat,
|
EmailFormat,
|
||||||
|
AuthType,
|
||||||
} from '../constants/constants';
|
} from '../constants/constants';
|
||||||
import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
|
import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
|
||||||
|
|
||||||
@@ -743,10 +744,10 @@ export interface AccountsConfigInfo {
|
|||||||
/**
|
/**
|
||||||
* The AuthInfo entity contains information about the authentication
|
* The AuthInfo entity contains information about the authentication
|
||||||
* configuration of the Gerrit server.
|
* configuration of the Gerrit server.
|
||||||
* https://gerrit-review.googlesource.com/Documentation/rest-api-config.html
|
* https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#auth-info
|
||||||
*/
|
*/
|
||||||
export interface AuthInfo {
|
export interface AuthInfo {
|
||||||
type: string;
|
auth_type: AuthType; // docs incorrectly names it 'type'
|
||||||
use_contributor_agreements: boolean;
|
use_contributor_agreements: boolean;
|
||||||
contributor_agreements?: ContributorAgreementInfo;
|
contributor_agreements?: ContributorAgreementInfo;
|
||||||
editable_account_fields: string;
|
editable_account_fields: string;
|
||||||
@@ -1124,17 +1125,17 @@ export interface ThreadSummaryInfo {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The TopMenuEntryInfo entity contains information about a top menu entry.
|
* The TopMenuEntryInfo entity contains information about a top menu entry.
|
||||||
* https://gerrit-review.googlesource.com/Documentation/rest-api-config.html
|
* https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#top-menu-entry-info
|
||||||
*/
|
*/
|
||||||
export interface TopMenuEntryInfo {
|
export interface TopMenuEntryInfo {
|
||||||
name: string;
|
name: string;
|
||||||
items: string;
|
items: TopMenuItemInfo[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The TopMenuItemInfo entity contains information about a menu item ina top
|
* The TopMenuItemInfo entity contains information about a menu item ina top
|
||||||
* menu entry.
|
* menu entry.
|
||||||
* https://gerrit-review.googlesource.com/Documentation/rest-api-config.html
|
* https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#top-menu-item-info
|
||||||
*/
|
*/
|
||||||
export interface TopMenuItemInfo {
|
export interface TopMenuItemInfo {
|
||||||
url: string;
|
url: string;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
*/
|
*/
|
||||||
export function asyncForeach<T>(
|
export function asyncForeach<T>(
|
||||||
array: T[],
|
array: T[],
|
||||||
fn: (item: T, stopCallback: () => void) => Promise<T>
|
fn: (item: T, stopCallback: () => void) => Promise<unknown>
|
||||||
): Promise<T | void> {
|
): Promise<T | void> {
|
||||||
if (!array.length) {
|
if (!array.length) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|||||||
@@ -211,13 +211,13 @@ export function getEventPath<T extends PolymerEvent>(e?: T) {
|
|||||||
export function descendedFromClass(
|
export function descendedFromClass(
|
||||||
element: Element,
|
element: Element,
|
||||||
className: string,
|
className: string,
|
||||||
opt_stopElement: Element
|
stopElement?: Element
|
||||||
) {
|
) {
|
||||||
let isDescendant = element.classList.contains(className);
|
let isDescendant = element.classList.contains(className);
|
||||||
while (
|
while (
|
||||||
!isDescendant &&
|
!isDescendant &&
|
||||||
element.parentElement &&
|
element.parentElement &&
|
||||||
(!opt_stopElement || element.parentElement !== opt_stopElement)
|
(!stopElement || element.parentElement !== stopElement)
|
||||||
) {
|
) {
|
||||||
isDescendant = element.classList.contains(className);
|
isDescendant = element.classList.contains(className);
|
||||||
element = element.parentElement;
|
element = element.parentElement;
|
||||||
|
|||||||
Reference in New Issue
Block a user