blob: b261a90a01d1ffecde91c5fa019e977ec1c31ecb [file] [log] [blame]
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04001/**
2 * @license
3 * Copyright (C) 2016 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
Andrew Bonventred2b90432016-03-29 14:12:33 -040017(function(window) {
18 'use strict';
19
Viktar Donich3e98dee2018-07-19 14:27:30 -070020 const PRELOADED_PROTOCOL = 'preloaded:';
21
Viktar Donich1d184ae2017-12-18 16:15:30 -080022 const PANEL_ENDPOINTS_MAPPING = {
23 CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK: 'change-view-integration',
24 CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK: 'change-metadata-item',
25 };
26
Tao Zhouc1d182d2019-10-15 10:23:22 +020027 // Import utils methods
28 const {
David Ostrovsky7dd43b32020-01-04 13:53:38 +010029 getPluginNameFromUrl,
30 send,
Tao Zhouc1d182d2019-10-15 10:23:22 +020031 } = window._apiUtils;
Andrew Bonventree665f082016-06-27 16:12:12 -040032
Viktar Donich8d369702017-07-19 11:21:13 -070033 /**
34 * Plugin-provided custom components can affect content in extension
35 * points using one of following methods:
36 * - DECORATE: custom component is set with `content` attribute and may
37 * decorate (e.g. style) DOM element.
38 * - REPLACE: contents of extension point are replaced with the custom
39 * component.
40 * - STYLE: custom component is a shared styles module that is inserted
41 * into the extension point.
42 */
Viktar Donich7d23f222017-05-17 09:25:06 -070043 const EndpointType = {
Viktar Donich8d369702017-07-19 11:21:13 -070044 DECORATE: 'decorate',
45 REPLACE: 'replace',
Viktar Donich7d23f222017-05-17 09:25:06 -070046 STYLE: 'style',
Viktar Donich7d23f222017-05-17 09:25:06 -070047 };
48
Andrew Bonventre4adac422016-04-19 14:33:53 -040049 function Plugin(opt_url) {
Viktar Donichab491f772017-08-15 08:02:58 -070050 this._domHooks = new GrDomHooksManager(this);
Viktar Donich5bbf1922017-06-05 15:20:52 -070051
Andrew Bonventree665f082016-06-27 16:12:12 -040052 if (!opt_url) {
53 console.warn('Plugin not being loaded from /plugins base path.',
54 'Unable to determine name.');
55 return;
56 }
Viktar Donich2f3ee892017-08-04 09:48:19 -070057 this.deprecated = {
Viktar Donich250bf092017-12-22 10:23:50 -080058 _loadedGwt: deprecatedAPI._loadedGwt.bind(this),
Viktar Donich172acf72017-10-23 16:17:23 -070059 install: deprecatedAPI.install.bind(this),
Viktar Donich172acf72017-10-23 16:17:23 -070060 onAction: deprecatedAPI.onAction.bind(this),
Viktar Donich1d184ae2017-12-18 16:15:30 -080061 panel: deprecatedAPI.panel.bind(this),
Viktar Donich6ea8c8a2017-12-12 14:41:47 -080062 popup: deprecatedAPI.popup.bind(this),
63 screen: deprecatedAPI.screen.bind(this),
Viktar Donich17c02262017-12-21 15:46:02 -080064 settingsScreen: deprecatedAPI.settingsScreen.bind(this),
Viktar Donich2f3ee892017-08-04 09:48:19 -070065 };
Viktar Donich15ef24c2017-12-07 16:04:38 -080066
67 this._url = new URL(opt_url);
68 this._name = getPluginNameFromUrl(this._url);
Viktar Donich196e7462018-08-13 17:51:10 -070069 if (this._url.protocol === PRELOADED_PROTOCOL) {
70 // Original plugin URL is used in plugin assets URLs calculation.
71 const assetsBaseUrl = window.ASSETS_PATH ||
72 (window.location.origin + Gerrit.BaseUrlBehavior.getBaseUrl());
73 this._url = new URL(assetsBaseUrl + '/plugins/' + this._name +
74 '/static/' + this._name + '.js');
75 }
Andrew Bonventre4adac422016-04-19 14:33:53 -040076 }
77
Andrew Bonventre8351fbb2016-06-15 14:33:20 -070078 Plugin._sharedAPIElement = document.createElement('gr-js-api-interface');
79
Andrew Bonventre4adac422016-04-19 14:33:53 -040080 Plugin.prototype._name = '';
Andrew Bonventred2b90432016-03-29 14:12:33 -040081
Andrew Bonventre387e3a42016-04-22 17:08:46 -040082 Plugin.prototype.getPluginName = function() {
83 return this._name;
84 };
85
Viktar Donich7d23f222017-05-17 09:25:06 -070086 Plugin.prototype.registerStyleModule = function(endpointName, moduleName) {
Viktar Donichd559f192017-07-24 13:23:18 -070087 Gerrit._endpoints.registerModule(
88 this, endpointName, EndpointType.STYLE, moduleName);
Viktar Donich7d23f222017-05-17 09:25:06 -070089 };
90
Thomas Shafer03275862019-02-26 15:39:16 -080091 /**
92 * Registers an endpoint for the plugin.
Thomas Draebing6cdfa902020-01-02 17:04:15 +010093 */
Viktar Donichd559f192017-07-24 13:23:18 -070094 Plugin.prototype.registerCustomComponent = function(
Viktar Donichab491f772017-08-15 08:02:58 -070095 endpointName, opt_moduleName, opt_options) {
Thomas Shafer03275862019-02-26 15:39:16 -080096 return this._registerCustomComponent(endpointName, opt_moduleName,
97 opt_options);
98 };
99
100 /**
101 * Registers a dynamic endpoint for the plugin.
102 *
103 * Dynamic plugins are registered by specific prefix, such as
104 * 'change-list-header'.
Thomas Draebing6cdfa902020-01-02 17:04:15 +0100105 */
Thomas Shafer03275862019-02-26 15:39:16 -0800106 Plugin.prototype.registerDynamicCustomComponent = function(
107 endpointName, opt_moduleName, opt_options) {
108 const fullEndpointName = `${endpointName}-${this.getPluginName()}`;
109 return this._registerCustomComponent(fullEndpointName, opt_moduleName,
110 opt_options, endpointName);
111 };
112
113 Plugin.prototype._registerCustomComponent = function(
114 endpointName, opt_moduleName, opt_options, dynamicEndpoint) {
Viktar Donich8d369702017-07-19 11:21:13 -0700115 const type = opt_options && opt_options.replace ?
Thomas Draebing6ff72df2019-12-27 10:58:03 +0100116 EndpointType.REPLACE : EndpointType.DECORATE;
Viktar Donichab491f772017-08-15 08:02:58 -0700117 const hook = this._domHooks.getDomHook(endpointName, opt_moduleName);
118 const moduleName = opt_moduleName || hook.getModuleName();
Viktar Donichd559f192017-07-24 13:23:18 -0700119 Gerrit._endpoints.registerModule(
Thomas Shafer03275862019-02-26 15:39:16 -0800120 this, endpointName, type, moduleName, hook, dynamicEndpoint);
Viktar Donichab491f772017-08-15 08:02:58 -0700121 return hook.getPublicAPI();
122 };
123
124 /**
125 * Returns instance of DOM hook API for endpoint. Creates a placeholder
126 * element for the first call.
127 */
128 Plugin.prototype.hook = function(endpointName, opt_options) {
129 return this.registerCustomComponent(endpointName, undefined, opt_options);
Viktar Donich9bd0ce62017-02-07 08:56:33 -0800130 };
131
Ravi Mistry9cd18d22016-11-30 10:18:41 -0500132 Plugin.prototype.getServerInfo = function() {
133 return document.createElement('gr-rest-api-interface').getConfig();
134 };
135
Andrew Bonventred2b90432016-03-29 14:12:33 -0400136 Plugin.prototype.on = function(eventName, callback) {
Andrew Bonventre8351fbb2016-06-15 14:33:20 -0700137 Plugin._sharedAPIElement.addEventCallback(eventName, callback);
Andrew Bonventred2b90432016-03-29 14:12:33 -0400138 };
139
Andrew Bonventre4adac422016-04-19 14:33:53 -0400140 Plugin.prototype.url = function(opt_path) {
Logan Hanks70d3e4c2019-01-02 10:28:03 -0800141 const relPath = '/plugins/' + this._name + (opt_path || '/');
142 if (window.location.origin === this._url.origin) {
143 // Plugin loaded from the same origin as gr-app, getBaseUrl in effect.
144 return this._url.origin + Gerrit.BaseUrlBehavior.getBaseUrl() + relPath;
145 } else {
146 // Plugin loaded from assets bundle, expect assets placed along with it.
147 return this._url.href.split('/plugins/' + this._name)[0] + relPath;
148 }
Andrew Bonventre4adac422016-04-19 14:33:53 -0400149 };
150
Viktar Donich6ea8c8a2017-12-12 14:41:47 -0800151 Plugin.prototype.screenUrl = function(opt_screenName) {
152 const origin = this._url.origin;
153 const base = Gerrit.BaseUrlBehavior.getBaseUrl();
154 const tokenPart = opt_screenName ? '/' + opt_screenName : '';
155 return `${origin}${base}/x/${this.getPluginName()}${tokenPart}`;
156 };
157
Viktar Donichd6be68a2017-06-28 15:07:51 -0700158 Plugin.prototype._send = function(method, url, opt_callback, opt_payload) {
Viktar Doniche5a2f5c2017-10-27 16:33:33 -0700159 return send(method, this.url(url), opt_callback, opt_payload);
Viktar Donich23893272017-05-24 16:03:42 -0700160 };
161
Viktar Donichd6be68a2017-06-28 15:07:51 -0700162 Plugin.prototype.get = function(url, opt_callback) {
Viktar Doniche5a2f5c2017-10-27 16:33:33 -0700163 console.warn('.get() is deprecated! Use .restApi().get()');
Viktar Donichd6be68a2017-06-28 15:07:51 -0700164 return this._send('GET', url, opt_callback);
Viktar Doniche5a2f5c2017-10-27 16:33:33 -0700165 };
Viktar Donich23893272017-05-24 16:03:42 -0700166
Viktar Donichd6be68a2017-06-28 15:07:51 -0700167 Plugin.prototype.post = function(url, payload, opt_callback) {
Viktar Doniche5a2f5c2017-10-27 16:33:33 -0700168 console.warn('.post() is deprecated! Use .restApi().post()');
Viktar Donichd6be68a2017-06-28 15:07:51 -0700169 return this._send('POST', url, opt_callback, payload);
Viktar Doniche5a2f5c2017-10-27 16:33:33 -0700170 };
Viktar Donich23893272017-05-24 16:03:42 -0700171
Viktar Donicha8f7a282017-09-30 11:56:32 +0100172 Plugin.prototype.put = function(url, payload, opt_callback) {
Viktar Doniche5a2f5c2017-10-27 16:33:33 -0700173 console.warn('.put() is deprecated! Use .restApi().put()');
Viktar Donicha8f7a282017-09-30 11:56:32 +0100174 return this._send('PUT', url, opt_callback, payload);
Viktar Doniche5a2f5c2017-10-27 16:33:33 -0700175 };
Viktar Donicha8f7a282017-09-30 11:56:32 +0100176
Viktar Doniche528a002017-09-05 11:18:08 -0700177 Plugin.prototype.delete = function(url, opt_callback) {
Viktar Doniche5a2f5c2017-10-27 16:33:33 -0700178 return Gerrit.delete(this.url(url), opt_callback);
179 };
Viktar Doniche528a002017-09-05 11:18:08 -0700180
Ravi Mistryaf1e0f82017-10-10 09:47:26 -0400181 Plugin.prototype.annotationApi = function() {
182 return new GrAnnotationActionsInterface(this);
183 };
184
Andrew Bonventrea6eb9432016-06-28 16:37:41 -0400185 Plugin.prototype.changeActions = function() {
Viktar Donich172acf72017-10-23 16:17:23 -0700186 return new GrChangeActionsInterface(this,
Thomas Draebing6ff72df2019-12-27 10:58:03 +0100187 Plugin._sharedAPIElement.getElement(
188 Plugin._sharedAPIElement.Element.CHANGE_ACTIONS));
Andrew Bonventre8351fbb2016-06-15 14:33:20 -0700189 };
190
Andrew Bonventrece9bbbb2016-06-29 13:58:14 -0400191 Plugin.prototype.changeReply = function() {
Viktar Donich7d23f222017-05-17 09:25:06 -0700192 return new GrChangeReplyInterface(this,
Thomas Draebing6ff72df2019-12-27 10:58:03 +0100193 Plugin._sharedAPIElement.getElement(
194 Plugin._sharedAPIElement.Element.REPLY_DIALOG));
Viktar Donich7d23f222017-05-17 09:25:06 -0700195 };
196
Viktar Donichd559f192017-07-24 13:23:18 -0700197 Plugin.prototype.changeView = function() {
198 return new GrChangeViewApi(this);
199 };
200
Viktar Donich8d369702017-07-19 11:21:13 -0700201 Plugin.prototype.theme = function() {
202 return new GrThemeApi(this);
203 };
204
Viktar Donichaacce172017-09-30 15:11:05 +0100205 Plugin.prototype.project = function() {
Paladox none2bd5c212017-11-16 18:54:02 +0000206 return new GrRepoApi(this);
Viktar Donichaacce172017-09-30 15:11:05 +0100207 };
208
Viktar Donichb6db19e2018-04-03 15:32:54 -0700209 Plugin.prototype.changeMetadata = function() {
210 return new GrChangeMetadataApi(this);
211 };
212
Wyatt Allenc1485932018-03-30 10:53:36 -0700213 Plugin.prototype.admin = function() {
214 return new GrAdminApi(this);
215 };
216
Viktar Donich17c02262017-12-21 15:46:02 -0800217 Plugin.prototype.settings = function() {
218 return new GrSettingsApi(this);
219 };
220
Dmitrii Filippov941ee632019-08-22 15:52:37 +0000221 Plugin.prototype.styles = function() {
222 return new GrStylesApi();
223 };
224
Viktar Doniche5a2f5c2017-10-27 16:33:33 -0700225 /**
226 * To make REST requests for plugin-provided endpoints, use
Tao Zhou25673ab2019-12-17 09:59:28 +0100227 *
Viktar Doniche5a2f5c2017-10-27 16:33:33 -0700228 * @example
229 * const pluginRestApi = plugin.restApi(plugin.url());
230 *
231 * @param {string} Base url for subsequent .get(), .post() etc requests.
232 */
233 Plugin.prototype.restApi = function(opt_prefix) {
234 return new GrPluginRestApi(opt_prefix);
235 };
236
Viktar Doniche7112572017-07-31 14:14:37 -0700237 Plugin.prototype.attributeHelper = function(element) {
238 return new GrAttributeHelper(element);
239 };
240
Viktar Donichae7c9612017-09-08 14:00:15 -0700241 Plugin.prototype.eventHelper = function(element) {
242 return new GrEventHelper(element);
243 };
244
Viktar Donich2f3ee892017-08-04 09:48:19 -0700245 Plugin.prototype.popup = function(moduleName) {
246 if (typeof moduleName !== 'string') {
Viktar Donich250bf092017-12-22 10:23:50 -0800247 console.error('.popup(element) deprecated, use .popup(moduleName)!');
248 return;
Viktar Donich2f3ee892017-08-04 09:48:19 -0700249 }
250 const api = new GrPopupInterface(this, moduleName);
251 return api.open();
252 };
253
Viktar Donich1d184ae2017-12-18 16:15:30 -0800254 Plugin.prototype.panel = function() {
255 console.error('.panel() is deprecated! ' +
256 'Use registerCustomComponent() instead.');
257 };
258
Viktar Donich17c02262017-12-21 15:46:02 -0800259 Plugin.prototype.settingsScreen = function() {
260 console.error('.settingsScreen() is deprecated! ' +
261 'Use .settings() instead.');
262 };
263
Viktar Donich6ea8c8a2017-12-12 14:41:47 -0800264 Plugin.prototype.screen = function(screenName, opt_moduleName) {
265 if (opt_moduleName && typeof opt_moduleName !== 'string') {
Viktar Donich250bf092017-12-22 10:23:50 -0800266 console.error('.screen(pattern, callback) deprecated, use ' +
267 '.screen(screenName, opt_moduleName)!');
268 return;
Viktar Donich6ea8c8a2017-12-12 14:41:47 -0800269 }
270 return this.registerCustomComponent(
Tao Zhou3a321322019-10-23 09:45:00 +0200271 this._getScreenName(screenName),
Viktar Donich6ea8c8a2017-12-12 14:41:47 -0800272 opt_moduleName);
273 };
274
Tao Zhou3a321322019-10-23 09:45:00 +0200275 Plugin.prototype._getScreenName = function(screenName) {
276 return `${this.getPluginName()}-screen-${screenName}`;
277 };
278
Viktar Donich172acf72017-10-23 16:17:23 -0700279 const deprecatedAPI = {
Viktar Donich250bf092017-12-22 10:23:50 -0800280 _loadedGwt: ()=> {},
281
Viktar Donich172acf72017-10-23 16:17:23 -0700282 install() {
283 console.log('Installing deprecated APIs is deprecated!');
284 for (const method in this.deprecated) {
285 if (method === 'install') continue;
286 this[method] = this.deprecated[method];
287 }
288 },
289
290 popup(el) {
291 console.warn('plugin.deprecated.popup() is deprecated, ' +
292 'use plugin.popup() insted!');
293 if (!el) {
294 throw new Error('Popup contents not found');
295 }
296 const api = new GrPopupInterface(this);
297 api.open().then(api => api._getElement().appendChild(el));
298 return api;
299 },
300
301 onAction(type, action, callback) {
302 console.warn('plugin.deprecated.onAction() is deprecated,' +
303 ' use plugin.changeActions() instead!');
304 if (type !== 'change' && type !== 'revision') {
305 console.warn(`${type} actions are not supported.`);
306 return;
307 }
308 this.on('showchange', (change, revision) => {
309 const details = this.changeActions().getActionDetails(action);
Viktar Donich36e425b2017-10-27 11:41:13 -0700310 if (!details) {
311 console.warn(
312 `${this.getPluginName()} onAction error: ${action} not found!`);
313 return;
314 }
Viktar Donich172acf72017-10-23 16:17:23 -0700315 this.changeActions().addTapListener(details.__key, () => {
316 callback(new GrPluginActionContext(this, details, change, revision));
317 });
318 });
319 },
320
Viktar Donich6ea8c8a2017-12-12 14:41:47 -0800321 screen(pattern, callback) {
322 console.warn('plugin.deprecated.screen is deprecated,' +
323 ' use plugin.screen instead!');
324 if (pattern instanceof RegExp) {
325 console.error('deprecated.screen() does not support RegExp. ' +
326 'Please use strings for patterns.');
327 return;
328 }
Tao Zhou3a321322019-10-23 09:45:00 +0200329 this.hook(this._getScreenName(pattern))
Viktar Donich6ea8c8a2017-12-12 14:41:47 -0800330 .onAttached(el => {
331 el.style.display = 'none';
332 callback({
333 body: el,
334 token: el.token,
335 onUnload: () => {},
336 setTitle: () => {},
337 setWindowTitle: () => {},
338 show: () => {
339 el.style.display = 'initial';
340 },
341 });
342 });
343 },
Viktar Donich1d184ae2017-12-18 16:15:30 -0800344
Viktar Donich17c02262017-12-21 15:46:02 -0800345 settingsScreen(path, menu, callback) {
346 console.warn('.settingsScreen() is deprecated! Use .settings() instead.');
347 const hook = this.settings()
348 .title(menu)
349 .token(path)
350 .module('div')
351 .build();
352 hook.onAttached(el => {
353 el.style.display = 'none';
354 const body = el.querySelector('div');
355 callback({
356 body,
357 onUnload: () => {},
358 setTitle: () => {},
359 setWindowTitle: () => {},
360 show: () => {
361 el.style.display = 'initial';
362 },
363 });
364 });
365 },
366
Viktar Donich1d184ae2017-12-18 16:15:30 -0800367 panel(extensionpoint, callback) {
368 console.warn('.panel() is deprecated! ' +
369 'Use registerCustomComponent() instead.');
370 const endpoint = PANEL_ENDPOINTS_MAPPING[extensionpoint];
371 if (!endpoint) {
372 console.warn(`.panel ${extensionpoint} not supported!`);
373 return;
374 }
375 this.hook(endpoint).onAttached(el => callback({
376 body: el,
377 p: {
378 CHANGE_INFO: el.change,
379 REVISION_INFO: el.revision,
380 },
Viktar Donich250bf092017-12-22 10:23:50 -0800381 onUnload: () => {},
Viktar Donich1d184ae2017-12-18 16:15:30 -0800382 }));
383 },
Andrew Bonventrece9bbbb2016-06-29 13:58:14 -0400384 };
385
Tao Zhouc1d182d2019-10-15 10:23:22 +0200386 window.Plugin = Plugin;
Andrew Bonventred2b90432016-03-29 14:12:33 -0400387})(window);