diff --git a/packages/htmlviewer-extension/package.json b/packages/htmlviewer-extension/package.json
index 4ab471395c96..8f6830b3d5ff 100644
--- a/packages/htmlviewer-extension/package.json
+++ b/packages/htmlviewer-extension/package.json
@@ -24,6 +24,7 @@
"style": "style/index.css",
"files": [
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
+ "schema/*.json",
"style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
"style/index.js"
],
@@ -38,6 +39,7 @@
"@jupyterlab/apputils": "^3.1.0-beta.0",
"@jupyterlab/docregistry": "^3.1.0-beta.0",
"@jupyterlab/htmlviewer": "^3.1.0-beta.0",
+ "@jupyterlab/settingregistry": "^3.1.0-beta.0",
"@jupyterlab/translation": "^3.1.0-beta.0",
"@jupyterlab/ui-components": "^3.1.0-beta.0"
},
@@ -49,7 +51,8 @@
"access": "public"
},
"jupyterlab": {
- "extension": true
+ "extension": true,
+ "schemaDir": "schema"
},
"styleModule": "style/index.js"
}
diff --git a/packages/htmlviewer-extension/schema/plugin.json b/packages/htmlviewer-extension/schema/plugin.json
new file mode 100644
index 000000000000..2a373d535b07
--- /dev/null
+++ b/packages/htmlviewer-extension/schema/plugin.json
@@ -0,0 +1,71 @@
+{
+ "title": "HTML Viewer",
+ "description": "HTML Viewer settings.",
+ "jupyter.lab.toolbars": {
+ "HTML Viewer": [
+ { "name": "refresh", "rank": 10 },
+ { "name": "trust", "rank": 20 }
+ ]
+ },
+ "jupyter.lab.transform": true,
+ "properties": {
+ "toolbar": {
+ "title": "HTML viewer toolbar items",
+ "description": "Note: To disable a toolbar item,\ncopy it to User Preferences and add the\n\"disabled\" key. The following example will disable the refresh item:\n{\n \"toolbar\": [\n {\n \"name\": \"refresh\",\n \"disabled\": true\n }\n ]\n}\n\nToolbar description:",
+ "items": {
+ "$ref": "#/definitions/toolbarItem"
+ },
+ "type": "array",
+ "default": []
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "definitions": {
+ "toolbarItem": {
+ "properties": {
+ "name": {
+ "title": "Unique name",
+ "type": "string"
+ },
+ "args": {
+ "title": "Command arguments",
+ "type": "object"
+ },
+ "command": {
+ "title": "Command id",
+ "type": "string",
+ "default": ""
+ },
+ "disabled": {
+ "title": "Whether the item is disabled or not",
+ "type": "boolean",
+ "default": false
+ },
+ "icon": {
+ "title": "Item icon id",
+ "description": "If defined, it will override the command icon",
+ "type": "string"
+ },
+ "label": {
+ "title": "Item icon label",
+ "description": "If defined, it will override the command label",
+ "type": "string"
+ },
+ "type": {
+ "title": "Item type",
+ "type": "string",
+ "enum": ["command", "spacer"]
+ },
+ "rank": {
+ "title": "Item rank",
+ "type": "number",
+ "minimum": 0
+ }
+ },
+ "required": ["name"],
+ "additionalProperties": false,
+ "type": "object"
+ }
+ }
+}
diff --git a/packages/htmlviewer-extension/src/index.tsx b/packages/htmlviewer-extension/src/index.tsx
index 25a012c53567..64401818504a 100644
--- a/packages/htmlviewer-extension/src/index.tsx
+++ b/packages/htmlviewer-extension/src/index.tsx
@@ -12,16 +12,28 @@ import {
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
-import { ICommandPalette, WidgetTracker } from '@jupyterlab/apputils';
+import {
+ createToolbarFactory,
+ ICommandPalette,
+ IToolbarWidgetRegistry,
+ WidgetTracker
+} from '@jupyterlab/apputils';
import { DocumentRegistry } from '@jupyterlab/docregistry';
import {
HTMLViewer,
HTMLViewerFactory,
- IHTMLViewerTracker
+ IHTMLViewerTracker,
+ ToolbarItems
} from '@jupyterlab/htmlviewer';
+import { ISettingRegistry } from '@jupyterlab/settingregistry';
import { ITranslator } from '@jupyterlab/translation';
import { html5Icon } from '@jupyterlab/ui-components';
+/**
+ * Factory name
+ */
+const FACTORY = 'HTML Viewer';
+
/**
* Command IDs used by the plugin.
*/
@@ -37,7 +49,12 @@ const htmlPlugin: JupyterFrontEndPlugin = {
id: '@jupyterlab/htmlviewer-extension:plugin',
provides: IHTMLViewerTracker,
requires: [ITranslator],
- optional: [ICommandPalette, ILayoutRestorer],
+ optional: [
+ ICommandPalette,
+ ILayoutRestorer,
+ ISettingRegistry,
+ IToolbarWidgetRegistry
+ ],
autoStart: true
};
@@ -48,10 +65,35 @@ function activateHTMLViewer(
app: JupyterFrontEnd,
translator: ITranslator,
palette: ICommandPalette | null,
- restorer: ILayoutRestorer | null
+ restorer: ILayoutRestorer | null,
+ settingRegistry: ISettingRegistry | null,
+ toolbarRegistry: IToolbarWidgetRegistry | null
): IHTMLViewerTracker {
- // Add an HTML file type to the docregistry.
+ let toolbarFactory:
+ | ((widget: HTMLViewer) => DocumentRegistry.IToolbarItem[])
+ | undefined;
const trans = translator.load('jupyterlab');
+
+ if (toolbarRegistry) {
+ toolbarRegistry.registerFactory(FACTORY, 'refresh', widget =>
+ ToolbarItems.createRefreshButton(widget, translator)
+ );
+ toolbarRegistry.registerFactory(FACTORY, 'trust', widget =>
+ ToolbarItems.createTrustButton(widget, translator)
+ );
+
+ if (settingRegistry) {
+ toolbarFactory = createToolbarFactory(
+ toolbarRegistry,
+ settingRegistry,
+ FACTORY,
+ htmlPlugin.id,
+ translator
+ );
+ }
+ }
+
+ // Add an HTML file type to the docregistry.
const ft: DocumentRegistry.IFileType = {
name: 'html',
contentType: 'file',
@@ -65,10 +107,12 @@ function activateHTMLViewer(
// Create a new viewer factory.
const factory = new HTMLViewerFactory({
- name: trans.__('HTML Viewer'),
+ name: FACTORY,
fileTypes: ['html'],
defaultFor: ['html'],
- readOnly: true
+ readOnly: true,
+ toolbarFactory,
+ translator
});
// Create a widget tracker for HTML documents.
@@ -108,19 +152,22 @@ function activateHTMLViewer(
// allowing script executions in its context.
app.commands.addCommand(CommandIDs.trustHTML, {
label: trans.__('Trust HTML File'),
+ caption: trans.__(`Whether the HTML file is trusted.
+ Trusting the file allows scripts to run in it,
+ which may result in security risks.
+ Only enable for files you trust.`),
isEnabled: () => !!tracker.currentWidget,
isToggled: () => {
const current = tracker.currentWidget;
if (!current) {
return false;
}
- const sandbox = current.content.sandbox;
- return sandbox.indexOf('allow-scripts') !== -1;
+ return current.trusted;
},
execute: () => {
const current = tracker.currentWidget;
if (!current) {
- return false;
+ return;
}
current.trusted = !current.trusted;
}
diff --git a/packages/htmlviewer-extension/tsconfig.json b/packages/htmlviewer-extension/tsconfig.json
index dcdf5f091711..cd580eae9f37 100644
--- a/packages/htmlviewer-extension/tsconfig.json
+++ b/packages/htmlviewer-extension/tsconfig.json
@@ -4,7 +4,9 @@
"outDir": "lib",
"rootDir": "src"
},
- "include": ["src/*"],
+ "include": [
+ "src/*"
+ ],
"references": [
{
"path": "../application"
@@ -18,6 +20,9 @@
{
"path": "../htmlviewer"
},
+ {
+ "path": "../settingregistry"
+ },
{
"path": "../translation"
},
diff --git a/packages/htmlviewer/package.json b/packages/htmlviewer/package.json
index 5f8f05a392ee..f965367867a2 100644
--- a/packages/htmlviewer/package.json
+++ b/packages/htmlviewer/package.json
@@ -40,6 +40,7 @@
"@jupyterlab/ui-components": "^3.1.0-beta.0",
"@lumino/coreutils": "^1.5.3",
"@lumino/signaling": "^1.4.3",
+ "@lumino/widgets": "^1.19.0",
"react": "^17.0.1"
},
"devDependencies": {
diff --git a/packages/htmlviewer/src/index.tsx b/packages/htmlviewer/src/index.tsx
index f1ada47d1647..fed8fca83320 100644
--- a/packages/htmlviewer/src/index.tsx
+++ b/packages/htmlviewer/src/index.tsx
@@ -26,6 +26,7 @@ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
import { refreshIcon } from '@jupyterlab/ui-components';
import { Token } from '@lumino/coreutils';
import { ISignal, Signal } from '@lumino/signaling';
+import { Widget } from '@lumino/widgets';
import * as React from 'react';
/**
@@ -74,7 +75,6 @@ export class HTMLViewer
content: new IFrame({ sandbox: ['allow-same-origin'] })
});
this.translator = options.translator || nullTranslator;
- const trans = this.translator.load('jupyterlab');
this.content.addClass(CSS_CLASS);
void this.context.ready.then(() => {
@@ -86,31 +86,6 @@ export class HTMLViewer
});
this._monitor.activityStopped.connect(this.update, this);
});
-
- // Make a refresh button for the toolbar.
- this.toolbar.addItem(
- 'refresh',
- new ToolbarButton({
- icon: refreshIcon,
- onClick: async () => {
- if (!this.context.model.dirty) {
- await this.context.revert();
- this.update();
- }
- },
- tooltip: trans.__('Rerender HTML Document')
- })
- );
- // Make a trust button for the toolbar.
- this.toolbar.addItem(
- 'trust',
- ReactWidget.create(
-
- )
- );
}
/**
@@ -234,6 +209,73 @@ export class HTMLViewerFactory extends ABCWidgetFactory {
protected createNewWidget(context: DocumentRegistry.Context): HTMLViewer {
return new HTMLViewer({ context });
}
+
+ /**
+ * Default factory for toolbar items to be added after the widget is created.
+ */
+ protected defaultToolbarFactory(
+ widget: HTMLViewer
+ ): DocumentRegistry.IToolbarItem[] {
+ return [
+ // Make a refresh button for the toolbar.
+ {
+ name: 'refresh',
+ widget: ToolbarItems.createRefreshButton(widget, this.translator)
+ },
+ // Make a trust button for the toolbar.
+ {
+ name: 'trust',
+ widget: ToolbarItems.createTrustButton(widget, this.translator)
+ }
+ ];
+ }
+}
+
+/**
+ * A namespace for toolbar items generator
+ */
+export namespace ToolbarItems {
+ /**
+ * Create the refresh button
+ *
+ * @param widget HTML viewer widget
+ * @param translator Application translator object
+ * @returns Toolbar item button
+ */
+ export function createRefreshButton(
+ widget: HTMLViewer,
+ translator?: ITranslator
+ ): Widget {
+ const trans = (translator ?? nullTranslator).load('jupyterlab');
+ return new ToolbarButton({
+ icon: refreshIcon,
+ onClick: async () => {
+ if (!widget.context.model.dirty) {
+ await widget.context.revert();
+ widget.update();
+ }
+ },
+ tooltip: trans.__('Rerender HTML Document')
+ });
+ }
+ /**
+ * Create the trust button
+ *
+ * @param document HTML viewer widget
+ * @param translator Application translator object
+ * @returns Toolbar item button
+ */
+ export function createTrustButton(
+ document: HTMLViewer,
+ translator: ITranslator
+ ): Widget {
+ return ReactWidget.create(
+
+ );
+ }
}
/**
@@ -270,9 +312,11 @@ namespace Private {
/**
* React component for a trusted button.
*
- * This wraps the ToolbarButtonComponent and watches for trust chagnes.
+ * This wraps the ToolbarButtonComponent and watches for trust changes.
*/
- export function TrustButtonComponent(props: TrustButtonComponent.IProps) {
+ export function TrustButtonComponent(
+ props: TrustButtonComponent.IProps
+ ): JSX.Element {
const translator = props.translator || nullTranslator;
const trans = translator.load('jupyterlab');
return (
@@ -280,7 +324,7 @@ namespace Private {
signal={props.htmlDocument.trustedChanged}
initialSender={props.htmlDocument}
>
- {session => (
+ {() => (
diff --git a/packages/htmlviewer/style/index.css b/packages/htmlviewer/style/index.css
index c845303e874b..98d5e9c91227 100644
--- a/packages/htmlviewer/style/index.css
+++ b/packages/htmlviewer/style/index.css
@@ -4,6 +4,7 @@
|----------------------------------------------------------------------------*/
/* This file was auto-generated by ensurePackage() in @jupyterlab/buildutils */
+@import url('~@lumino/widgets/style/index.css');
@import url('~@jupyterlab/ui-components/style/index.css');
@import url('~@jupyterlab/apputils/style/index.css');
@import url('~@jupyterlab/docregistry/style/index.css');
diff --git a/packages/htmlviewer/style/index.js b/packages/htmlviewer/style/index.js
index b3e161e5ff8c..698de7a5d912 100644
--- a/packages/htmlviewer/style/index.js
+++ b/packages/htmlviewer/style/index.js
@@ -4,6 +4,7 @@
|----------------------------------------------------------------------------*/
/* This file was auto-generated by ensurePackage() in @jupyterlab/buildutils */
+import '@lumino/widgets/style/index.js';
import '@jupyterlab/ui-components/style/index.js';
import '@jupyterlab/apputils/style/index.js';
import '@jupyterlab/docregistry/style/index.js';