@zikojs/notebook 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/code-editor/index.js +53 -38
- package/src/compiler/evaluate.js +12 -4
- package/src/index.d.ts +95 -3
- package/src/notebook.js +585 -194
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zikojs/notebook",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "A lightweight, extensible JavaScript notebook component with Markdown, executable cells, persistent state, and pluggable runtimes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"@codemirror/state": "^6.7.4",
|
|
45
45
|
"@codemirror/view": "^6.43.11",
|
|
46
46
|
"@zikojs/lucide": "^0.2.2",
|
|
47
|
+
"@zikojs/console": "latest",
|
|
47
48
|
"acorn": "^8.18.0",
|
|
48
49
|
"codemirror": "^6.0.2",
|
|
49
50
|
"lucide": "^1.44.0",
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
"remark-rehype": "^11.1.2",
|
|
53
54
|
"vanjs-core": "^1.6.1",
|
|
54
55
|
"zextra": "^1.15.0",
|
|
55
|
-
"ziko": "
|
|
56
|
+
"ziko": "latest"
|
|
56
57
|
},
|
|
57
58
|
"scripts": {
|
|
58
59
|
"test": "echo \"Error: no test specified\" && exit 1",
|
package/src/code-editor/index.js
CHANGED
|
@@ -166,47 +166,62 @@ export function createCodeEditor(app, cellData, actions) {
|
|
|
166
166
|
? view.state.doc.toString()
|
|
167
167
|
: cellData.code,
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
EditorView.editable.of(!cellData.readonly)
|
|
186
|
-
)
|
|
187
|
-
]
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
const currentValue = view.state.doc.toString();
|
|
191
|
-
|
|
192
|
-
if (currentValue !== cellData.code) {
|
|
193
|
-
view.dispatch({
|
|
194
|
-
changes: {
|
|
195
|
-
from: 0,
|
|
196
|
-
to: currentValue.length,
|
|
197
|
-
insert: cellData.code
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
}
|
|
169
|
+
|
|
170
|
+
sync: (isActive, isMarkdownView) => {
|
|
171
|
+
if (!isMarkdownView) {
|
|
172
|
+
if (!view) {
|
|
173
|
+
requestAnimationFrame(() => {
|
|
174
|
+
initEditor();
|
|
175
|
+
|
|
176
|
+
// The editor may only become available after initialization.
|
|
177
|
+
// Check the current active cell, not a potentially stale
|
|
178
|
+
// isActive value captured by this callback.
|
|
179
|
+
if (
|
|
180
|
+
app.store.get("activeCellId") === cellData.id &&
|
|
181
|
+
!cellData.readonly &&
|
|
182
|
+
view
|
|
183
|
+
) {
|
|
184
|
+
view.focus();
|
|
201
185
|
}
|
|
186
|
+
});
|
|
187
|
+
} else {
|
|
188
|
+
view.dispatch({
|
|
189
|
+
effects: [
|
|
190
|
+
languageCompartment.reconfigure(
|
|
191
|
+
languageFor(cellData.type)
|
|
192
|
+
),
|
|
193
|
+
readOnlyCompartment.reconfigure(
|
|
194
|
+
EditorState.readOnly.of(cellData.readonly)
|
|
195
|
+
),
|
|
196
|
+
editableCompartment.reconfigure(
|
|
197
|
+
EditorView.editable.of(!cellData.readonly)
|
|
198
|
+
)
|
|
199
|
+
]
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const currentValue = view.state.doc.toString();
|
|
203
|
+
|
|
204
|
+
if (currentValue !== cellData.code) {
|
|
205
|
+
view.dispatch({
|
|
206
|
+
changes: {
|
|
207
|
+
from: 0,
|
|
208
|
+
to: currentValue.length,
|
|
209
|
+
insert: cellData.code
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
202
213
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
view
|
|
208
|
-
view = null;
|
|
214
|
+
if (
|
|
215
|
+
isActive &&
|
|
216
|
+
!cellData.readonly
|
|
217
|
+
) {
|
|
218
|
+
requestAnimationFrame(() => view?.focus());
|
|
209
219
|
}
|
|
210
220
|
}
|
|
221
|
+
} else if (view) {
|
|
222
|
+
view.destroy();
|
|
223
|
+
view = null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
211
226
|
};
|
|
212
227
|
}
|
package/src/compiler/evaluate.js
CHANGED
|
@@ -64,11 +64,19 @@ export const evaluateCodeAsync = async (
|
|
|
64
64
|
"TARGET",
|
|
65
65
|
"__scope__",
|
|
66
66
|
`
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
// Bind the active TARGET for this cell evaluation
|
|
68
|
+
if (typeof __Ziko__ !== "undefined" && __Ziko__.__Config__) {
|
|
69
|
+
__Ziko__.__Config__.default.target = TARGET;
|
|
70
|
+
__Ziko__.__Config__.default.autoMount = true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (async () => {
|
|
74
|
+
with (__scope__) {
|
|
75
|
+
return await (async () => {
|
|
69
76
|
${compiledCode}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
77
|
+
})();
|
|
78
|
+
}
|
|
79
|
+
})();
|
|
72
80
|
`,
|
|
73
81
|
);
|
|
74
82
|
|
package/src/index.d.ts
CHANGED
|
@@ -1,5 +1,97 @@
|
|
|
1
|
-
import type { UIElement } from 'ziko/dom/UIElement'
|
|
2
|
-
export declare class UINotebook extends UIElement{
|
|
3
1
|
|
|
2
|
+
import type { UIElement } from "ziko/dom/UIElement";
|
|
3
|
+
|
|
4
|
+
export type MaybePromise<T> = T | PromiseLike<T>;
|
|
5
|
+
|
|
6
|
+
export interface NotebookCell {
|
|
7
|
+
id?: number;
|
|
8
|
+
type?: "code" | "markdown";
|
|
9
|
+
code?: MaybePromise<string>;
|
|
10
|
+
readonly?: boolean;
|
|
11
|
+
runtime?: string | null;
|
|
12
|
+
isEditingMarkdown?: boolean;
|
|
13
|
+
execCount?: number | null;
|
|
14
|
+
outputNode?: HTMLElement | null;
|
|
15
|
+
hasDomOutput?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type NotebookCells = MaybePromise<NotebookCell[]>;
|
|
19
|
+
|
|
20
|
+
export interface NotebookOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Initial cells, or a Promise resolving to the cells.
|
|
23
|
+
*/
|
|
24
|
+
cells?: NotebookCells;
|
|
25
|
+
|
|
26
|
+
importMap?: Record<string, string>;
|
|
27
|
+
runCells?: boolean;
|
|
28
|
+
minLines?: number;
|
|
29
|
+
maxCells?: number;
|
|
30
|
+
showHeader?: boolean;
|
|
31
|
+
codeMirrorConfig?: unknown[] | Record<string, unknown>;
|
|
32
|
+
codeMirrorPlugins?: unknown[];
|
|
33
|
+
runtimePlugins?: Array<{
|
|
34
|
+
name: string;
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
}>;
|
|
37
|
+
remarkPlugins?: unknown[];
|
|
38
|
+
rehypePlugins?: unknown[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export declare class UINotebook extends UIElement {
|
|
42
|
+
constructor(options?: NotebookOptions);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolves when initial cells and Promise-based cell code
|
|
46
|
+
* have finished loading.
|
|
47
|
+
*
|
|
48
|
+
* Rejects if the initial cells source fails or does not
|
|
49
|
+
* resolve to an array. Individual code-source failures are
|
|
50
|
+
* displayed in their respective cells.
|
|
51
|
+
*/
|
|
52
|
+
readonly ready: Promise<this>;
|
|
53
|
+
|
|
54
|
+
readonly id: string;
|
|
55
|
+
|
|
56
|
+
importMap: Record<string, string>;
|
|
57
|
+
runtimePlugins: NotebookOptions["runtimePlugins"];
|
|
58
|
+
codeMirrorConfig: NotebookOptions["codeMirrorConfig"];
|
|
59
|
+
codeMirrorPlugins: unknown[];
|
|
60
|
+
remarkPlugins: unknown[];
|
|
61
|
+
rehypePlugins: unknown[];
|
|
62
|
+
minLines: number;
|
|
63
|
+
maxCells: number;
|
|
64
|
+
|
|
65
|
+
runCell(id: number): Promise<void>;
|
|
66
|
+
runAllCells(): Promise<void>;
|
|
67
|
+
|
|
68
|
+
addCell(
|
|
69
|
+
type?: "code" | "markdown",
|
|
70
|
+
initialCode?: MaybePromise<string> | null,
|
|
71
|
+
afterId?: number | null,
|
|
72
|
+
readonly?: boolean
|
|
73
|
+
): number | null;
|
|
74
|
+
|
|
75
|
+
deleteCell(id: number): void;
|
|
76
|
+
setActiveCell(id: number): void;
|
|
77
|
+
focusCell(id: number): void;
|
|
78
|
+
clearOutputs(): void;
|
|
79
|
+
resetScope(): Record<string, unknown>;
|
|
80
|
+
getScope(): Record<string, unknown>;
|
|
81
|
+
|
|
82
|
+
getNotebookData(options?: {
|
|
83
|
+
inputs?: boolean;
|
|
84
|
+
outputs?: boolean;
|
|
85
|
+
}): Array<{
|
|
86
|
+
id: number;
|
|
87
|
+
type: "code" | "markdown";
|
|
88
|
+
readonly: boolean;
|
|
89
|
+
order: number | null;
|
|
90
|
+
code?: string;
|
|
91
|
+
outputHtml?: string | null;
|
|
92
|
+
}>;
|
|
4
93
|
}
|
|
5
|
-
|
|
94
|
+
|
|
95
|
+
export declare function Notebook(
|
|
96
|
+
options?: NotebookOptions
|
|
97
|
+
): UINotebook;
|
package/src/notebook.js
CHANGED
|
@@ -1,22 +1,35 @@
|
|
|
1
|
+
|
|
1
2
|
import { NotebookStore } from "./store.js";
|
|
2
|
-
import {
|
|
3
|
-
Header,
|
|
4
|
-
CellItem,
|
|
3
|
+
import {
|
|
4
|
+
Header,
|
|
5
|
+
CellItem,
|
|
5
6
|
MarkdownView,
|
|
6
|
-
ErrorOutput
|
|
7
|
+
ErrorOutput,
|
|
7
8
|
} from "./components/index.js";
|
|
8
9
|
import { evaluateCodeAsync } from "./compiler/index.js";
|
|
9
10
|
import { tags, UIElement } from "ziko/dom";
|
|
10
|
-
import { call_with_optional_props } from
|
|
11
|
-
|
|
12
|
-
const { div } = tags
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
import { call_with_optional_props } from "ziko/dom/internal-utils";
|
|
12
|
+
|
|
13
|
+
const { div } = tags;
|
|
14
|
+
|
|
15
|
+
const isPromiseLike = (value) =>
|
|
16
|
+
value != null &&
|
|
17
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
18
|
+
typeof value.then === "function";
|
|
19
|
+
|
|
20
|
+
const resolveCode = async (code) => {
|
|
21
|
+
const resolved = await code;
|
|
22
|
+
|
|
23
|
+
if (typeof resolved !== "string") {
|
|
24
|
+
throw new TypeError(
|
|
25
|
+
`Cell code must resolve to a string; received ${typeof resolved}.`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return resolved;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export class UINotebook extends UIElement {
|
|
20
33
|
constructor({
|
|
21
34
|
cells: initialCells = [],
|
|
22
35
|
importMap = {},
|
|
@@ -28,16 +41,20 @@ export class UINotebook extends UIElement{
|
|
|
28
41
|
codeMirrorPlugins = [],
|
|
29
42
|
runtimePlugins = [],
|
|
30
43
|
remarkPlugins = [],
|
|
31
|
-
rehypePlugins = []
|
|
32
|
-
|
|
44
|
+
rehypePlugins = [],
|
|
33
45
|
} = {}) {
|
|
34
|
-
super({ element
|
|
35
|
-
|
|
36
|
-
this.
|
|
37
|
-
|
|
46
|
+
super({ element: "div" });
|
|
47
|
+
|
|
48
|
+
this.setAttr({ class: "ziko-notebook" });
|
|
49
|
+
|
|
50
|
+
this.id = globalThis.crypto?.randomUUID
|
|
51
|
+
? globalThis.crypto.randomUUID()
|
|
52
|
+
: "nb_" + Math.random().toString(36).substring(2, 9);
|
|
53
|
+
|
|
38
54
|
if (!window.__notebook_scope_map) {
|
|
39
55
|
window.__notebook_scope_map = new Map();
|
|
40
56
|
}
|
|
57
|
+
|
|
41
58
|
this.resetScope();
|
|
42
59
|
|
|
43
60
|
this.importMap = importMap;
|
|
@@ -49,157 +66,371 @@ export class UINotebook extends UIElement{
|
|
|
49
66
|
this.minLines = minLines;
|
|
50
67
|
this.maxCells = maxCells;
|
|
51
68
|
|
|
52
|
-
|
|
53
|
-
let nextCellId = startingCells.length ? Math.max(...startingCells.map(c => c.id || 0)) + 1 : 1;
|
|
69
|
+
this.nextCellId = 1;
|
|
54
70
|
|
|
55
71
|
this.store = new NotebookStore({
|
|
56
|
-
cells:
|
|
57
|
-
|
|
58
|
-
type: cell.type || "code",
|
|
59
|
-
code: cell.code || "",
|
|
60
|
-
readonly: cell.readonly ?? false,
|
|
61
|
-
runtime: cell.runtime || null,
|
|
62
|
-
isEditingMarkdown: cell.readonly ? false : (cell.isEditingMarkdown ?? true),
|
|
63
|
-
execCount: cell.execCount ?? null,
|
|
64
|
-
outputNode: cell.outputNode || null,
|
|
65
|
-
hasDomOutput: cell.hasDomOutput ?? false,
|
|
66
|
-
_copiedFlag: false,
|
|
67
|
-
_dom: null, _editor: null, _actions: null,
|
|
68
|
-
_inPrompt: null, _outPrompt: null, _bodySlot: null, _outputGrid: null, _outputRef: null
|
|
69
|
-
})),
|
|
70
|
-
activeCellId: startingCells.length ? startingCells[0].id : null,
|
|
72
|
+
cells: [],
|
|
73
|
+
activeCellId: null,
|
|
71
74
|
executionCounter: 1,
|
|
72
|
-
executionQueueState: {
|
|
73
|
-
|
|
75
|
+
executionQueueState: {
|
|
76
|
+
isRunning: false,
|
|
77
|
+
currentIndex: 0,
|
|
78
|
+
total: 0,
|
|
79
|
+
},
|
|
80
|
+
maxCells: this.maxCells,
|
|
74
81
|
});
|
|
75
82
|
|
|
76
|
-
this.nextCellId = nextCellId;
|
|
77
|
-
|
|
78
83
|
this.notebookGlobalShortcuts = {
|
|
79
|
-
deleteActive: () =>
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
deleteActive: () =>
|
|
85
|
+
this.deleteCell(this.store.get("activeCellId")),
|
|
86
|
+
addMdBelow: () =>
|
|
87
|
+
this.addCell("markdown", "", this.store.get("activeCellId")),
|
|
88
|
+
addCodeBelow: () =>
|
|
89
|
+
this.addCell("code", "", this.store.get("activeCellId")),
|
|
82
90
|
};
|
|
83
91
|
|
|
84
92
|
this.cellsContainer = div({ class: "cells-container" }).element;
|
|
93
|
+
|
|
85
94
|
this.header = Header({
|
|
86
95
|
store: this.store,
|
|
87
96
|
actions: {
|
|
88
97
|
runAll: () => this.runAllCells(),
|
|
89
98
|
clearOutputs: () => this.clearOutputs(),
|
|
90
|
-
addCode: () =>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
99
|
+
addCode: () =>
|
|
100
|
+
this.addCell("code", "", this.store.get("activeCellId")),
|
|
101
|
+
addMarkdown: () =>
|
|
102
|
+
this.addCell("markdown", "", this.store.get("activeCellId")),
|
|
103
|
+
deleteActive: () =>
|
|
104
|
+
this.deleteCell(this.store.get("activeCellId")),
|
|
105
|
+
exportData: () =>
|
|
106
|
+
console.log("Notebook Data Export:", this.getNotebookData()),
|
|
107
|
+
},
|
|
95
108
|
});
|
|
96
109
|
|
|
97
|
-
if(!showHeader)
|
|
110
|
+
if (!showHeader) {
|
|
111
|
+
this.header.style.display = "none";
|
|
112
|
+
}
|
|
98
113
|
|
|
99
114
|
this.append(
|
|
100
|
-
this.header,
|
|
101
|
-
div({ class: "notebook" },
|
|
102
|
-
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
this._renderAllCells();
|
|
115
|
+
this.header,
|
|
116
|
+
div({ class: "notebook" }, this.cellsContainer)
|
|
117
|
+
);
|
|
106
118
|
|
|
107
|
-
this.store.on(
|
|
119
|
+
this.store.on("change:activeCellId", (newId, oldId) => {
|
|
108
120
|
if (oldId !== null) {
|
|
109
|
-
const
|
|
110
|
-
|
|
121
|
+
const previous = this.store
|
|
122
|
+
.get("cells")
|
|
123
|
+
.find((cell) => cell.id === oldId);
|
|
124
|
+
|
|
125
|
+
if (previous?._dom) {
|
|
126
|
+
this._refreshCell(previous);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const next = this.store
|
|
131
|
+
.get("cells")
|
|
132
|
+
.find((cell) => cell.id === newId);
|
|
133
|
+
|
|
134
|
+
if (next?._dom) {
|
|
135
|
+
this._refreshCell(next);
|
|
111
136
|
}
|
|
112
|
-
const next = this.store.get('cells').find(c => c.id === newId);
|
|
113
|
-
if (next) this._refreshCell(next);
|
|
114
137
|
});
|
|
115
138
|
|
|
139
|
+
// Always expose one readiness Promise, whether the initial
|
|
140
|
+
// data is synchronous or asynchronous.
|
|
141
|
+
this.ready = this._initialize(initialCells);
|
|
142
|
+
|
|
143
|
+
// Prevent an unhandled-rejection warning when callers rely on
|
|
144
|
+
// runCells but don't explicitly await notebook.ready.
|
|
145
|
+
// Awaiting notebook.ready still rejects if the initial cells
|
|
146
|
+
// collection itself fails to load.
|
|
147
|
+
this.ready.catch(() => {});
|
|
148
|
+
|
|
116
149
|
if (runCells) {
|
|
117
|
-
|
|
150
|
+
this.ready
|
|
151
|
+
.then(() => this.runAllCells())
|
|
152
|
+
.catch((error) => {
|
|
153
|
+
console.error("Failed to initialize notebook:", error);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Resolve the initial cells array, normalize it, render it,
|
|
160
|
+
* and resolve any Promise-based cell code.
|
|
161
|
+
*/
|
|
162
|
+
async _initialize(initialCells) {
|
|
163
|
+
const resolvedCells = await initialCells;
|
|
164
|
+
|
|
165
|
+
if (!Array.isArray(resolvedCells)) {
|
|
166
|
+
throw new TypeError(
|
|
167
|
+
"Notebook cells must be an array or a Promise resolving to an array."
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
this._replaceInitialCells(resolvedCells);
|
|
172
|
+
await this._resolvePendingCellCode();
|
|
173
|
+
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Normalize and replace initial cells.
|
|
179
|
+
* Used for both synchronous and asynchronous initial data.
|
|
180
|
+
*/
|
|
181
|
+
_replaceInitialCells(initialCells) {
|
|
182
|
+
const startingCells =
|
|
183
|
+
this.maxCells !== Infinity
|
|
184
|
+
? initialCells.slice(0, this.maxCells)
|
|
185
|
+
: initialCells;
|
|
186
|
+
|
|
187
|
+
const ids = startingCells
|
|
188
|
+
.map((cell) => cell.id)
|
|
189
|
+
.filter((id) => Number.isFinite(id));
|
|
190
|
+
|
|
191
|
+
this.nextCellId = ids.length ? Math.max(...ids) + 1 : 1;
|
|
192
|
+
|
|
193
|
+
const normalizedCells = startingCells.map((cell) => {
|
|
194
|
+
const id = cell.id ?? this.nextCellId++;
|
|
195
|
+
const source = cell.code ?? "";
|
|
196
|
+
const pendingCode = isPromiseLike(source)
|
|
197
|
+
? Promise.resolve(source)
|
|
198
|
+
: null;
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
id,
|
|
202
|
+
type: cell.type || "code",
|
|
203
|
+
code: pendingCode
|
|
204
|
+
? ""
|
|
205
|
+
: typeof source === "string"
|
|
206
|
+
? source
|
|
207
|
+
: "",
|
|
208
|
+
_codePromise: pendingCode,
|
|
209
|
+
_codeLoadError: null,
|
|
210
|
+
|
|
211
|
+
readonly: cell.readonly ?? false,
|
|
212
|
+
runtime: cell.runtime || null,
|
|
213
|
+
isEditingMarkdown: cell.readonly
|
|
214
|
+
? false
|
|
215
|
+
: (cell.isEditingMarkdown ?? true),
|
|
216
|
+
execCount: cell.execCount ?? null,
|
|
217
|
+
outputNode: cell.outputNode || null,
|
|
218
|
+
hasDomOutput: cell.hasDomOutput ?? false,
|
|
219
|
+
|
|
220
|
+
_copiedFlag: false,
|
|
221
|
+
_dom: null,
|
|
222
|
+
_editor: null,
|
|
223
|
+
_actions: null,
|
|
224
|
+
_inPrompt: null,
|
|
225
|
+
_outPrompt: null,
|
|
226
|
+
_bodySlot: null,
|
|
227
|
+
_outputGrid: null,
|
|
228
|
+
_outputRef: null,
|
|
229
|
+
};
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
this.store.set("cells", normalizedCells);
|
|
233
|
+
this.store.set(
|
|
234
|
+
"activeCellId",
|
|
235
|
+
normalizedCells.length ? normalizedCells[0].id : null
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
this._renderAllCells();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Resolve every pending code source independently.
|
|
243
|
+
* A single failed code request does not block other cells.
|
|
244
|
+
*/
|
|
245
|
+
async _resolvePendingCellCode() {
|
|
246
|
+
const cells = this.store.get("cells");
|
|
247
|
+
|
|
248
|
+
await Promise.all(
|
|
249
|
+
cells.map((cell) => this._resolveCellCode(cell))
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async _resolveCellCode(cellData) {
|
|
254
|
+
if (!cellData._codePromise) {
|
|
255
|
+
return !cellData._codeLoadError;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const sourcePromise = cellData._codePromise;
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const code = await resolveCode(sourcePromise);
|
|
262
|
+
|
|
263
|
+
// Ignore stale results if the cell's source was replaced
|
|
264
|
+
// while the previous Promise was still pending.
|
|
265
|
+
if (cellData._codePromise !== sourcePromise) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
cellData.code = code;
|
|
270
|
+
cellData._codePromise = null;
|
|
271
|
+
cellData._codeLoadError = null;
|
|
272
|
+
|
|
273
|
+
if (cellData._dom) {
|
|
274
|
+
this._refreshCell(cellData);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return true;
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (cellData._codePromise !== sourcePromise) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
cellData._codePromise = null;
|
|
284
|
+
cellData._codeLoadError = error;
|
|
285
|
+
|
|
286
|
+
if (cellData._outputRef) {
|
|
287
|
+
cellData._outputRef.replaceChildren();
|
|
288
|
+
ErrorOutput(error).mount(cellData._outputRef);
|
|
289
|
+
cellData.hasDomOutput = true;
|
|
290
|
+
|
|
291
|
+
if (cellData._dom) {
|
|
292
|
+
this._refreshCell(cellData);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
console.error(
|
|
297
|
+
`Failed to load code for notebook cell ${cellData.id}:`,
|
|
298
|
+
error
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
return false;
|
|
118
302
|
}
|
|
119
303
|
}
|
|
120
304
|
|
|
121
|
-
_isMaxCellsReached() {
|
|
305
|
+
_isMaxCellsReached() {
|
|
306
|
+
return this.store.get("cells").length >= this.maxCells;
|
|
307
|
+
}
|
|
122
308
|
|
|
123
309
|
_cellActions(cellData) {
|
|
124
310
|
const app = this;
|
|
311
|
+
|
|
125
312
|
return {
|
|
126
313
|
setActive: () => app.setActiveCell(cellData.id),
|
|
314
|
+
|
|
127
315
|
runCode: async (autoNext = true) => {
|
|
316
|
+
// If this cell's code is still loading, wait for it.
|
|
317
|
+
await app._resolveCellCode(cellData);
|
|
318
|
+
|
|
319
|
+
if (cellData._codeLoadError) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
128
323
|
const code = cellData._editor.getValue();
|
|
129
324
|
cellData.code = code;
|
|
130
325
|
|
|
131
326
|
if (cellData.type === "markdown") {
|
|
132
327
|
cellData.isEditingMarkdown = false;
|
|
133
|
-
|
|
134
|
-
|
|
328
|
+
|
|
329
|
+
if (autoNext) {
|
|
330
|
+
app.nextCell(cellData.id, "markdown");
|
|
331
|
+
} else {
|
|
332
|
+
app._refreshCell(cellData);
|
|
333
|
+
}
|
|
334
|
+
|
|
135
335
|
return;
|
|
136
336
|
}
|
|
137
337
|
|
|
138
338
|
if (!code.trim()) return;
|
|
139
339
|
|
|
140
|
-
cellData._outputRef.
|
|
340
|
+
cellData._outputRef.replaceChildren();
|
|
341
|
+
|
|
141
342
|
let hasError = false;
|
|
142
343
|
|
|
143
344
|
try {
|
|
144
345
|
const scope = app.getScope();
|
|
346
|
+
|
|
145
347
|
await evaluateCodeAsync(
|
|
146
348
|
code,
|
|
147
349
|
cellData._outputRef,
|
|
148
350
|
{
|
|
149
351
|
importMap: app.importMap,
|
|
150
352
|
plugins: app.runtimePlugins,
|
|
151
|
-
runtime: cellData.runtime
|
|
353
|
+
runtime: cellData.runtime,
|
|
152
354
|
},
|
|
153
|
-
scope
|
|
355
|
+
scope
|
|
154
356
|
);
|
|
155
|
-
} catch (
|
|
357
|
+
} catch (error) {
|
|
156
358
|
hasError = true;
|
|
157
|
-
ErrorOutput(
|
|
359
|
+
ErrorOutput(error).mount(cellData._outputRef);
|
|
158
360
|
}
|
|
159
361
|
|
|
160
|
-
const counter =
|
|
161
|
-
|
|
362
|
+
const counter = app.store.get("executionCounter");
|
|
363
|
+
|
|
364
|
+
app.store.set("executionCounter", counter + 1);
|
|
162
365
|
cellData.execCount = counter;
|
|
163
|
-
cellData.hasDomOutput =
|
|
366
|
+
cellData.hasDomOutput =
|
|
367
|
+
cellData._outputRef.childNodes.length > 0 || hasError;
|
|
164
368
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
369
|
+
// Output mounting remains handled by the existing component
|
|
370
|
+
// lifecycle / autoMount behavior.
|
|
371
|
+
if (autoNext && !hasError) {
|
|
372
|
+
app.nextCell(cellData.id, "code");
|
|
373
|
+
}
|
|
169
374
|
|
|
170
|
-
if (autoNext && !hasError) app.nextCell(cellData.id, "code");
|
|
171
375
|
app._refreshCell(cellData);
|
|
172
376
|
},
|
|
377
|
+
|
|
173
378
|
copyCode: async () => {
|
|
174
|
-
const textToCopy =
|
|
379
|
+
const textToCopy =
|
|
380
|
+
cellData._editor.getValue() || cellData.code;
|
|
381
|
+
|
|
175
382
|
try {
|
|
176
383
|
await navigator.clipboard.writeText(textToCopy);
|
|
177
|
-
} catch
|
|
178
|
-
const temporary = tags.textarea(textToCopy).mount(
|
|
179
|
-
|
|
384
|
+
} catch {
|
|
385
|
+
const temporary = tags.textarea(textToCopy).mount(
|
|
386
|
+
document.body
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
temporary.element.select();
|
|
180
390
|
document.execCommand("copy");
|
|
181
|
-
temporary.unmount()
|
|
391
|
+
temporary.unmount();
|
|
182
392
|
}
|
|
183
393
|
},
|
|
394
|
+
|
|
184
395
|
toggleReadonly: () => {
|
|
185
396
|
cellData.readonly = !cellData.readonly;
|
|
186
|
-
|
|
397
|
+
|
|
398
|
+
if (
|
|
399
|
+
cellData.readonly &&
|
|
400
|
+
cellData.type === "markdown"
|
|
401
|
+
) {
|
|
402
|
+
cellData.isEditingMarkdown = false;
|
|
403
|
+
}
|
|
404
|
+
|
|
187
405
|
app._refreshCell(cellData);
|
|
188
406
|
},
|
|
407
|
+
|
|
189
408
|
toggleType: () => {
|
|
190
409
|
if (cellData.readonly) return;
|
|
191
|
-
|
|
410
|
+
|
|
411
|
+
cellData.type =
|
|
412
|
+
cellData.type === "code" ? "markdown" : "code";
|
|
413
|
+
|
|
192
414
|
cellData.isEditingMarkdown = true;
|
|
193
415
|
cellData.hasDomOutput = false;
|
|
416
|
+
|
|
194
417
|
app._refreshCell(cellData);
|
|
195
418
|
},
|
|
419
|
+
|
|
196
420
|
editMarkdown: () => {
|
|
197
421
|
if (cellData.readonly) return;
|
|
422
|
+
|
|
198
423
|
cellData.isEditingMarkdown = true;
|
|
199
424
|
app._refreshCell(cellData);
|
|
200
425
|
},
|
|
201
|
-
|
|
426
|
+
|
|
427
|
+
addBelow: () => {
|
|
428
|
+
app.setActiveCell(cellData.id);
|
|
429
|
+
app.addCell(cellData.type, null, cellData.id);
|
|
430
|
+
},
|
|
431
|
+
|
|
202
432
|
deleteCell: () => app.deleteCell(cellData.id),
|
|
433
|
+
|
|
203
434
|
setRuntime: (newRuntime) => {
|
|
204
435
|
cellData.runtime = newRuntime;
|
|
205
436
|
app._refreshCell(cellData);
|
|
@@ -208,195 +439,355 @@ export class UINotebook extends UIElement{
|
|
|
208
439
|
}
|
|
209
440
|
|
|
210
441
|
_buildCellDom(cellData) {
|
|
211
|
-
return CellItem({
|
|
442
|
+
return CellItem({
|
|
443
|
+
cellData,
|
|
444
|
+
app: this,
|
|
445
|
+
store: this.store,
|
|
446
|
+
});
|
|
212
447
|
}
|
|
213
448
|
|
|
214
449
|
_refreshCell(cellData) {
|
|
215
|
-
|
|
450
|
+
if (!cellData._dom) return;
|
|
451
|
+
|
|
452
|
+
const activeId = this.store.get("activeCellId");
|
|
216
453
|
const isActive = activeId === cellData.id;
|
|
217
|
-
cellData._dom.className = `cell ${isActive ? "active" : ""} ${cellData.readonly ? "readonly" : ""}`.trim();
|
|
218
454
|
|
|
219
|
-
|
|
220
|
-
|
|
455
|
+
cellData._dom.className =
|
|
456
|
+
`cell ${isActive ? "active" : ""} ${
|
|
457
|
+
cellData.readonly ? "readonly" : ""
|
|
458
|
+
}`.trim();
|
|
459
|
+
|
|
460
|
+
const countText = cellData.execCount
|
|
461
|
+
? `[${cellData.execCount}]`
|
|
462
|
+
: "[ ]";
|
|
221
463
|
|
|
222
|
-
|
|
464
|
+
const isMarkdownView =
|
|
465
|
+
cellData.type === "markdown" &&
|
|
466
|
+
!cellData.isEditingMarkdown;
|
|
467
|
+
|
|
468
|
+
cellData._inPrompt.textContent =
|
|
469
|
+
cellData.type === "code" ? `In ${countText}:` : "";
|
|
223
470
|
|
|
224
471
|
cellData._editor.sync(isActive, isMarkdownView);
|
|
472
|
+
|
|
225
473
|
cellData._bodySlot.replaceChildren(
|
|
226
|
-
isMarkdownView
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
474
|
+
isMarkdownView
|
|
475
|
+
? MarkdownView({
|
|
476
|
+
cellData,
|
|
477
|
+
actions: cellData._actions,
|
|
478
|
+
remarkPlugins: this.remarkPlugins,
|
|
479
|
+
rehypePlugins: this.rehypePlugins,
|
|
480
|
+
})
|
|
481
|
+
: cellData._editor.dom
|
|
232
482
|
);
|
|
233
483
|
|
|
234
|
-
const showOutput =
|
|
235
|
-
|
|
484
|
+
const showOutput =
|
|
485
|
+
cellData.type === "code" && cellData.hasDomOutput;
|
|
486
|
+
|
|
487
|
+
cellData._outputGrid.style.display =
|
|
488
|
+
showOutput ? "grid" : "none";
|
|
489
|
+
|
|
236
490
|
cellData._outPrompt.textContent = `Out ${countText}:`;
|
|
237
491
|
}
|
|
238
492
|
|
|
239
493
|
_renderAllCells() {
|
|
240
|
-
const cells = this.store.get(
|
|
241
|
-
|
|
494
|
+
const cells = this.store.get("cells");
|
|
495
|
+
|
|
496
|
+
this.cellsContainer.replaceChildren(
|
|
497
|
+
...cells.map(
|
|
498
|
+
(cell) => cell._dom || this._buildCellDom(cell)
|
|
499
|
+
)
|
|
500
|
+
);
|
|
242
501
|
}
|
|
243
502
|
|
|
244
503
|
setActiveCell(id) {
|
|
245
|
-
|
|
246
|
-
|
|
504
|
+
const cells = this.store.get("cells");
|
|
505
|
+
const target = cells.find((cell) => cell.id === id);
|
|
247
506
|
|
|
248
|
-
|
|
507
|
+
if (!target) return;
|
|
249
508
|
|
|
250
|
-
|
|
509
|
+
const currentActiveId = this.store.get("activeCellId");
|
|
251
510
|
|
|
252
|
-
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
511
|
+
if (currentActiveId === id) return;
|
|
255
512
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
});
|
|
513
|
+
cells.forEach((cell) => {
|
|
514
|
+
if (cell.id !== id && cell.type === "markdown") {
|
|
515
|
+
cell.isEditingMarkdown = false;
|
|
516
|
+
}
|
|
517
|
+
});
|
|
262
518
|
|
|
263
|
-
|
|
519
|
+
this.store.set("activeCellId", id);
|
|
264
520
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
this._refreshCell(cell);
|
|
269
|
-
}
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
focusCell(targetId) {
|
|
274
|
-
// Close editing on other markdown cells before focusing
|
|
275
|
-
this.store.get('cells').forEach(c => {
|
|
276
|
-
if (c.type === "markdown" && c.isEditingMarkdown && c.id !== targetId) {
|
|
277
|
-
c.isEditingMarkdown = false;
|
|
278
|
-
this._refreshCell(c);
|
|
521
|
+
cells.forEach((cell) => {
|
|
522
|
+
if (cell._dom) {
|
|
523
|
+
this._refreshCell(cell);
|
|
279
524
|
}
|
|
280
525
|
});
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
focusCell(id) {
|
|
529
|
+
const cells = this.store.get("cells");
|
|
530
|
+
const cell = cells.find((c) => c.id === id);
|
|
281
531
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
532
|
+
if (!cell) return;
|
|
533
|
+
|
|
534
|
+
for (const c of cells) {
|
|
535
|
+
if (c.type === "markdown" && c.id !== id) {
|
|
536
|
+
c.isEditingMarkdown = false;
|
|
285
537
|
}
|
|
286
|
-
this.store.set('activeCellId', targetId);
|
|
287
538
|
}
|
|
288
539
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
540
|
+
if (cell.type === "markdown" && !cell.readonly) {
|
|
541
|
+
cell.isEditingMarkdown = true;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
this.store.set("activeCellId", id);
|
|
545
|
+
this._refreshCell(cell);
|
|
546
|
+
|
|
547
|
+
// Scroll the target cell into view when it is outside the viewport.
|
|
548
|
+
requestAnimationFrame(() => {
|
|
549
|
+
const element = cell._dom;
|
|
550
|
+
|
|
551
|
+
if (!element?.isConnected) return;
|
|
552
|
+
|
|
553
|
+
const rect = element.getBoundingClientRect();
|
|
554
|
+
const viewportHeight = window.innerHeight;
|
|
555
|
+
|
|
556
|
+
if (rect.top < 0 || rect.bottom > viewportHeight) {
|
|
557
|
+
element.scrollIntoView({
|
|
558
|
+
behavior: "smooth",
|
|
559
|
+
block: rect.top < 0 ? "start" : "nearest",
|
|
560
|
+
});
|
|
294
561
|
}
|
|
562
|
+
});
|
|
563
|
+
}
|
|
295
564
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
addCell(type = "code", initialCode = null, afterId = null, readonly = false) {
|
|
568
|
+
const cells = [...this.store.get("cells")];
|
|
569
|
+
|
|
570
|
+
if (cells.length >= this.maxCells) {
|
|
571
|
+
console.warn(
|
|
572
|
+
`Cannot create cell: Maximum cell limit (${this.maxCells}) reached.`
|
|
573
|
+
);
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
306
576
|
|
|
307
|
-
|
|
577
|
+
// Prefer the cell associated with the clicked "+" button.
|
|
578
|
+
// Fall back to the last focused cell, then the last cell.
|
|
579
|
+
const sourceCell =
|
|
580
|
+
(afterId !== null
|
|
581
|
+
? cells.find((cell) => cell.id === afterId)
|
|
582
|
+
: null) ??
|
|
583
|
+
cells.find(
|
|
584
|
+
(cell) => cell.id === this.store.get("activeCellId")
|
|
585
|
+
) ??
|
|
586
|
+
cells.at(-1);
|
|
587
|
+
|
|
588
|
+
const newId = this.nextCellId++;
|
|
589
|
+
const defaultText =
|
|
590
|
+
type === "markdown"
|
|
591
|
+
? "### New Markdown Cell\nClick to edit..."
|
|
592
|
+
: "";
|
|
593
|
+
|
|
594
|
+
const newCell = {
|
|
595
|
+
id: newId,
|
|
596
|
+
type,
|
|
597
|
+
code: initialCode !== null ? initialCode : defaultText,
|
|
598
|
+
readonly,
|
|
599
|
+
isEditingMarkdown:
|
|
600
|
+
!readonly && type === "markdown" && initialCode === null,
|
|
601
|
+
runtime: sourceCell?.runtime ?? null,
|
|
602
|
+
execCount: null,
|
|
603
|
+
outputNode: null,
|
|
604
|
+
hasDomOutput: false,
|
|
605
|
+
_copiedFlag: false,
|
|
606
|
+
_dom: null,
|
|
607
|
+
_editor: null,
|
|
608
|
+
_actions: null,
|
|
609
|
+
_inPrompt: null,
|
|
610
|
+
_outPrompt: null,
|
|
611
|
+
_bodySlot: null,
|
|
612
|
+
_outputGrid: null,
|
|
613
|
+
_outputRef: null,
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
if (afterId === null) {
|
|
617
|
+
cells.push(newCell);
|
|
618
|
+
} else {
|
|
619
|
+
const index = cells.findIndex((cell) => cell.id === afterId);
|
|
620
|
+
if (index === -1) {
|
|
308
621
|
cells.push(newCell);
|
|
309
622
|
} else {
|
|
310
|
-
const index = cells.findIndex(c => c.id === afterId);
|
|
311
623
|
cells.splice(index + 1, 0, newCell);
|
|
312
624
|
}
|
|
313
|
-
|
|
314
|
-
this.store.set('cells', cells);
|
|
315
|
-
this._renderAllCells();
|
|
316
|
-
this.focusCell(newId);
|
|
317
|
-
return newId;
|
|
318
625
|
}
|
|
319
626
|
|
|
627
|
+
this.store.set("cells", cells);
|
|
628
|
+
this._renderAllCells();
|
|
629
|
+
this.focusCell(newId);
|
|
630
|
+
|
|
631
|
+
return newId;
|
|
632
|
+
}
|
|
633
|
+
|
|
320
634
|
deleteCell(id) {
|
|
321
|
-
const cells = this.store.get(
|
|
635
|
+
const cells = this.store.get("cells");
|
|
636
|
+
|
|
322
637
|
if (cells.length <= 1) return;
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
638
|
+
|
|
639
|
+
const index = cells.findIndex((cell) => cell.id === id);
|
|
640
|
+
const updatedCells = cells.filter(
|
|
641
|
+
(cell) => cell.id !== id
|
|
642
|
+
);
|
|
643
|
+
|
|
644
|
+
this.store.set("cells", updatedCells);
|
|
327
645
|
this._renderAllCells();
|
|
328
|
-
|
|
646
|
+
|
|
647
|
+
const nextActiveIndex = Math.min(
|
|
648
|
+
Math.max(index, 0),
|
|
649
|
+
updatedCells.length - 1
|
|
650
|
+
);
|
|
651
|
+
|
|
329
652
|
this.focusCell(updatedCells[nextActiveIndex].id);
|
|
330
653
|
}
|
|
331
654
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
655
|
+
|
|
656
|
+
nextCell(currentId, defaultType) {
|
|
657
|
+
const cells = this.store.get("cells");
|
|
658
|
+
const index = cells.findIndex((cell) => cell.id === currentId);
|
|
659
|
+
|
|
660
|
+
if (index === -1) return;
|
|
661
|
+
|
|
662
|
+
if (index === cells.length - 1) {
|
|
663
|
+
const newId = this.addCell(defaultType, null, currentId);
|
|
664
|
+
|
|
665
|
+
if (newId !== null) {
|
|
666
|
+
this.focusCell(newId);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return;
|
|
337
670
|
}
|
|
338
671
|
|
|
672
|
+
this.focusCell(cells[index + 1].id);
|
|
673
|
+
}
|
|
674
|
+
|
|
339
675
|
async runCell(id) {
|
|
340
|
-
|
|
341
|
-
|
|
676
|
+
await this.ready;
|
|
677
|
+
|
|
678
|
+
const cell = this.store
|
|
679
|
+
.get("cells")
|
|
680
|
+
.find((item) => item.id === id);
|
|
681
|
+
|
|
682
|
+
if (cell?._actions) {
|
|
683
|
+
await cell._actions.runCode(false);
|
|
684
|
+
}
|
|
342
685
|
}
|
|
343
686
|
|
|
344
687
|
async runAllCells() {
|
|
345
|
-
|
|
346
|
-
|
|
688
|
+
await this.ready;
|
|
689
|
+
|
|
690
|
+
const cells = this.store.get("cells");
|
|
691
|
+
const codeCells = cells.filter(
|
|
692
|
+
(cell) => cell.type === "code"
|
|
693
|
+
);
|
|
694
|
+
|
|
347
695
|
if (codeCells.length === 0) return;
|
|
348
696
|
|
|
349
|
-
this.store.set(
|
|
697
|
+
this.store.set("executionQueueState", {
|
|
698
|
+
isRunning: true,
|
|
699
|
+
currentIndex: 0,
|
|
700
|
+
total: codeCells.length,
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
try {
|
|
704
|
+
for (const cell of cells) {
|
|
705
|
+
if (cell.type !== "code" || !cell._actions) {
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
350
708
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
currentIndex: codeCells.indexOf(cell),
|
|
356
|
-
total: codeCells.length
|
|
709
|
+
this.store.set("executionQueueState", {
|
|
710
|
+
isRunning: true,
|
|
711
|
+
currentIndex: codeCells.indexOf(cell),
|
|
712
|
+
total: codeCells.length,
|
|
357
713
|
});
|
|
714
|
+
|
|
358
715
|
await cell._actions.runCode(false);
|
|
359
716
|
}
|
|
717
|
+
} finally {
|
|
718
|
+
this.store.set("executionQueueState", {
|
|
719
|
+
isRunning: false,
|
|
720
|
+
currentIndex: 0,
|
|
721
|
+
total: 0,
|
|
722
|
+
});
|
|
360
723
|
}
|
|
361
|
-
|
|
362
|
-
this.store.set('executionQueueState', { isRunning: false, currentIndex: 0, total: 0 });
|
|
363
724
|
}
|
|
364
725
|
|
|
365
726
|
clearOutputs() {
|
|
366
|
-
this.store.get(
|
|
727
|
+
this.store.get("cells").forEach((cell) => {
|
|
367
728
|
cell.execCount = null;
|
|
368
729
|
cell.outputNode = null;
|
|
369
730
|
cell.hasDomOutput = false;
|
|
370
|
-
|
|
731
|
+
|
|
732
|
+
if (cell._outputRef) {
|
|
733
|
+
cell._outputRef.replaceChildren();
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
this.store.set("executionCounter", 1);
|
|
738
|
+
|
|
739
|
+
this.store.get("cells").forEach((cell) => {
|
|
740
|
+
this._refreshCell(cell);
|
|
371
741
|
});
|
|
372
|
-
this.store.set('executionCounter', 1);
|
|
373
|
-
this.store.get('cells').forEach(c => this._refreshCell(c));
|
|
374
742
|
}
|
|
375
743
|
|
|
376
744
|
resetScope() {
|
|
377
745
|
if (!window.__notebook_scope_map) {
|
|
378
746
|
window.__notebook_scope_map = new Map();
|
|
379
747
|
}
|
|
748
|
+
|
|
380
749
|
const newScope = Object.create(null);
|
|
750
|
+
|
|
381
751
|
window.__notebook_scope_map.set(this.id, newScope);
|
|
752
|
+
|
|
382
753
|
return newScope;
|
|
383
754
|
}
|
|
384
755
|
|
|
385
756
|
getScope() {
|
|
386
|
-
if (
|
|
757
|
+
if (
|
|
758
|
+
!window.__notebook_scope_map ||
|
|
759
|
+
!window.__notebook_scope_map.has(this.id)
|
|
760
|
+
) {
|
|
387
761
|
return this.resetScope();
|
|
388
762
|
}
|
|
763
|
+
|
|
389
764
|
return window.__notebook_scope_map.get(this.id);
|
|
390
765
|
}
|
|
391
766
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
767
|
+
|
|
768
|
+
getNotebookData({ inputs = true, outputs = true } = {}) {
|
|
769
|
+
return this.store.get("cells").map((cell) => {
|
|
770
|
+
const exportedCell = {
|
|
771
|
+
id: cell.id,
|
|
772
|
+
type: cell.type,
|
|
773
|
+
readonly: cell.readonly,
|
|
774
|
+
runtime: cell.runtime ?? null,
|
|
775
|
+
order: cell.execCount,
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
if (inputs) {
|
|
779
|
+
exportedCell.code = cell._editor?.getValue() ?? cell.code;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (outputs) {
|
|
783
|
+
exportedCell.outputHtml = cell.outputNode
|
|
784
|
+
? cell.outputNode.innerHTML
|
|
785
|
+
: cell._outputRef?.innerHTML ?? null;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
return exportedCell;
|
|
789
|
+
});
|
|
790
|
+
}
|
|
400
791
|
}
|
|
401
792
|
|
|
402
|
-
export const Notebook = call_with_optional_props(UINotebook)
|
|
793
|
+
export const Notebook = call_with_optional_props(UINotebook);
|