Support for .screen() plugin API
Adds `.screen()` plugin API method for adding plugin-provided screens. Such screens only have common headers and footers, while rest of the page is filled with the registered web component. Any navigation to URL containing `#/x/` hash results in client-only redirect to `/x/` and showing appropriate plugin-provided screen. Adds a `plugin.screenUrl()` method for generating consistent URLs for such screens. Adds an example plugin with number of ways the provided API can be used. Adds partial support for GWT UI `.screen()` method. Notable difference - does not support RegExp for the screen matching. Change-Id: I0be3dee8eba6f8535a1fb2be05f473f3649bad8f
This commit is contained in:
@@ -161,11 +161,14 @@ Note: TODO
|
|||||||
`plugin.hook(endpointName, opt_options)`
|
`plugin.hook(endpointName, opt_options)`
|
||||||
|
|
||||||
See list of supported link:pg-plugin-endpoints.html[endpoints].
|
See list of supported link:pg-plugin-endpoints.html[endpoints].
|
||||||
|
|
||||||
Note: TODO
|
Note: TODO
|
||||||
|
|
||||||
=== registerCustomComponent
|
=== registerCustomComponent
|
||||||
`plugin.registerCustomComponent(endpointName, opt_moduleName, opt_options)`
|
`plugin.registerCustomComponent(endpointName, opt_moduleName, opt_options)`
|
||||||
|
|
||||||
|
See list of supported link:pg-plugin-endpoints.html[endpoints].
|
||||||
|
|
||||||
Note: TODO
|
Note: TODO
|
||||||
|
|
||||||
=== registerStyleModule
|
=== registerStyleModule
|
||||||
@@ -239,6 +242,28 @@ Note: TODO
|
|||||||
|
|
||||||
Note: TODO
|
Note: TODO
|
||||||
|
|
||||||
|
=== screen
|
||||||
|
`plugin.screen(screenName, opt_moduleName)`
|
||||||
|
|
||||||
|
.Params:
|
||||||
|
- `*string* screenName` URL path fragment of the screen, e.g.
|
||||||
|
`/x/pluginname/*screenname*`
|
||||||
|
- `*string* opt_moduleName` (Optional) Web component to be instantiated for this
|
||||||
|
screen.
|
||||||
|
|
||||||
|
.Returns:
|
||||||
|
- Instance of GrDomHook.
|
||||||
|
|
||||||
|
=== screenUrl
|
||||||
|
`plugin.url(opt_screenName)`
|
||||||
|
|
||||||
|
.Params:
|
||||||
|
- `*string* screenName` (optional) URL path fragment of the screen, e.g.
|
||||||
|
`/x/pluginname/*screenname*`
|
||||||
|
|
||||||
|
.Returns:
|
||||||
|
- Absolute URL for the screen, e.g. `http://localhost/base/x/pluginname/screenname`
|
||||||
|
|
||||||
=== theme
|
=== theme
|
||||||
`plugin.theme()`
|
`plugin.theme()`
|
||||||
|
|
||||||
|
@@ -72,14 +72,15 @@ limitations under the License.
|
|||||||
|
|
||||||
View: {
|
View: {
|
||||||
ADMIN: 'admin',
|
ADMIN: 'admin',
|
||||||
CHANGE: 'change',
|
|
||||||
AGREEMENTS: 'agreements',
|
AGREEMENTS: 'agreements',
|
||||||
|
CHANGE: 'change',
|
||||||
DASHBOARD: 'dashboard',
|
DASHBOARD: 'dashboard',
|
||||||
DIFF: 'diff',
|
DIFF: 'diff',
|
||||||
EDIT: 'edit',
|
EDIT: 'edit',
|
||||||
|
GROUP: 'group',
|
||||||
|
PLUGIN_SCREEN: 'plugin-screen',
|
||||||
SEARCH: 'search',
|
SEARCH: 'search',
|
||||||
SETTINGS: 'settings',
|
SETTINGS: 'settings',
|
||||||
GROUP: 'group',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
GroupDetailView: {
|
GroupDetailView: {
|
||||||
|
@@ -130,6 +130,8 @@
|
|||||||
// Matches /c/<changeNum>/ /<URL tail>
|
// Matches /c/<changeNum>/ /<URL tail>
|
||||||
// Catches improperly encoded URLs (context: Issue 7100)
|
// Catches improperly encoded URLs (context: Issue 7100)
|
||||||
IMPROPERLY_ENCODED_PLUS: /^\/c\/(.+)\/\ \/(.+)$/,
|
IMPROPERLY_ENCODED_PLUS: /^\/c\/(.+)\/\ \/(.+)$/,
|
||||||
|
|
||||||
|
PLUGIN_SCREEN: /^\/x\/([\w-]+)\/([\w-]+)\/?/,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -621,6 +623,14 @@
|
|||||||
page((ctx, next) => {
|
page((ctx, next) => {
|
||||||
document.body.scrollTop = 0;
|
document.body.scrollTop = 0;
|
||||||
|
|
||||||
|
if (ctx.hash.match(RoutePattern.PLUGIN_SCREEN)) {
|
||||||
|
// Redirect all urls using hash #/x/plugin/screen to /x/plugin/screen
|
||||||
|
// This is needed to allow plugins to add basic #/x/ screen links to
|
||||||
|
// any location.
|
||||||
|
this._redirect(ctx.hash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Fire asynchronously so that the URL is changed by the time the event
|
// Fire asynchronously so that the URL is changed by the time the event
|
||||||
// is processed.
|
// is processed.
|
||||||
this.async(() => {
|
this.async(() => {
|
||||||
@@ -748,6 +758,8 @@
|
|||||||
this._mapRoute(RoutePattern.IMPROPERLY_ENCODED_PLUS,
|
this._mapRoute(RoutePattern.IMPROPERLY_ENCODED_PLUS,
|
||||||
'_handleImproperlyEncodedPlusRoute');
|
'_handleImproperlyEncodedPlusRoute');
|
||||||
|
|
||||||
|
this._mapRoute(RoutePattern.PLUGIN_SCREEN, '_handlePluginScreen');
|
||||||
|
|
||||||
// Note: this route should appear last so it only catches URLs unmatched
|
// Note: this route should appear last so it only catches URLs unmatched
|
||||||
// by other patterns.
|
// by other patterns.
|
||||||
this._mapRoute(RoutePattern.DEFAULT, '_handleDefaultRoute');
|
this._mapRoute(RoutePattern.DEFAULT, '_handleDefaultRoute');
|
||||||
@@ -1276,6 +1288,13 @@
|
|||||||
this._redirect(`/c/${ctx.params[0]}/+/${ctx.params[1]}${hash}`);
|
this._redirect(`/c/${ctx.params[0]}/+/${ctx.params[1]}${hash}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_handlePluginScreen(ctx) {
|
||||||
|
const view = Gerrit.Nav.View.PLUGIN_SCREEN;
|
||||||
|
const plugin = ctx.params[0];
|
||||||
|
const screen = ctx.params[1];
|
||||||
|
this._setParams({view, plugin, screen});
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Catchall route for when no other route is matched.
|
* Catchall route for when no other route is matched.
|
||||||
*/
|
*/
|
||||||
|
@@ -168,6 +168,7 @@ limitations under the License.
|
|||||||
'_handleTagListFilterOffsetRoute',
|
'_handleTagListFilterOffsetRoute',
|
||||||
'_handleTagListFilterRoute',
|
'_handleTagListFilterRoute',
|
||||||
'_handleTagListOffsetRoute',
|
'_handleTagListOffsetRoute',
|
||||||
|
'_handlePluginScreen',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Handler names that check authentication themselves, and thus don't need
|
// Handler names that check authentication themselves, and thus don't need
|
||||||
@@ -1332,6 +1333,16 @@ limitations under the License.
|
|||||||
assert.deepEqual(setParamsStub.lastCall.args[0], appParams);
|
assert.deepEqual(setParamsStub.lastCall.args[0], appParams);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('_handlePluginScreen', () => {
|
||||||
|
const ctx = {params: ['foo', 'bar']};
|
||||||
|
assertDataToParams(ctx, '_handlePluginScreen', {
|
||||||
|
view: Gerrit.Nav.View.PLUGIN_SCREEN,
|
||||||
|
plugin: 'foo',
|
||||||
|
screen: 'bar',
|
||||||
|
});
|
||||||
|
assert.isFalse(redirectStub.called);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
suite('_parseQueryString', () => {
|
suite('_parseQueryString', () => {
|
||||||
|
@@ -48,6 +48,7 @@ limitations under the License.
|
|||||||
<link rel="import" href="./diff/gr-diff-view/gr-diff-view.html">
|
<link rel="import" href="./diff/gr-diff-view/gr-diff-view.html">
|
||||||
<link rel="import" href="./edit/gr-editor-view/gr-editor-view.html">
|
<link rel="import" href="./edit/gr-editor-view/gr-editor-view.html">
|
||||||
<link rel="import" href="./plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
|
<link rel="import" href="./plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
|
||||||
|
<link rel="import" href="./plugins/gr-endpoint-param/gr-endpoint-param.html">
|
||||||
<link rel="import" href="./plugins/gr-external-style/gr-external-style.html">
|
<link rel="import" href="./plugins/gr-external-style/gr-external-style.html">
|
||||||
<link rel="import" href="./plugins/gr-plugin-host/gr-plugin-host.html">
|
<link rel="import" href="./plugins/gr-plugin-host/gr-plugin-host.html">
|
||||||
<link rel="import" href="./settings/gr-cla-view/gr-cla-view.html">
|
<link rel="import" href="./settings/gr-cla-view/gr-cla-view.html">
|
||||||
@@ -172,6 +173,11 @@ limitations under the License.
|
|||||||
<gr-admin-view path="[[_path]]"
|
<gr-admin-view path="[[_path]]"
|
||||||
params=[[params]]></gr-admin-view>
|
params=[[params]]></gr-admin-view>
|
||||||
</template>
|
</template>
|
||||||
|
<template is="dom-if" if="[[_showPluginScreen]]" restamp="true">
|
||||||
|
<gr-endpoint-decorator name="[[_pluginScreenName]]">
|
||||||
|
<gr-endpoint-param name="token" value="[[params.screen]]"></gr-endpoint-param>
|
||||||
|
</gr-endpoint-decorator>
|
||||||
|
</template>
|
||||||
<template is="dom-if" if="[[_showCLAView]]" restamp="true">
|
<template is="dom-if" if="[[_showCLAView]]" restamp="true">
|
||||||
<gr-cla-view path="[[_path]]"></gr-cla-view>
|
<gr-cla-view path="[[_path]]"></gr-cla-view>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
properties: {
|
properties: {
|
||||||
/**
|
/**
|
||||||
* @type {{ query: string, view: string }}
|
* @type {{ query: string, view: string, screen: string }}
|
||||||
*/
|
*/
|
||||||
params: Object,
|
params: Object,
|
||||||
keyEventTarget: {
|
keyEventTarget: {
|
||||||
@@ -72,6 +72,7 @@
|
|||||||
_showAdminView: Boolean,
|
_showAdminView: Boolean,
|
||||||
_showCLAView: Boolean,
|
_showCLAView: Boolean,
|
||||||
_showEditorView: Boolean,
|
_showEditorView: Boolean,
|
||||||
|
_showPluginScreen: Boolean,
|
||||||
/** @type {?} */
|
/** @type {?} */
|
||||||
_viewState: Object,
|
_viewState: Object,
|
||||||
/** @type {?} */
|
/** @type {?} */
|
||||||
@@ -79,6 +80,10 @@
|
|||||||
_lastSearchPage: String,
|
_lastSearchPage: String,
|
||||||
_path: String,
|
_path: String,
|
||||||
_isShadowDom: Boolean,
|
_isShadowDom: Boolean,
|
||||||
|
_pluginScreenName: {
|
||||||
|
type: String,
|
||||||
|
computed: '_computePluginScreenName(params)',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
listeners: {
|
listeners: {
|
||||||
@@ -160,6 +165,14 @@
|
|||||||
view === Gerrit.Nav.View.GROUP);
|
view === Gerrit.Nav.View.GROUP);
|
||||||
this.set('_showCLAView', view === Gerrit.Nav.View.AGREEMENTS);
|
this.set('_showCLAView', view === Gerrit.Nav.View.AGREEMENTS);
|
||||||
this.set('_showEditorView', view === Gerrit.Nav.View.EDIT);
|
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);
|
||||||
|
}
|
||||||
if (this.params.justRegistered) {
|
if (this.params.justRegistered) {
|
||||||
this.$.registration.open();
|
this.$.registration.open();
|
||||||
}
|
}
|
||||||
@@ -282,5 +295,9 @@
|
|||||||
Gerrit.Nav.navigateToStatusSearch(status);
|
Gerrit.Nav.navigateToStatusSearch(status);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_computePluginScreenName({plugin, screen}) {
|
||||||
|
return Gerrit._getPluginScreenName(plugin, screen);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
@@ -456,5 +456,33 @@ limitations under the License.
|
|||||||
assert.isFalse(stub.called);
|
assert.isFalse(stub.called);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
suite('screen', () => {
|
||||||
|
test('screenUrl()', () => {
|
||||||
|
sandbox.stub(Gerrit.BaseUrlBehavior, 'getBaseUrl').returns('/base');
|
||||||
|
assert.equal(plugin.screenUrl(), 'http://test.com/base/x/testplugin');
|
||||||
|
assert.equal(
|
||||||
|
plugin.screenUrl('foo'), 'http://test.com/base/x/testplugin/foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deprecated works', () => {
|
||||||
|
const stub = sandbox.stub();
|
||||||
|
const hookStub = {onAttached: sandbox.stub()};
|
||||||
|
sandbox.stub(plugin, 'hook').returns(hookStub);
|
||||||
|
plugin.deprecated.screen('foo', stub);
|
||||||
|
assert.isTrue(plugin.hook.calledWith('testplugin-screen-foo'));
|
||||||
|
const fakeEl = {style: {display: ''}};
|
||||||
|
hookStub.onAttached.callArgWith(0, fakeEl);
|
||||||
|
assert.isTrue(stub.called);
|
||||||
|
assert.equal(fakeEl.style.display, 'none');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('works', () => {
|
||||||
|
sandbox.stub(plugin, 'registerCustomComponent');
|
||||||
|
plugin.screen('foo', 'some-module');
|
||||||
|
assert.isTrue(plugin.registerCustomComponent.calledWith(
|
||||||
|
'testplugin-screen-foo', 'some-module'));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
*/
|
*/
|
||||||
const plugins = {};
|
const plugins = {};
|
||||||
|
|
||||||
const stubbedMethods = ['_loadedGwt', 'screen', 'settingsScreen', 'panel'];
|
const stubbedMethods = ['_loadedGwt', 'settingsScreen', 'panel'];
|
||||||
const GWT_PLUGIN_STUB = {};
|
const GWT_PLUGIN_STUB = {};
|
||||||
for (const name of stubbedMethods) {
|
for (const name of stubbedMethods) {
|
||||||
GWT_PLUGIN_STUB[name] = warnNotSupported.bind(null, name);
|
GWT_PLUGIN_STUB[name] = warnNotSupported.bind(null, name);
|
||||||
@@ -92,7 +92,11 @@
|
|||||||
url.href, '— Unable to determine name.');
|
url.href, '— Unable to determine name.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return pathname.split('/')[2];
|
// Pathname should normally look like this:
|
||||||
|
// /plugins/PLUGINNAME/static/SCRIPTNAME.html
|
||||||
|
// Or, for app/samples:
|
||||||
|
// /plugins/PLUGINNAME.html
|
||||||
|
return pathname.split('/')[2].split('.')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
function Plugin(opt_url) {
|
function Plugin(opt_url) {
|
||||||
@@ -105,8 +109,9 @@
|
|||||||
}
|
}
|
||||||
this.deprecated = {
|
this.deprecated = {
|
||||||
install: deprecatedAPI.install.bind(this),
|
install: deprecatedAPI.install.bind(this),
|
||||||
popup: deprecatedAPI.popup.bind(this),
|
|
||||||
onAction: deprecatedAPI.onAction.bind(this),
|
onAction: deprecatedAPI.onAction.bind(this),
|
||||||
|
popup: deprecatedAPI.popup.bind(this),
|
||||||
|
screen: deprecatedAPI.screen.bind(this),
|
||||||
};
|
};
|
||||||
|
|
||||||
this._url = new URL(opt_url);
|
this._url = new URL(opt_url);
|
||||||
@@ -159,6 +164,13 @@
|
|||||||
this._name + (opt_path || '/');
|
this._name + (opt_path || '/');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Plugin.prototype.screenUrl = function(opt_screenName) {
|
||||||
|
const origin = this._url.origin;
|
||||||
|
const base = Gerrit.BaseUrlBehavior.getBaseUrl();
|
||||||
|
const tokenPart = opt_screenName ? '/' + opt_screenName : '';
|
||||||
|
return `${origin}${base}/x/${this.getPluginName()}${tokenPart}`;
|
||||||
|
};
|
||||||
|
|
||||||
Plugin.prototype._send = function(method, url, opt_callback, opt_payload) {
|
Plugin.prototype._send = function(method, url, opt_callback, opt_payload) {
|
||||||
return send(method, this.url(url), opt_callback, opt_payload);
|
return send(method, this.url(url), opt_callback, opt_payload);
|
||||||
};
|
};
|
||||||
@@ -237,6 +249,15 @@
|
|||||||
return api.open();
|
return api.open();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Plugin.prototype.screen = function(screenName, opt_moduleName) {
|
||||||
|
if (opt_moduleName && typeof opt_moduleName !== 'string') {
|
||||||
|
throw new Error('deprecated, use deprecated.screen');
|
||||||
|
}
|
||||||
|
return this.registerCustomComponent(
|
||||||
|
Gerrit._getPluginScreenName(this.getPluginName(), screenName),
|
||||||
|
opt_moduleName);
|
||||||
|
};
|
||||||
|
|
||||||
const deprecatedAPI = {
|
const deprecatedAPI = {
|
||||||
install() {
|
install() {
|
||||||
console.log('Installing deprecated APIs is deprecated!');
|
console.log('Installing deprecated APIs is deprecated!');
|
||||||
@@ -277,6 +298,29 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
screen(pattern, callback) {
|
||||||
|
console.warn('plugin.deprecated.screen is deprecated,' +
|
||||||
|
' use plugin.screen instead!');
|
||||||
|
if (pattern instanceof RegExp) {
|
||||||
|
console.error('deprecated.screen() does not support RegExp. ' +
|
||||||
|
'Please use strings for patterns.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.hook(Gerrit._getPluginScreenName(this.getPluginName(), pattern))
|
||||||
|
.onAttached(el => {
|
||||||
|
el.style.display = 'none';
|
||||||
|
callback({
|
||||||
|
body: el,
|
||||||
|
token: el.token,
|
||||||
|
onUnload: () => {},
|
||||||
|
setTitle: () => {},
|
||||||
|
setWindowTitle: () => {},
|
||||||
|
show: () => {
|
||||||
|
el.style.display = 'initial';
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const Gerrit = window.Gerrit || {};
|
const Gerrit = window.Gerrit || {};
|
||||||
@@ -420,5 +464,9 @@
|
|||||||
return Gerrit._pluginsPending === 0;
|
return Gerrit._pluginsPending === 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Gerrit._getPluginScreenName = function(pluginName, screenName) {
|
||||||
|
return `${pluginName}-screen-${screenName}`;
|
||||||
|
};
|
||||||
|
|
||||||
window.Gerrit = Gerrit;
|
window.Gerrit = Gerrit;
|
||||||
})(window);
|
})(window);
|
||||||
|
49
polygerrit-ui/app/samples/some-screen.html
Normal file
49
polygerrit-ui/app/samples/some-screen.html
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<dom-module id="some-screen">
|
||||||
|
<script>
|
||||||
|
Gerrit.install(plugin => {
|
||||||
|
// Recommended approach for screen() API.
|
||||||
|
plugin.screen('main', 'some-screen-main');
|
||||||
|
|
||||||
|
const mainUrl = plugin.screenUrl('main');
|
||||||
|
|
||||||
|
// Support for deprecated screen API.
|
||||||
|
plugin.deprecated.screen('foo', ({token, body, show}) => {
|
||||||
|
body.innerHTML = `This is a plugin screen at ${token}<br/>` +
|
||||||
|
`<a href="${mainUrl}">Go to main plugin screen</a>`;
|
||||||
|
show();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Quick and dirty way to get something on screen.
|
||||||
|
plugin.screen('bar').onAttached(el => {
|
||||||
|
el.innerHTML = `This is a plugin screen at ${el.token}<br/>` +
|
||||||
|
`<a href="${mainUrl}">Go to main plugin screen</a>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a "Plugin screen" link to the change view screen.
|
||||||
|
plugin.hook('change-metadata-item').onAttached(el => {
|
||||||
|
el.innerHTML = `<a href="${mainUrl}">Plugin screen</a>`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</dom-module>
|
||||||
|
|
||||||
|
<dom-module id="some-screen-main">
|
||||||
|
<template>
|
||||||
|
This is the <b>main</b> plugin screen at [[token]]
|
||||||
|
<ul>
|
||||||
|
<li><a href$="[[rootUrl]]/foo">via deprecated</a></li>
|
||||||
|
<li><a href$="[[rootUrl]]/bar">without component</a></li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
Polymer({
|
||||||
|
is: 'some-screen-main',
|
||||||
|
properties: {
|
||||||
|
rootUrl: String,
|
||||||
|
},
|
||||||
|
attached() {
|
||||||
|
this.rootUrl = `${this.plugin.screenUrl()}`;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</dom-module>
|
@@ -200,7 +200,7 @@ type server struct{}
|
|||||||
|
|
||||||
// Any path prefixes that should resolve to index.html.
|
// Any path prefixes that should resolve to index.html.
|
||||||
var (
|
var (
|
||||||
fePaths = []string{"/q/", "/c/", "/p/", "/dashboard/", "/admin/"}
|
fePaths = []string{"/q/", "/c/", "/p/", "/x/", "/dashboard/", "/admin/"}
|
||||||
issueNumRE = regexp.MustCompile(`^\/\d+\/?$`)
|
issueNumRE = regexp.MustCompile(`^\/\d+\/?$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user