Skip to content

Commit dc3c26f

Browse files
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>
1 parent b8e9cae commit dc3c26f

File tree

7 files changed

+153
-1
lines changed

7 files changed

+153
-1
lines changed

packages/notebook-extension/src/index.ts

+33-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import { Poll } from '@lumino/polling';
3434

3535
import { Widget } from '@lumino/widgets';
3636

37+
import { TrustedComponent } from './trusted';
38+
3739
/**
3840
* The class for kernel status errors.
3941
*/
@@ -364,6 +366,35 @@ const notebookToolsWidget: JupyterFrontEndPlugin<void> = {
364366
}
365367
};
366368

369+
/**
370+
* A plugin that adds a Trusted indicator to the menu area
371+
*/
372+
const trusted: JupyterFrontEndPlugin<void> = {
373+
id: '@jupyter-notebook/notebook-extension:trusted',
374+
autoStart: true,
375+
requires: [INotebookShell, ITranslator],
376+
activate: (
377+
app: JupyterFrontEnd,
378+
notebookShell: INotebookShell,
379+
translator: ITranslator
380+
): void => {
381+
const onChange = async () => {
382+
const current = notebookShell.currentWidget;
383+
if (!(current instanceof NotebookPanel)) {
384+
return;
385+
}
386+
387+
const notebook = current.content;
388+
await current.context.ready;
389+
390+
const widget = TrustedComponent.create({ notebook, translator });
391+
notebookShell.add(widget, 'menu', { rank: 11_000 });
392+
};
393+
394+
notebookShell.currentChanged.connect(onChange);
395+
}
396+
};
397+
367398
/**
368399
* Export the plugins as default.
369400
*/
@@ -372,7 +403,8 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
372403
kernelLogo,
373404
kernelStatus,
374405
scrollOutput,
375-
notebookToolsWidget
406+
notebookToolsWidget,
407+
trusted
376408
];
377409

378410
export default plugins;
+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { ReactWidget } from '@jupyterlab/apputils';
2+
3+
import { Notebook, NotebookActions } from '@jupyterlab/notebook';
4+
5+
import { ITranslator } from '@jupyterlab/translation';
6+
7+
import { toArray } from '@lumino/algorithm';
8+
9+
import React, { useEffect, useState } from 'react';
10+
11+
/**
12+
* Check if a notebook is trusted
13+
* @param notebook The notebook to check
14+
* @returns true if the notebook is trusted, false otherwise
15+
*/
16+
const isTrusted = (notebook: Notebook): boolean => {
17+
const model = notebook.model;
18+
if (!model) {
19+
return false;
20+
}
21+
const cells = toArray(model.cells);
22+
23+
const trusted = cells.reduce((accum, current) => {
24+
if (current.trusted) {
25+
return accum + 1;
26+
} else {
27+
return accum;
28+
}
29+
}, 0);
30+
31+
const total = cells.length;
32+
return trusted === total;
33+
};
34+
35+
/**
36+
* A React component to display the Trusted badge in the menu bar.
37+
* @param notebook The Notebook
38+
* @param translator The Translation service
39+
*/
40+
const TrustedButton = ({
41+
notebook,
42+
translator
43+
}: {
44+
notebook: Notebook;
45+
translator: ITranslator;
46+
}): JSX.Element => {
47+
const trans = translator.load('notebook');
48+
const [trusted, setTrusted] = useState(isTrusted(notebook));
49+
50+
const checkTrust = () => {
51+
const v = isTrusted(notebook);
52+
setTrusted(v);
53+
};
54+
55+
const trust = async () => {
56+
await NotebookActions.trust(notebook, translator);
57+
checkTrust();
58+
};
59+
60+
useEffect(() => {
61+
notebook.modelContentChanged.connect(checkTrust);
62+
notebook.activeCellChanged.connect(checkTrust);
63+
checkTrust();
64+
return () => {
65+
notebook.modelContentChanged.disconnect(checkTrust);
66+
notebook.activeCellChanged.disconnect(checkTrust);
67+
};
68+
});
69+
70+
return (
71+
<button
72+
className={'jp-NotebookTrustedStatus'}
73+
style={!trusted ? { cursor: 'pointer' } : { cursor: 'help' }}
74+
onClick={() => !trusted && trust()}
75+
title={
76+
trusted
77+
? trans.__('JavaScript enabled for notebook display')
78+
: trans.__('JavaScript disabled for notebook display')
79+
}
80+
>
81+
{trusted ? trans.__('Trusted') : trans.__('Not Trusted')}
82+
</button>
83+
);
84+
};
85+
86+
/**
87+
* A namespace for TrustedComponent statics.
88+
*/
89+
export namespace TrustedComponent {
90+
/**
91+
* Create a new TrustedComponent
92+
*
93+
* @param notebook The notebook
94+
* @param translator The translator
95+
*/
96+
export const create = ({
97+
notebook,
98+
translator
99+
}: {
100+
notebook: Notebook;
101+
translator: ITranslator;
102+
}): ReactWidget => {
103+
return ReactWidget.create(
104+
<TrustedButton notebook={notebook} translator={translator} />
105+
);
106+
};
107+
}

packages/notebook-extension/style/base.css

+13
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,19 @@ body[data-notebook='notebooks']
220220
animation: 0.5s fade-out forwards;
221221
}
222222

223+
.jp-NotebookTrustedStatus {
224+
background: var(--jp-layout-color1);
225+
color: var(--jp-ui-font-color1);
226+
margin-top: 4px;
227+
margin-bottom: 4px;
228+
border: solid 1px var(--jp-border-color2);
229+
cursor: help;
230+
}
231+
232+
.jp-NotebookTrustedStatus-not-trusted {
233+
cursor: pointer;
234+
}
235+
223236
@keyframes fade-out {
224237
0% {
225238
opacity: 1;
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)