Add trusted indicator (#6736)
* Add trusted indicator * Lint * Add docstring * Fix retro names * Update Playwright Snapshots * Update Playwright Snapshots --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
@ -34,6 +34,8 @@ import { Poll } from '@lumino/polling';
|
||||
|
||||
import { Widget } from '@lumino/widgets';
|
||||
|
||||
import { TrustedComponent } from './trusted';
|
||||
|
||||
/**
|
||||
* The class for kernel status errors.
|
||||
*/
|
||||
@ -364,6 +366,35 @@ const notebookToolsWidget: JupyterFrontEndPlugin<void> = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A plugin that adds a Trusted indicator to the menu area
|
||||
*/
|
||||
const trusted: JupyterFrontEndPlugin<void> = {
|
||||
id: '@jupyter-notebook/notebook-extension:trusted',
|
||||
autoStart: true,
|
||||
requires: [INotebookShell, ITranslator],
|
||||
activate: (
|
||||
app: JupyterFrontEnd,
|
||||
notebookShell: INotebookShell,
|
||||
translator: ITranslator
|
||||
): void => {
|
||||
const onChange = async () => {
|
||||
const current = notebookShell.currentWidget;
|
||||
if (!(current instanceof NotebookPanel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const notebook = current.content;
|
||||
await current.context.ready;
|
||||
|
||||
const widget = TrustedComponent.create({ notebook, translator });
|
||||
notebookShell.add(widget, 'menu', { rank: 11_000 });
|
||||
};
|
||||
|
||||
notebookShell.currentChanged.connect(onChange);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Export the plugins as default.
|
||||
*/
|
||||
@ -372,7 +403,8 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
|
||||
kernelLogo,
|
||||
kernelStatus,
|
||||
scrollOutput,
|
||||
notebookToolsWidget
|
||||
notebookToolsWidget,
|
||||
trusted
|
||||
];
|
||||
|
||||
export default plugins;
|
||||
|
107
packages/notebook-extension/src/trusted.tsx
Normal file
@ -0,0 +1,107 @@
|
||||
import { ReactWidget } from '@jupyterlab/apputils';
|
||||
|
||||
import { Notebook, NotebookActions } from '@jupyterlab/notebook';
|
||||
|
||||
import { ITranslator } from '@jupyterlab/translation';
|
||||
|
||||
import { toArray } from '@lumino/algorithm';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
/**
|
||||
* Check if a notebook is trusted
|
||||
* @param notebook The notebook to check
|
||||
* @returns true if the notebook is trusted, false otherwise
|
||||
*/
|
||||
const isTrusted = (notebook: Notebook): boolean => {
|
||||
const model = notebook.model;
|
||||
if (!model) {
|
||||
return false;
|
||||
}
|
||||
const cells = toArray(model.cells);
|
||||
|
||||
const trusted = cells.reduce((accum, current) => {
|
||||
if (current.trusted) {
|
||||
return accum + 1;
|
||||
} else {
|
||||
return accum;
|
||||
}
|
||||
}, 0);
|
||||
|
||||
const total = cells.length;
|
||||
return trusted === total;
|
||||
};
|
||||
|
||||
/**
|
||||
* A React component to display the Trusted badge in the menu bar.
|
||||
* @param notebook The Notebook
|
||||
* @param translator The Translation service
|
||||
*/
|
||||
const TrustedButton = ({
|
||||
notebook,
|
||||
translator
|
||||
}: {
|
||||
notebook: Notebook;
|
||||
translator: ITranslator;
|
||||
}): JSX.Element => {
|
||||
const trans = translator.load('notebook');
|
||||
const [trusted, setTrusted] = useState(isTrusted(notebook));
|
||||
|
||||
const checkTrust = () => {
|
||||
const v = isTrusted(notebook);
|
||||
setTrusted(v);
|
||||
};
|
||||
|
||||
const trust = async () => {
|
||||
await NotebookActions.trust(notebook, translator);
|
||||
checkTrust();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
notebook.modelContentChanged.connect(checkTrust);
|
||||
notebook.activeCellChanged.connect(checkTrust);
|
||||
checkTrust();
|
||||
return () => {
|
||||
notebook.modelContentChanged.disconnect(checkTrust);
|
||||
notebook.activeCellChanged.disconnect(checkTrust);
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<button
|
||||
className={'jp-NotebookTrustedStatus'}
|
||||
style={!trusted ? { cursor: 'pointer' } : { cursor: 'help' }}
|
||||
onClick={() => !trusted && trust()}
|
||||
title={
|
||||
trusted
|
||||
? trans.__('JavaScript enabled for notebook display')
|
||||
: trans.__('JavaScript disabled for notebook display')
|
||||
}
|
||||
>
|
||||
{trusted ? trans.__('Trusted') : trans.__('Not Trusted')}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* A namespace for TrustedComponent statics.
|
||||
*/
|
||||
export namespace TrustedComponent {
|
||||
/**
|
||||
* Create a new TrustedComponent
|
||||
*
|
||||
* @param notebook The notebook
|
||||
* @param translator The translator
|
||||
*/
|
||||
export const create = ({
|
||||
notebook,
|
||||
translator
|
||||
}: {
|
||||
notebook: Notebook;
|
||||
translator: ITranslator;
|
||||
}): ReactWidget => {
|
||||
return ReactWidget.create(
|
||||
<TrustedButton notebook={notebook} translator={translator} />
|
||||
);
|
||||
};
|
||||
}
|
@ -220,6 +220,19 @@ body[data-notebook='notebooks']
|
||||
animation: 0.5s fade-out forwards;
|
||||
}
|
||||
|
||||
.jp-NotebookTrustedStatus {
|
||||
background: var(--jp-layout-color1);
|
||||
color: var(--jp-ui-font-color1);
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
border: solid 1px var(--jp-border-color2);
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.jp-NotebookTrustedStatus-not-trusted {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@keyframes fade-out {
|
||||
0% {
|
||||
opacity: 1;
|
||||
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 8.6 KiB |