@zikojs/notebook 0.8.0 → 0.10.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 +4 -3
- package/src/compiler/evaluate.js +49 -11
- package/src/components/CellItem/index.js +53 -9
- package/src/components/Header/index.js +13 -13
- package/src/components/MarkdownView/index.js +116 -11
- package/src/components/index.css +18 -1
- package/src/index.css +1 -1
- package/src/index.d.ts +95 -3
- package/src/notebook.js +577 -157
package/src/notebook.js
CHANGED
|
@@ -1,371 +1,791 @@
|
|
|
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 = {},
|
|
23
36
|
runCells = true,
|
|
24
|
-
codeMirrorConfig = {},
|
|
25
|
-
codeMirrorPlugins = [],
|
|
26
37
|
minLines = 3,
|
|
27
38
|
maxCells = Infinity,
|
|
28
39
|
showHeader = true,
|
|
29
|
-
|
|
40
|
+
codeMirrorConfig = {},
|
|
41
|
+
codeMirrorPlugins = [],
|
|
42
|
+
runtimePlugins = [],
|
|
43
|
+
remarkPlugins = [],
|
|
44
|
+
rehypePlugins = [],
|
|
30
45
|
} = {}) {
|
|
31
|
-
super({ element
|
|
32
|
-
|
|
33
|
-
this.
|
|
34
|
-
|
|
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
|
+
|
|
35
54
|
if (!window.__notebook_scope_map) {
|
|
36
55
|
window.__notebook_scope_map = new Map();
|
|
37
56
|
}
|
|
57
|
+
|
|
38
58
|
this.resetScope();
|
|
39
59
|
|
|
40
60
|
this.importMap = importMap;
|
|
41
61
|
this.runtimePlugins = runtimePlugins;
|
|
42
62
|
this.codeMirrorConfig = codeMirrorConfig;
|
|
43
63
|
this.codeMirrorPlugins = codeMirrorPlugins;
|
|
64
|
+
this.remarkPlugins = remarkPlugins;
|
|
65
|
+
this.rehypePlugins = rehypePlugins;
|
|
44
66
|
this.minLines = minLines;
|
|
45
67
|
this.maxCells = maxCells;
|
|
46
68
|
|
|
47
|
-
|
|
48
|
-
let nextCellId = startingCells.length ? Math.max(...startingCells.map(c => c.id || 0)) + 1 : 1;
|
|
69
|
+
this.nextCellId = 1;
|
|
49
70
|
|
|
50
71
|
this.store = new NotebookStore({
|
|
51
|
-
cells:
|
|
52
|
-
|
|
53
|
-
type: cell.type || "code",
|
|
54
|
-
code: cell.code || "",
|
|
55
|
-
readonly: cell.readonly ?? false,
|
|
56
|
-
isEditingMarkdown: cell.readonly ? false : (cell.isEditingMarkdown ?? true),
|
|
57
|
-
execCount: cell.execCount ?? null,
|
|
58
|
-
outputNode: cell.outputNode || null,
|
|
59
|
-
hasDomOutput: cell.hasDomOutput ?? false,
|
|
60
|
-
_copiedFlag: false,
|
|
61
|
-
_dom: null, _editor: null, _actions: null,
|
|
62
|
-
_inPrompt: null, _outPrompt: null, _bodySlot: null, _outputGrid: null, _outputRef: null
|
|
63
|
-
})),
|
|
64
|
-
activeCellId: startingCells.length ? startingCells[0].id : null,
|
|
72
|
+
cells: [],
|
|
73
|
+
activeCellId: null,
|
|
65
74
|
executionCounter: 1,
|
|
66
|
-
executionQueueState: {
|
|
67
|
-
|
|
75
|
+
executionQueueState: {
|
|
76
|
+
isRunning: false,
|
|
77
|
+
currentIndex: 0,
|
|
78
|
+
total: 0,
|
|
79
|
+
},
|
|
80
|
+
maxCells: this.maxCells,
|
|
68
81
|
});
|
|
69
82
|
|
|
70
|
-
this.nextCellId = nextCellId;
|
|
71
|
-
|
|
72
83
|
this.notebookGlobalShortcuts = {
|
|
73
|
-
deleteActive: () =>
|
|
74
|
-
|
|
75
|
-
|
|
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")),
|
|
76
90
|
};
|
|
77
91
|
|
|
78
92
|
this.cellsContainer = div({ class: "cells-container" }).element;
|
|
93
|
+
|
|
79
94
|
this.header = Header({
|
|
80
95
|
store: this.store,
|
|
81
96
|
actions: {
|
|
82
97
|
runAll: () => this.runAllCells(),
|
|
83
98
|
clearOutputs: () => this.clearOutputs(),
|
|
84
|
-
addCode: () =>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
},
|
|
89
108
|
});
|
|
90
109
|
|
|
91
|
-
if(!showHeader)
|
|
110
|
+
if (!showHeader) {
|
|
111
|
+
this.header.style.display = "none";
|
|
112
|
+
}
|
|
92
113
|
|
|
93
114
|
this.append(
|
|
94
|
-
this.header,
|
|
95
|
-
div({ class: "notebook" },
|
|
96
|
-
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
this._renderAllCells();
|
|
115
|
+
this.header,
|
|
116
|
+
div({ class: "notebook" }, this.cellsContainer)
|
|
117
|
+
);
|
|
100
118
|
|
|
101
|
-
this.store.on(
|
|
119
|
+
this.store.on("change:activeCellId", (newId, oldId) => {
|
|
102
120
|
if (oldId !== null) {
|
|
103
|
-
const
|
|
104
|
-
|
|
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);
|
|
105
136
|
}
|
|
106
|
-
const next = this.store.get('cells').find(c => c.id === newId);
|
|
107
|
-
if (next) this._refreshCell(next);
|
|
108
137
|
});
|
|
109
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
|
+
|
|
110
149
|
if (runCells) {
|
|
111
|
-
|
|
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;
|
|
112
302
|
}
|
|
113
303
|
}
|
|
114
304
|
|
|
115
|
-
_isMaxCellsReached() {
|
|
305
|
+
_isMaxCellsReached() {
|
|
306
|
+
return this.store.get("cells").length >= this.maxCells;
|
|
307
|
+
}
|
|
116
308
|
|
|
117
309
|
_cellActions(cellData) {
|
|
118
310
|
const app = this;
|
|
311
|
+
|
|
119
312
|
return {
|
|
120
313
|
setActive: () => app.setActiveCell(cellData.id),
|
|
314
|
+
|
|
121
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
|
+
|
|
122
323
|
const code = cellData._editor.getValue();
|
|
123
324
|
cellData.code = code;
|
|
124
325
|
|
|
125
326
|
if (cellData.type === "markdown") {
|
|
126
327
|
cellData.isEditingMarkdown = false;
|
|
127
|
-
|
|
128
|
-
|
|
328
|
+
|
|
329
|
+
if (autoNext) {
|
|
330
|
+
app.nextCell(cellData.id, "markdown");
|
|
331
|
+
} else {
|
|
332
|
+
app._refreshCell(cellData);
|
|
333
|
+
}
|
|
334
|
+
|
|
129
335
|
return;
|
|
130
336
|
}
|
|
131
337
|
|
|
132
338
|
if (!code.trim()) return;
|
|
133
339
|
|
|
134
|
-
cellData._outputRef.
|
|
340
|
+
cellData._outputRef.replaceChildren();
|
|
341
|
+
|
|
135
342
|
let hasError = false;
|
|
136
343
|
|
|
137
344
|
try {
|
|
138
345
|
const scope = app.getScope();
|
|
346
|
+
|
|
139
347
|
await evaluateCodeAsync(
|
|
140
348
|
code,
|
|
141
349
|
cellData._outputRef,
|
|
142
350
|
{
|
|
143
351
|
importMap: app.importMap,
|
|
144
352
|
plugins: app.runtimePlugins,
|
|
353
|
+
runtime: cellData.runtime,
|
|
145
354
|
},
|
|
146
|
-
scope
|
|
355
|
+
scope
|
|
147
356
|
);
|
|
148
|
-
} catch (
|
|
357
|
+
} catch (error) {
|
|
149
358
|
hasError = true;
|
|
150
|
-
ErrorOutput(
|
|
359
|
+
ErrorOutput(error).mount(cellData._outputRef);
|
|
151
360
|
}
|
|
152
361
|
|
|
153
|
-
const counter =
|
|
154
|
-
|
|
362
|
+
const counter = app.store.get("executionCounter");
|
|
363
|
+
|
|
364
|
+
app.store.set("executionCounter", counter + 1);
|
|
155
365
|
cellData.execCount = counter;
|
|
156
|
-
cellData.hasDomOutput =
|
|
366
|
+
cellData.hasDomOutput =
|
|
367
|
+
cellData._outputRef.childNodes.length > 0 || hasError;
|
|
157
368
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
369
|
+
// Output mounting remains handled by the existing component
|
|
370
|
+
// lifecycle / autoMount behavior.
|
|
371
|
+
if (autoNext && !hasError) {
|
|
372
|
+
app.nextCell(cellData.id, "code");
|
|
373
|
+
}
|
|
162
374
|
|
|
163
|
-
if (autoNext && !hasError) app.nextCell(cellData.id, "code");
|
|
164
375
|
app._refreshCell(cellData);
|
|
165
376
|
},
|
|
377
|
+
|
|
166
378
|
copyCode: async () => {
|
|
167
|
-
const textToCopy =
|
|
379
|
+
const textToCopy =
|
|
380
|
+
cellData._editor.getValue() || cellData.code;
|
|
381
|
+
|
|
168
382
|
try {
|
|
169
383
|
await navigator.clipboard.writeText(textToCopy);
|
|
170
|
-
} catch
|
|
171
|
-
const temporary = tags.textarea(textToCopy).mount(
|
|
172
|
-
|
|
384
|
+
} catch {
|
|
385
|
+
const temporary = tags.textarea(textToCopy).mount(
|
|
386
|
+
document.body
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
temporary.element.select();
|
|
173
390
|
document.execCommand("copy");
|
|
174
|
-
temporary.unmount()
|
|
391
|
+
temporary.unmount();
|
|
175
392
|
}
|
|
176
393
|
},
|
|
394
|
+
|
|
177
395
|
toggleReadonly: () => {
|
|
178
396
|
cellData.readonly = !cellData.readonly;
|
|
179
|
-
|
|
397
|
+
|
|
398
|
+
if (
|
|
399
|
+
cellData.readonly &&
|
|
400
|
+
cellData.type === "markdown"
|
|
401
|
+
) {
|
|
402
|
+
cellData.isEditingMarkdown = false;
|
|
403
|
+
}
|
|
404
|
+
|
|
180
405
|
app._refreshCell(cellData);
|
|
181
406
|
},
|
|
407
|
+
|
|
182
408
|
toggleType: () => {
|
|
183
409
|
if (cellData.readonly) return;
|
|
184
|
-
|
|
410
|
+
|
|
411
|
+
cellData.type =
|
|
412
|
+
cellData.type === "code" ? "markdown" : "code";
|
|
413
|
+
|
|
185
414
|
cellData.isEditingMarkdown = true;
|
|
186
415
|
cellData.hasDomOutput = false;
|
|
416
|
+
|
|
187
417
|
app._refreshCell(cellData);
|
|
188
418
|
},
|
|
419
|
+
|
|
189
420
|
editMarkdown: () => {
|
|
190
421
|
if (cellData.readonly) return;
|
|
422
|
+
|
|
191
423
|
cellData.isEditingMarkdown = true;
|
|
192
424
|
app._refreshCell(cellData);
|
|
193
425
|
},
|
|
194
|
-
|
|
195
|
-
|
|
426
|
+
|
|
427
|
+
addBelow: () =>
|
|
428
|
+
app.addCell(cellData.type, null, cellData.id),
|
|
429
|
+
|
|
430
|
+
deleteCell: () => app.deleteCell(cellData.id),
|
|
431
|
+
|
|
432
|
+
setRuntime: (newRuntime) => {
|
|
433
|
+
cellData.runtime = newRuntime;
|
|
434
|
+
app._refreshCell(cellData);
|
|
435
|
+
},
|
|
196
436
|
};
|
|
197
437
|
}
|
|
198
438
|
|
|
199
439
|
_buildCellDom(cellData) {
|
|
200
|
-
return CellItem({
|
|
440
|
+
return CellItem({
|
|
441
|
+
cellData,
|
|
442
|
+
app: this,
|
|
443
|
+
store: this.store,
|
|
444
|
+
});
|
|
201
445
|
}
|
|
202
446
|
|
|
203
447
|
_refreshCell(cellData) {
|
|
204
|
-
|
|
448
|
+
if (!cellData._dom) return;
|
|
449
|
+
|
|
450
|
+
const activeId = this.store.get("activeCellId");
|
|
205
451
|
const isActive = activeId === cellData.id;
|
|
206
|
-
cellData._dom.className = `cell ${isActive ? "active" : ""} ${cellData.readonly ? "readonly" : ""}`.trim();
|
|
207
452
|
|
|
208
|
-
|
|
209
|
-
|
|
453
|
+
cellData._dom.className =
|
|
454
|
+
`cell ${isActive ? "active" : ""} ${
|
|
455
|
+
cellData.readonly ? "readonly" : ""
|
|
456
|
+
}`.trim();
|
|
457
|
+
|
|
458
|
+
const countText = cellData.execCount
|
|
459
|
+
? `[${cellData.execCount}]`
|
|
460
|
+
: "[ ]";
|
|
461
|
+
|
|
462
|
+
const isMarkdownView =
|
|
463
|
+
cellData.type === "markdown" &&
|
|
464
|
+
!cellData.isEditingMarkdown;
|
|
210
465
|
|
|
211
|
-
cellData._inPrompt.textContent =
|
|
466
|
+
cellData._inPrompt.textContent =
|
|
467
|
+
cellData.type === "code" ? `In ${countText}:` : "";
|
|
212
468
|
|
|
213
469
|
cellData._editor.sync(isActive, isMarkdownView);
|
|
470
|
+
|
|
214
471
|
cellData._bodySlot.replaceChildren(
|
|
215
|
-
isMarkdownView
|
|
472
|
+
isMarkdownView
|
|
473
|
+
? MarkdownView({
|
|
474
|
+
cellData,
|
|
475
|
+
actions: cellData._actions,
|
|
476
|
+
remarkPlugins: this.remarkPlugins,
|
|
477
|
+
rehypePlugins: this.rehypePlugins,
|
|
478
|
+
})
|
|
479
|
+
: cellData._editor.dom
|
|
216
480
|
);
|
|
217
481
|
|
|
218
|
-
const showOutput =
|
|
219
|
-
|
|
482
|
+
const showOutput =
|
|
483
|
+
cellData.type === "code" && cellData.hasDomOutput;
|
|
484
|
+
|
|
485
|
+
cellData._outputGrid.style.display =
|
|
486
|
+
showOutput ? "grid" : "none";
|
|
487
|
+
|
|
220
488
|
cellData._outPrompt.textContent = `Out ${countText}:`;
|
|
221
489
|
}
|
|
222
490
|
|
|
223
491
|
_renderAllCells() {
|
|
224
|
-
const cells = this.store.get(
|
|
225
|
-
|
|
492
|
+
const cells = this.store.get("cells");
|
|
493
|
+
|
|
494
|
+
this.cellsContainer.replaceChildren(
|
|
495
|
+
...cells.map(
|
|
496
|
+
(cell) => cell._dom || this._buildCellDom(cell)
|
|
497
|
+
)
|
|
498
|
+
);
|
|
226
499
|
}
|
|
227
500
|
|
|
228
501
|
setActiveCell(id) {
|
|
229
|
-
const
|
|
502
|
+
const cells = this.store.get("cells");
|
|
503
|
+
const target = cells.find((cell) => cell.id === id);
|
|
504
|
+
|
|
505
|
+
if (!target) return;
|
|
506
|
+
|
|
507
|
+
const currentActiveId = this.store.get("activeCellId");
|
|
508
|
+
|
|
230
509
|
if (currentActiveId === id) return;
|
|
231
510
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
c.isEditingMarkdown = false;
|
|
236
|
-
this._refreshCell(c);
|
|
511
|
+
cells.forEach((cell) => {
|
|
512
|
+
if (cell.id !== id && cell.type === "markdown") {
|
|
513
|
+
cell.isEditingMarkdown = false;
|
|
237
514
|
}
|
|
238
515
|
});
|
|
239
516
|
|
|
240
|
-
this.store.set(
|
|
517
|
+
this.store.set("activeCellId", id);
|
|
518
|
+
|
|
519
|
+
cells.forEach((cell) => {
|
|
520
|
+
if (cell._dom) {
|
|
521
|
+
this._refreshCell(cell);
|
|
522
|
+
}
|
|
523
|
+
});
|
|
241
524
|
}
|
|
242
525
|
|
|
243
526
|
focusCell(targetId) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
527
|
+
this.store.get("cells").forEach((cell) => {
|
|
528
|
+
if (
|
|
529
|
+
cell.type === "markdown" &&
|
|
530
|
+
cell.isEditingMarkdown &&
|
|
531
|
+
cell.id !== targetId
|
|
532
|
+
) {
|
|
533
|
+
cell.isEditingMarkdown = false;
|
|
534
|
+
this._refreshCell(cell);
|
|
249
535
|
}
|
|
250
536
|
});
|
|
251
537
|
|
|
252
|
-
const target = this.store
|
|
253
|
-
|
|
538
|
+
const target = this.store
|
|
539
|
+
.get("cells")
|
|
540
|
+
.find((cell) => cell.id === targetId);
|
|
541
|
+
|
|
542
|
+
if (
|
|
543
|
+
target &&
|
|
544
|
+
target.type === "markdown" &&
|
|
545
|
+
!target.readonly
|
|
546
|
+
) {
|
|
254
547
|
target.isEditingMarkdown = true;
|
|
255
548
|
}
|
|
256
|
-
|
|
549
|
+
|
|
550
|
+
this.store.set("activeCellId", targetId);
|
|
257
551
|
}
|
|
258
552
|
|
|
259
|
-
addCell(
|
|
260
|
-
|
|
553
|
+
addCell(
|
|
554
|
+
type = "code",
|
|
555
|
+
initialCode = null,
|
|
556
|
+
afterId = null,
|
|
557
|
+
readonly = false
|
|
558
|
+
) {
|
|
559
|
+
const cells = [...this.store.get("cells")];
|
|
560
|
+
|
|
261
561
|
if (cells.length >= this.maxCells) {
|
|
262
|
-
console.warn(
|
|
562
|
+
console.warn(
|
|
563
|
+
`Cannot create cell: Maximum cell limit (${this.maxCells}) reached.`
|
|
564
|
+
);
|
|
565
|
+
|
|
263
566
|
return null;
|
|
264
567
|
}
|
|
265
568
|
|
|
266
569
|
const newId = this.nextCellId++;
|
|
267
|
-
|
|
570
|
+
|
|
571
|
+
const defaultText =
|
|
572
|
+
type === "markdown"
|
|
573
|
+
? "### New Markdown Cell\nClick to edit..."
|
|
574
|
+
: "";
|
|
575
|
+
|
|
576
|
+
const source =
|
|
577
|
+
initialCode !== null ? initialCode : defaultText;
|
|
578
|
+
|
|
579
|
+
const pendingCode = isPromiseLike(source)
|
|
580
|
+
? Promise.resolve(source)
|
|
581
|
+
: null;
|
|
582
|
+
|
|
268
583
|
const newCell = {
|
|
269
|
-
id: newId,
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
584
|
+
id: newId,
|
|
585
|
+
type,
|
|
586
|
+
code: pendingCode
|
|
587
|
+
? ""
|
|
588
|
+
: typeof source === "string"
|
|
589
|
+
? source
|
|
590
|
+
: "",
|
|
591
|
+
_codePromise: pendingCode,
|
|
592
|
+
_codeLoadError: null,
|
|
593
|
+
|
|
594
|
+
readonly,
|
|
595
|
+
isEditingMarkdown:
|
|
596
|
+
!readonly &&
|
|
597
|
+
type === "markdown" &&
|
|
598
|
+
initialCode === null,
|
|
599
|
+
runtime: null,
|
|
600
|
+
execCount: null,
|
|
601
|
+
outputNode: null,
|
|
602
|
+
hasDomOutput: false,
|
|
603
|
+
|
|
604
|
+
_copiedFlag: false,
|
|
605
|
+
_dom: null,
|
|
606
|
+
_editor: null,
|
|
607
|
+
_actions: null,
|
|
608
|
+
_inPrompt: null,
|
|
609
|
+
_outPrompt: null,
|
|
610
|
+
_bodySlot: null,
|
|
611
|
+
_outputGrid: null,
|
|
612
|
+
_outputRef: null,
|
|
274
613
|
};
|
|
275
614
|
|
|
276
615
|
if (afterId === null) {
|
|
277
616
|
cells.push(newCell);
|
|
278
617
|
} else {
|
|
279
|
-
const index = cells.findIndex(
|
|
618
|
+
const index = cells.findIndex(
|
|
619
|
+
(cell) => cell.id === afterId
|
|
620
|
+
);
|
|
621
|
+
|
|
280
622
|
cells.splice(index + 1, 0, newCell);
|
|
281
623
|
}
|
|
282
624
|
|
|
283
|
-
this.store.set(
|
|
625
|
+
this.store.set("cells", cells);
|
|
284
626
|
this._renderAllCells();
|
|
285
627
|
this.focusCell(newId);
|
|
628
|
+
|
|
629
|
+
// Resolve newly added Promise-based code without blocking
|
|
630
|
+
// the synchronous addCell() API.
|
|
631
|
+
if (pendingCode) {
|
|
632
|
+
this._resolveCellCode(newCell);
|
|
633
|
+
}
|
|
634
|
+
|
|
286
635
|
return newId;
|
|
287
636
|
}
|
|
288
637
|
|
|
289
638
|
deleteCell(id) {
|
|
290
|
-
const cells = this.store.get(
|
|
639
|
+
const cells = this.store.get("cells");
|
|
640
|
+
|
|
291
641
|
if (cells.length <= 1) return;
|
|
292
|
-
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
642
|
+
|
|
643
|
+
const index = cells.findIndex((cell) => cell.id === id);
|
|
644
|
+
const updatedCells = cells.filter(
|
|
645
|
+
(cell) => cell.id !== id
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
this.store.set("cells", updatedCells);
|
|
296
649
|
this._renderAllCells();
|
|
297
|
-
|
|
650
|
+
|
|
651
|
+
const nextActiveIndex = Math.min(
|
|
652
|
+
Math.max(index, 0),
|
|
653
|
+
updatedCells.length - 1
|
|
654
|
+
);
|
|
655
|
+
|
|
298
656
|
this.focusCell(updatedCells[nextActiveIndex].id);
|
|
299
657
|
}
|
|
300
658
|
|
|
301
659
|
nextCell(currentId, defaultType) {
|
|
302
|
-
const cells = this.store.get(
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
660
|
+
const cells = this.store.get("cells");
|
|
661
|
+
const index = cells.findIndex(
|
|
662
|
+
(cell) => cell.id === currentId
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
if (index === cells.length - 1) {
|
|
666
|
+
this.addCell(defaultType, null, currentId);
|
|
667
|
+
} else if (index >= 0) {
|
|
668
|
+
this.focusCell(cells[index + 1].id);
|
|
669
|
+
}
|
|
306
670
|
}
|
|
307
671
|
|
|
308
672
|
async runCell(id) {
|
|
309
|
-
|
|
310
|
-
|
|
673
|
+
await this.ready;
|
|
674
|
+
|
|
675
|
+
const cell = this.store
|
|
676
|
+
.get("cells")
|
|
677
|
+
.find((item) => item.id === id);
|
|
678
|
+
|
|
679
|
+
if (cell?._actions) {
|
|
680
|
+
await cell._actions.runCode(false);
|
|
681
|
+
}
|
|
311
682
|
}
|
|
312
683
|
|
|
313
684
|
async runAllCells() {
|
|
314
|
-
|
|
315
|
-
|
|
685
|
+
await this.ready;
|
|
686
|
+
|
|
687
|
+
const cells = this.store.get("cells");
|
|
688
|
+
const codeCells = cells.filter(
|
|
689
|
+
(cell) => cell.type === "code"
|
|
690
|
+
);
|
|
691
|
+
|
|
316
692
|
if (codeCells.length === 0) return;
|
|
317
693
|
|
|
318
|
-
this.store.set(
|
|
694
|
+
this.store.set("executionQueueState", {
|
|
695
|
+
isRunning: true,
|
|
696
|
+
currentIndex: 0,
|
|
697
|
+
total: codeCells.length,
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
try {
|
|
701
|
+
for (const cell of cells) {
|
|
702
|
+
if (cell.type !== "code" || !cell._actions) {
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
319
705
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
currentIndex: codeCells.indexOf(cell),
|
|
325
|
-
total: codeCells.length
|
|
706
|
+
this.store.set("executionQueueState", {
|
|
707
|
+
isRunning: true,
|
|
708
|
+
currentIndex: codeCells.indexOf(cell),
|
|
709
|
+
total: codeCells.length,
|
|
326
710
|
});
|
|
711
|
+
|
|
327
712
|
await cell._actions.runCode(false);
|
|
328
713
|
}
|
|
714
|
+
} finally {
|
|
715
|
+
this.store.set("executionQueueState", {
|
|
716
|
+
isRunning: false,
|
|
717
|
+
currentIndex: 0,
|
|
718
|
+
total: 0,
|
|
719
|
+
});
|
|
329
720
|
}
|
|
330
|
-
|
|
331
|
-
this.store.set('executionQueueState', { isRunning: false, currentIndex: 0, total: 0 });
|
|
332
721
|
}
|
|
333
722
|
|
|
334
723
|
clearOutputs() {
|
|
335
|
-
this.store.get(
|
|
724
|
+
this.store.get("cells").forEach((cell) => {
|
|
336
725
|
cell.execCount = null;
|
|
337
726
|
cell.outputNode = null;
|
|
338
727
|
cell.hasDomOutput = false;
|
|
339
|
-
|
|
728
|
+
|
|
729
|
+
if (cell._outputRef) {
|
|
730
|
+
cell._outputRef.replaceChildren();
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
this.store.set("executionCounter", 1);
|
|
735
|
+
|
|
736
|
+
this.store.get("cells").forEach((cell) => {
|
|
737
|
+
this._refreshCell(cell);
|
|
340
738
|
});
|
|
341
|
-
this.store.set('executionCounter', 1);
|
|
342
|
-
this.store.get('cells').forEach(c => this._refreshCell(c));
|
|
343
739
|
}
|
|
344
740
|
|
|
345
741
|
resetScope() {
|
|
346
742
|
if (!window.__notebook_scope_map) {
|
|
347
743
|
window.__notebook_scope_map = new Map();
|
|
348
744
|
}
|
|
745
|
+
|
|
349
746
|
const newScope = Object.create(null);
|
|
747
|
+
|
|
350
748
|
window.__notebook_scope_map.set(this.id, newScope);
|
|
749
|
+
|
|
351
750
|
return newScope;
|
|
352
751
|
}
|
|
353
752
|
|
|
354
753
|
getScope() {
|
|
355
|
-
if (
|
|
754
|
+
if (
|
|
755
|
+
!window.__notebook_scope_map ||
|
|
756
|
+
!window.__notebook_scope_map.has(this.id)
|
|
757
|
+
) {
|
|
356
758
|
return this.resetScope();
|
|
357
759
|
}
|
|
760
|
+
|
|
358
761
|
return window.__notebook_scope_map.get(this.id);
|
|
359
762
|
}
|
|
360
763
|
|
|
361
|
-
getNotebookData({
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
764
|
+
getNotebookData({
|
|
765
|
+
inputs = true,
|
|
766
|
+
outputs = true,
|
|
767
|
+
} = {}) {
|
|
768
|
+
return this.store.get("cells").map((cell) => {
|
|
769
|
+
const exportedCell = {
|
|
770
|
+
id: cell.id,
|
|
771
|
+
type: cell.type,
|
|
772
|
+
readonly: cell.readonly,
|
|
773
|
+
order: cell.execCount,
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
if (inputs) {
|
|
777
|
+
exportedCell.code = cell.code;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
if (outputs) {
|
|
781
|
+
exportedCell.outputHtml = cell.outputNode
|
|
782
|
+
? cell.outputNode.innerHTML
|
|
783
|
+
: null;
|
|
784
|
+
}
|
|
785
|
+
|
|
366
786
|
return exportedCell;
|
|
367
787
|
});
|
|
368
788
|
}
|
|
369
789
|
}
|
|
370
790
|
|
|
371
|
-
export const Notebook = call_with_optional_props(UINotebook)
|
|
791
|
+
export const Notebook = call_with_optional_props(UINotebook);
|