Files
gerrit/polygerrit-ui/app/elements/gr-app.js
Thomas Draebing 1fbf53df60 Add UI element to display messages of the day
This change adds an UI element that displays messages registered with
the MessageOfTheDay extension point. The messages will be displayed
below the main navigation header. This functionality was already part
of the GWT-UI, but was never migrated to the new UI.

The messages can be dismissed by a button click, that will create a
cookie that will prevent the message from being displayed again until
the redisplay time configured with the message.

The content of the message can be HTML. Depending on where the message-
content is coming from, this might be harmful. In case of the
messageoftheday plugin, the message is added as a config from the Gerrit
server site, which can probably be considered save. However, in a future
change rendering HTML should be made configurable and switched off by
default. Text should then be rendered using `gr-formatted-text`.

Change-Id: I2cbbcbcd49734b645701b9fac62a016a269d37f2
2020-01-31 10:14:27 +01:00

475 lines
15 KiB
JavaScript

/**
* @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.
*/
(function() {
'use strict';
// Eagerly render Polymer components when backgrounded. (Skips
// requestAnimationFrame.)
// @see https://github.com/Polymer/polymer/issues/3851
// TODO: Reassess after Polymer 2.0 upgrade.
// @see Issue 4699
Polymer.RenderStatus._makeReady();
Polymer({
is: 'gr-app',
/**
* Fired when the URL location changes.
*
* @event location-change
*/
properties: {
/**
* @type {{ query: string, view: string, screen: string }}
*/
params: Object,
keyEventTarget: {
type: Object,
value() { return document.body; },
},
_account: {
type: Object,
observer: '_accountChanged',
},
/**
* The last time the g key was pressed in milliseconds (or a keydown event
* was handled if the key is held down).
*
* @type {number|null}
*/
_lastGKeyPressTimestamp: {
type: Number,
value: null,
},
/**
* @type {{ plugin: Object }}
*/
_serverConfig: Object,
_version: String,
_showChangeListView: Boolean,
_showDashboardView: Boolean,
_showChangeView: Boolean,
_showDiffView: Boolean,
_showSettingsView: Boolean,
_showAdminView: Boolean,
_showCLAView: Boolean,
_showEditorView: Boolean,
_showPluginScreen: Boolean,
_showDocumentationSearch: Boolean,
/** @type {?} */
_viewState: Object,
/** @type {?} */
_lastError: Object,
_lastSearchPage: String,
_path: String,
_isShadowDom: Boolean,
_pluginScreenName: {
type: String,
computed: '_computePluginScreenName(params)',
},
_settingsUrl: String,
_feedbackUrl: String,
},
listeners: {
'page-error': '_handlePageError',
'title-change': '_handleTitleChange',
'location-change': '_handleLocationChange',
'rpc-log': '_handleRpcLog',
},
observers: [
'_viewChanged(params.view)',
'_paramsChanged(params.*)',
],
behaviors: [
Gerrit.BaseUrlBehavior,
Gerrit.KeyboardShortcutBehavior,
],
keyboardShortcuts() {
return {
[this.Shortcut.OPEN_SHORTCUT_HELP_DIALOG]: '_showKeyboardShortcuts',
[this.Shortcut.GO_TO_USER_DASHBOARD]: '_goToUserDashboard',
[this.Shortcut.GO_TO_OPENED_CHANGES]: '_goToOpenedChanges',
[this.Shortcut.GO_TO_MERGED_CHANGES]: '_goToMergedChanges',
[this.Shortcut.GO_TO_ABANDONED_CHANGES]: '_goToAbandonedChanges',
[this.Shortcut.GO_TO_WATCHED_CHANGES]: '_goToWatchedChanges',
};
},
created() {
this._bindKeyboardShortcuts();
},
ready() {
this._isShadowDom = Polymer.Settings.useShadow;
this.$.router.start();
this.$.restAPI.getAccount().then(account => {
this._account = account;
});
this.$.restAPI.getConfig().then(config => {
this._serverConfig = config;
if (config && config.gerrit && config.gerrit.report_bug_url) {
this._feedbackUrl = config.gerrit.report_bug_url;
}
});
this.$.restAPI.getVersion().then(version => {
this._version = version;
this._logWelcome();
});
if (window.localStorage.getItem('dark-theme')) {
this.$.libLoader.getDarkTheme().then(module => {
Polymer.dom(this.root).appendChild(module);
});
}
// Note: this is evaluated here to ensure that it only happens after the
// router has been initialized. @see Issue 7837
this._settingsUrl = Gerrit.Nav.getUrlForSettings();
this.$.reporting.appStarted(document.visibilityState === 'hidden');
this._viewState = {
changeView: {
changeNum: null,
patchRange: null,
selectedFileIndex: 0,
showReplyDialog: false,
diffMode: null,
numFilesShown: null,
scrollTop: 0,
},
changeListView: {
query: null,
offset: 0,
selectedChangeIndex: 0,
},
dashboardView: {
selectedChangeIndex: 0,
},
};
},
_bindKeyboardShortcuts() {
this.bindShortcut(this.Shortcut.SEND_REPLY,
this.DOC_ONLY, 'ctrl+enter', 'meta+enter');
this.bindShortcut(
this.Shortcut.OPEN_SHORTCUT_HELP_DIALOG, '?');
this.bindShortcut(
this.Shortcut.GO_TO_USER_DASHBOARD, this.GO_KEY, 'i');
this.bindShortcut(
this.Shortcut.GO_TO_OPENED_CHANGES, this.GO_KEY, 'o');
this.bindShortcut(
this.Shortcut.GO_TO_MERGED_CHANGES, this.GO_KEY, 'm');
this.bindShortcut(
this.Shortcut.GO_TO_ABANDONED_CHANGES, this.GO_KEY, 'a');
this.bindShortcut(
this.Shortcut.GO_TO_WATCHED_CHANGES, this.GO_KEY, 'w');
this.bindShortcut(
this.Shortcut.CURSOR_NEXT_CHANGE, 'j');
this.bindShortcut(
this.Shortcut.CURSOR_PREV_CHANGE, 'k');
this.bindShortcut(
this.Shortcut.OPEN_CHANGE, 'o');
this.bindShortcut(
this.Shortcut.NEXT_PAGE, 'n', ']');
this.bindShortcut(
this.Shortcut.PREV_PAGE, 'p', '[');
this.bindShortcut(
this.Shortcut.TOGGLE_CHANGE_REVIEWED, 'r');
this.bindShortcut(
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');
this.bindShortcut(
this.Shortcut.OPEN_DOWNLOAD_DIALOG, 'd');
this.bindShortcut(
this.Shortcut.EXPAND_ALL_MESSAGES, 'x');
this.bindShortcut(
this.Shortcut.COLLAPSE_ALL_MESSAGES, 'z');
this.bindShortcut(
this.Shortcut.REFRESH_CHANGE, 'shift+r');
this.bindShortcut(
this.Shortcut.UP_TO_DASHBOARD, 'u');
this.bindShortcut(
this.Shortcut.UP_TO_CHANGE, 'u');
this.bindShortcut(
this.Shortcut.TOGGLE_DIFF_MODE, 'm');
this.bindShortcut(
this.Shortcut.NEXT_LINE, 'j', 'down');
this.bindShortcut(
this.Shortcut.PREV_LINE, 'k', 'up');
this.bindShortcut(
this.Shortcut.NEXT_CHUNK, 'n');
this.bindShortcut(
this.Shortcut.PREV_CHUNK, 'p');
this.bindShortcut(
this.Shortcut.EXPAND_ALL_DIFF_CONTEXT, 'shift+x');
this.bindShortcut(
this.Shortcut.NEXT_COMMENT_THREAD, 'shift+n');
this.bindShortcut(
this.Shortcut.PREV_COMMENT_THREAD, 'shift+p');
this.bindShortcut(
this.Shortcut.EXPAND_ALL_COMMENT_THREADS, this.DOC_ONLY, 'e');
this.bindShortcut(
this.Shortcut.COLLAPSE_ALL_COMMENT_THREADS,
this.DOC_ONLY, 'shift+e');
this.bindShortcut(
this.Shortcut.LEFT_PANE, 'shift+left');
this.bindShortcut(
this.Shortcut.RIGHT_PANE, 'shift+right');
this.bindShortcut(
this.Shortcut.TOGGLE_LEFT_PANE, 'shift+a');
this.bindShortcut(
this.Shortcut.NEW_COMMENT, 'c');
this.bindShortcut(
this.Shortcut.SAVE_COMMENT,
'ctrl+enter', 'meta+enter', 'ctrl+s', 'meta+s');
this.bindShortcut(
this.Shortcut.OPEN_DIFF_PREFS, ',');
this.bindShortcut(
this.Shortcut.TOGGLE_DIFF_REVIEWED, 'r');
this.bindShortcut(
this.Shortcut.NEXT_FILE, ']');
this.bindShortcut(
this.Shortcut.PREV_FILE, '[');
this.bindShortcut(
this.Shortcut.NEXT_FILE_WITH_COMMENTS, 'shift+j');
this.bindShortcut(
this.Shortcut.PREV_FILE_WITH_COMMENTS, 'shift+k');
this.bindShortcut(
this.Shortcut.CURSOR_NEXT_FILE, 'j', 'down');
this.bindShortcut(
this.Shortcut.CURSOR_PREV_FILE, 'k', 'up');
this.bindShortcut(
this.Shortcut.OPEN_FILE, 'o', 'enter');
this.bindShortcut(
this.Shortcut.TOGGLE_FILE_REVIEWED, 'r');
this.bindShortcut(
this.Shortcut.NEXT_UNREVIEWED_FILE, 'shift+m');
this.bindShortcut(
this.Shortcut.TOGGLE_ALL_INLINE_DIFFS, 'shift+i:keyup');
this.bindShortcut(
this.Shortcut.TOGGLE_INLINE_DIFF, 'i:keyup');
this.bindShortcut(
this.Shortcut.OPEN_FIRST_FILE, ']');
this.bindShortcut(
this.Shortcut.OPEN_LAST_FILE, '[');
this.bindShortcut(
this.Shortcut.SEARCH, '/');
},
_accountChanged(account) {
if (!account) { return; }
// Preferences are cached when a user is logged in; warm them.
this.$.restAPI.getPreferences();
this.$.restAPI.getDiffPreferences();
this.$.restAPI.getEditPreferences();
this.$.errorManager.knownAccountId =
this._account && this._account._account_id || null;
},
_viewChanged(view) {
this.$.errorView.classList.remove('show');
this.set('_showChangeListView', view === Gerrit.Nav.View.SEARCH);
this.set('_showDashboardView', view === Gerrit.Nav.View.DASHBOARD);
this.set('_showChangeView', view === Gerrit.Nav.View.CHANGE);
this.set('_showDiffView', view === Gerrit.Nav.View.DIFF);
this.set('_showSettingsView', view === Gerrit.Nav.View.SETTINGS);
this.set('_showAdminView', view === Gerrit.Nav.View.ADMIN ||
view === Gerrit.Nav.View.GROUP || view === Gerrit.Nav.View.REPO);
this.set('_showCLAView', view === Gerrit.Nav.View.AGREEMENTS);
this.set('_showEditorView', view === Gerrit.Nav.View.EDIT);
const isPluginScreen = view === Gerrit.Nav.View.PLUGIN_SCREEN;
this.set('_showPluginScreen', false);
// Navigation within plugin screens does not restamp gr-endpoint-decorator
// because _showPluginScreen value does not change. To force restamp,
// change _showPluginScreen value between true and false.
if (isPluginScreen) {
this.async(() => this.set('_showPluginScreen', true), 1);
}
this.set('_showDocumentationSearch',
view === Gerrit.Nav.View.DOCUMENTATION_SEARCH);
if (this.params.justRegistered) {
this.$.registrationOverlay.open();
this.$.registrationDialog.loadData().then(() => {
this.$.registrationOverlay.refit();
});
}
this.$.header.unfloat();
},
_computeShowGwtUiLink(config) {
return !window.DEPRECATE_GWT_UI &&
config.gerrit.web_uis && config.gerrit.web_uis.includes('GWT');
},
_getMessages(config) {
return config.messages ? config.messages : [];
},
_handlePageError(e) {
const props = [
'_showChangeListView',
'_showDashboardView',
'_showChangeView',
'_showDiffView',
'_showSettingsView',
'_showAdminView',
];
for (const showProp of props) {
this.set(showProp, false);
}
this.$.errorView.classList.add('show');
const response = e.detail.response;
const err = {text: [response.status, response.statusText].join(' ')};
if (response.status === 404) {
err.emoji = '¯\\_(ツ)_/¯';
this._lastError = err;
} else {
err.emoji = 'o_O';
response.text().then(text => {
err.moreInfo = text;
this._lastError = err;
});
}
},
_handleLocationChange(e) {
const hash = e.detail.hash.substring(1);
let pathname = e.detail.pathname;
if (pathname.startsWith('/c/') && parseInt(hash, 10) > 0) {
pathname += '@' + hash;
}
this.set('_path', pathname);
},
_paramsChanged(paramsRecord) {
const params = paramsRecord.base;
const viewsToCheck = [Gerrit.Nav.View.SEARCH, Gerrit.Nav.View.DASHBOARD];
if (viewsToCheck.includes(params.view)) {
this.set('_lastSearchPage', location.pathname);
}
},
_handleTitleChange(e) {
if (e.detail.title) {
document.title = e.detail.title + ' · Gerrit Code Review';
} else {
document.title = '';
}
},
_showKeyboardShortcuts(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
this.$.keyboardShortcuts.open();
},
_handleKeyboardShortcutDialogClose() {
this.$.keyboardShortcuts.close();
},
_handleAccountDetailUpdate(e) {
this.$.mainHeader.reload();
if (this.params.view === Gerrit.Nav.View.SETTINGS) {
this.$$('gr-settings-view').reloadAccountDetail();
}
},
_handleRegistrationDialogClose(e) {
this.params.justRegistered = false;
this.$.registrationOverlay.close();
},
_computeShadowClass(isShadowDom) {
return isShadowDom ? 'shadow' : '';
},
_goToUserDashboard() {
Gerrit.Nav.navigateToUserDashboard();
},
_goToOpenedChanges() {
Gerrit.Nav.navigateToStatusSearch('open');
},
_goToMergedChanges() {
Gerrit.Nav.navigateToStatusSearch('merged');
},
_goToAbandonedChanges() {
Gerrit.Nav.navigateToStatusSearch('abandoned');
},
_goToWatchedChanges() {
// The query is hardcoded, and doesn't respect custom menu entries
Gerrit.Nav.navigateToSearchQuery('is:watched is:open');
},
_computePluginScreenName({plugin, screen}) {
return Gerrit._getPluginScreenName(plugin, screen);
},
_logWelcome() {
console.group('Runtime Info');
console.log('Gerrit UI (PolyGerrit)');
console.log(`Gerrit Server Version: ${this._version}`);
if (window.VERSION_INFO) {
console.log(`UI Version Info: ${window.VERSION_INFO}`);
}
const renderTime = new Date(window.performance.timing.loadEventStart);
console.log(`Document loaded at: ${renderTime}`);
if (this._feedbackUrl) {
console.log(`Please file bugs and feedback at: ${this._feedbackUrl}`);
}
console.groupEnd();
},
/**
* Intercept RPC log events emitted by REST API interfaces.
* Note: the REST API interface cannot use gr-reporting directly because
* that would create a cyclic dependency.
*/
_handleRpcLog(e) {
this.$.reporting.reportRpcTiming(e.detail.anonymizedUrl,
e.detail.elapsed);
},
});
})();