@zikojs/notebook 0.7.3 → 0.9.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 +2 -2
- package/src/compiler/evaluate.js +97 -0
- package/src/compiler/imports.js +94 -0
- package/src/compiler/index.js +12 -0
- package/src/compiler/scope.js +95 -0
- package/src/compiler/transform.js +127 -0
- package/src/components/Button/index.css +55 -0
- package/src/components/Button/index.js +4 -0
- package/src/{components.js → components/CellItem/index.js} +84 -92
- package/src/components/ErrorOutput/index.css +10 -0
- package/src/components/ErrorOutput/index.js +2 -0
- package/src/components/Header/index.css +47 -0
- package/src/components/Header/index.js +46 -0
- package/src/components/MarkdownView/index.css +90 -0
- package/src/components/MarkdownView/index.js +133 -0
- package/src/components/index.css +21 -0
- package/src/components/index.js +4 -0
- package/src/index.css +8 -211
- package/src/notebook.js +76 -26
- package/src/transform.js +0 -116
- /package/src/{codeEditor.js → code-editor/index.js} +0 -0
- /package/src/{icons.js → components/icons/index.js} +0 -0
package/src/notebook.js
CHANGED
|
@@ -1,20 +1,35 @@
|
|
|
1
1
|
import { NotebookStore } from "./store.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
Header,
|
|
4
|
+
CellItem,
|
|
5
|
+
MarkdownView,
|
|
6
|
+
ErrorOutput
|
|
7
|
+
} from "./components/index.js";
|
|
8
|
+
import { evaluateCodeAsync } from "./compiler/index.js";
|
|
4
9
|
import { tags, UIElement } from "ziko/dom";
|
|
5
10
|
import { call_with_optional_props } from 'ziko/dom/internal-utils'
|
|
6
|
-
|
|
11
|
+
import { useState, useDerived } from "ziko/hooks";
|
|
7
12
|
const { div } = tags
|
|
13
|
+
// const [st, setSt] = useState('hh')
|
|
14
|
+
// globalThis.mm = useDerived((n)=>n, [st])
|
|
15
|
+
// globalThis.pp = tags.p({}, mm).mount(document.body)
|
|
16
|
+
// globalThis.st = st
|
|
17
|
+
// globalThis.setSt = setSt
|
|
18
|
+
|
|
8
19
|
export class UINotebook extends UIElement{
|
|
9
20
|
constructor({
|
|
10
21
|
cells: initialCells = [],
|
|
11
22
|
importMap = {},
|
|
12
23
|
runCells = true,
|
|
13
|
-
codeMirrorConfig = {},
|
|
14
|
-
codeMirrorPlugins = [],
|
|
15
24
|
minLines = 3,
|
|
16
25
|
maxCells = Infinity,
|
|
17
|
-
showHeader = true
|
|
26
|
+
showHeader = true,
|
|
27
|
+
codeMirrorConfig = {},
|
|
28
|
+
codeMirrorPlugins = [],
|
|
29
|
+
runtimePlugins = [],
|
|
30
|
+
remarkPlugins = [],
|
|
31
|
+
rehypePlugins = []
|
|
32
|
+
|
|
18
33
|
} = {}) {
|
|
19
34
|
super({ element : 'div'})
|
|
20
35
|
this.setAttr({class : 'ziko-notebook'})
|
|
@@ -26,8 +41,11 @@ export class UINotebook extends UIElement{
|
|
|
26
41
|
this.resetScope();
|
|
27
42
|
|
|
28
43
|
this.importMap = importMap;
|
|
44
|
+
this.runtimePlugins = runtimePlugins;
|
|
29
45
|
this.codeMirrorConfig = codeMirrorConfig;
|
|
30
46
|
this.codeMirrorPlugins = codeMirrorPlugins;
|
|
47
|
+
this.remarkPlugins = remarkPlugins;
|
|
48
|
+
this.rehypePlugins = rehypePlugins;
|
|
31
49
|
this.minLines = minLines;
|
|
32
50
|
this.maxCells = maxCells;
|
|
33
51
|
|
|
@@ -40,6 +58,7 @@ export class UINotebook extends UIElement{
|
|
|
40
58
|
type: cell.type || "code",
|
|
41
59
|
code: cell.code || "",
|
|
42
60
|
readonly: cell.readonly ?? false,
|
|
61
|
+
runtime: cell.runtime || null,
|
|
43
62
|
isEditingMarkdown: cell.readonly ? false : (cell.isEditingMarkdown ?? true),
|
|
44
63
|
execCount: cell.execCount ?? null,
|
|
45
64
|
outputNode: cell.outputNode || null,
|
|
@@ -123,10 +142,19 @@ export class UINotebook extends UIElement{
|
|
|
123
142
|
|
|
124
143
|
try {
|
|
125
144
|
const scope = app.getScope();
|
|
126
|
-
await evaluateCodeAsync(
|
|
145
|
+
await evaluateCodeAsync(
|
|
146
|
+
code,
|
|
147
|
+
cellData._outputRef,
|
|
148
|
+
{
|
|
149
|
+
importMap: app.importMap,
|
|
150
|
+
plugins: app.runtimePlugins,
|
|
151
|
+
runtime: cellData.runtime // <-- Passed dynamically from the cell configuration
|
|
152
|
+
},
|
|
153
|
+
scope,
|
|
154
|
+
);
|
|
127
155
|
} catch (err) {
|
|
128
156
|
hasError = true;
|
|
129
|
-
|
|
157
|
+
ErrorOutput(err).mount(cellData._outputRef);
|
|
130
158
|
}
|
|
131
159
|
|
|
132
160
|
const counter = this.store.get('executionCounter');
|
|
@@ -140,19 +168,17 @@ export class UINotebook extends UIElement{
|
|
|
140
168
|
cellData._outputRef.appendChild(container);
|
|
141
169
|
|
|
142
170
|
if (autoNext && !hasError) app.nextCell(cellData.id, "code");
|
|
143
|
-
|
|
171
|
+
app._refreshCell(cellData);
|
|
144
172
|
},
|
|
145
173
|
copyCode: async () => {
|
|
146
174
|
const textToCopy = cellData._editor.getValue() || cellData.code;
|
|
147
175
|
try {
|
|
148
176
|
await navigator.clipboard.writeText(textToCopy);
|
|
149
177
|
} catch (err) {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
document.body.appendChild(textArea);
|
|
153
|
-
textArea.select();
|
|
178
|
+
const temporary = tags.textarea(textToCopy).mount(document.body)
|
|
179
|
+
temporary.element.select()
|
|
154
180
|
document.execCommand("copy");
|
|
155
|
-
|
|
181
|
+
temporary.unmount()
|
|
156
182
|
}
|
|
157
183
|
},
|
|
158
184
|
toggleReadonly: () => {
|
|
@@ -173,7 +199,11 @@ export class UINotebook extends UIElement{
|
|
|
173
199
|
app._refreshCell(cellData);
|
|
174
200
|
},
|
|
175
201
|
addBelow: () => app.addCell(cellData.type, null, cellData.id),
|
|
176
|
-
deleteCell: () => app.deleteCell(cellData.id)
|
|
202
|
+
deleteCell: () => app.deleteCell(cellData.id),
|
|
203
|
+
setRuntime: (newRuntime) => {
|
|
204
|
+
cellData.runtime = newRuntime;
|
|
205
|
+
app._refreshCell(cellData);
|
|
206
|
+
},
|
|
177
207
|
};
|
|
178
208
|
}
|
|
179
209
|
|
|
@@ -193,7 +223,12 @@ export class UINotebook extends UIElement{
|
|
|
193
223
|
|
|
194
224
|
cellData._editor.sync(isActive, isMarkdownView);
|
|
195
225
|
cellData._bodySlot.replaceChildren(
|
|
196
|
-
isMarkdownView ? MarkdownView({
|
|
226
|
+
isMarkdownView ? MarkdownView({
|
|
227
|
+
cellData,
|
|
228
|
+
actions: cellData._actions,
|
|
229
|
+
remarkPlugins : this.remarkPlugins,
|
|
230
|
+
rehypePlugins : this.rehypePlugins
|
|
231
|
+
}) : cellData._editor.dom
|
|
197
232
|
);
|
|
198
233
|
|
|
199
234
|
const showOutput = cellData.type === "code" && cellData.hasDomOutput;
|
|
@@ -207,20 +242,34 @@ export class UINotebook extends UIElement{
|
|
|
207
242
|
}
|
|
208
243
|
|
|
209
244
|
setActiveCell(id) {
|
|
210
|
-
|
|
211
|
-
|
|
245
|
+
const cells = this.store.get('cells');
|
|
246
|
+
const target = cells.find(c => c.id === id);
|
|
212
247
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
c.isEditingMarkdown = false;
|
|
217
|
-
this._refreshCell(c);
|
|
218
|
-
}
|
|
219
|
-
});
|
|
248
|
+
if (!target) return;
|
|
249
|
+
|
|
250
|
+
const currentActiveId = this.store.get('activeCellId');
|
|
220
251
|
|
|
221
|
-
|
|
252
|
+
if (currentActiveId === id) {
|
|
253
|
+
return;
|
|
222
254
|
}
|
|
223
255
|
|
|
256
|
+
// Only one cell can be active.
|
|
257
|
+
cells.forEach(cell => {
|
|
258
|
+
if (cell.id !== id && cell.type === "markdown") {
|
|
259
|
+
cell.isEditingMarkdown = false;
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
this.store.set('activeCellId', id);
|
|
264
|
+
|
|
265
|
+
// Refresh every cell so the previous active cell loses "active".
|
|
266
|
+
cells.forEach(cell => {
|
|
267
|
+
if (cell._dom) {
|
|
268
|
+
this._refreshCell(cell);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
224
273
|
focusCell(targetId) {
|
|
225
274
|
// Close editing on other markdown cells before focusing
|
|
226
275
|
this.store.get('cells').forEach(c => {
|
|
@@ -249,6 +298,7 @@ export class UINotebook extends UIElement{
|
|
|
249
298
|
const newCell = {
|
|
250
299
|
id: newId, type, code: initialCode !== null ? initialCode : defaultText, readonly,
|
|
251
300
|
isEditingMarkdown: !readonly && type === "markdown" && initialCode === null,
|
|
301
|
+
runtime: null,
|
|
252
302
|
execCount: null, outputNode: null, hasDomOutput: false, _copiedFlag: false,
|
|
253
303
|
_dom: null, _editor: null, _actions: null,
|
|
254
304
|
_inPrompt: null, _outPrompt: null, _bodySlot: null, _outputGrid: null, _outputRef: null
|
package/src/transform.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import * as acorn from "acorn";
|
|
2
|
-
|
|
3
|
-
export const rewriteImportNode = (node, code, importMapConfig) => {
|
|
4
|
-
let source = node.source.value;
|
|
5
|
-
if (importMapConfig && importMapConfig[source]) {
|
|
6
|
-
source = importMapConfig[source];
|
|
7
|
-
} else if (!source.startsWith("http://") && !source.startsWith("https://") && !source.startsWith("./") && !source.startsWith("../")) {
|
|
8
|
-
source = `https://esm.sh/${source}`;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
if (node.specifiers.length === 0) return `await import("${source}");`;
|
|
12
|
-
|
|
13
|
-
let defaultImportName = null;
|
|
14
|
-
let namespaceId = null;
|
|
15
|
-
let namedImports = [];
|
|
16
|
-
|
|
17
|
-
node.specifiers.forEach(spec => {
|
|
18
|
-
if (spec.type === "ImportNamespaceSpecifier") namespaceId = spec.local.name;
|
|
19
|
-
else if (spec.type === "ImportDefaultSpecifier") defaultImportName = spec.local.name;
|
|
20
|
-
else if (spec.type === "ImportSpecifier") namedImports.push({ imported: spec.imported.name, local: spec.local.name });
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
if (namespaceId) return `__scope__.${namespaceId} = await import("${source}");`;
|
|
24
|
-
|
|
25
|
-
const tmp = `__mod_${node.start}`;
|
|
26
|
-
let lines = [`const ${tmp} = await import("${source}");`];
|
|
27
|
-
|
|
28
|
-
if (defaultImportName && namedImports.length === 0) {
|
|
29
|
-
lines.push(`__scope__.${defaultImportName} = ${tmp}.default ?? ${tmp};`);
|
|
30
|
-
return lines.join("\n");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (defaultImportName) lines.push(`__scope__.${defaultImportName} = ${tmp}.default ?? ${tmp};`);
|
|
34
|
-
namedImports.forEach(({ imported, local }) => {
|
|
35
|
-
lines.push(`__scope__.${local} = ${tmp}.${imported} !== undefined ? ${tmp}.${imported} : (${tmp}.default ?? ${tmp});`);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
return lines.join("\n");
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export const transformImportsAndScope = (code, importMapConfig) => {
|
|
42
|
-
try {
|
|
43
|
-
const ast = acorn.parse(code, { ecmaVersion: "latest", sourceType: "module", allowReturnOutsideFunction: true });
|
|
44
|
-
let modifications = [];
|
|
45
|
-
|
|
46
|
-
ast.body.forEach(node => {
|
|
47
|
-
if (node.type === "ImportDeclaration") {
|
|
48
|
-
modifications.push({ start: node.start, end: node.end, replacement: rewriteImportNode(node, code, importMapConfig) });
|
|
49
|
-
} else if (node.type === "VariableDeclaration") {
|
|
50
|
-
let replacementCode = "";
|
|
51
|
-
node.declarations.forEach(decl => {
|
|
52
|
-
if (decl.id.type === "Identifier") {
|
|
53
|
-
const varName = decl.id.name;
|
|
54
|
-
const initCode = decl.init ? code.slice(decl.init.start, decl.init.end) : "undefined";
|
|
55
|
-
replacementCode += `__scope__.${varName} = ${initCode};\n`;
|
|
56
|
-
} else if (decl.id.type === "ObjectPattern") {
|
|
57
|
-
const initCode = decl.init ? code.slice(decl.init.start, decl.init.end) : "undefined";
|
|
58
|
-
const tempVar = `__destruct_obj_${decl.start}`;
|
|
59
|
-
replacementCode += `(() => { const ${tempVar} = ${initCode}; `;
|
|
60
|
-
decl.id.properties.forEach(prop => {
|
|
61
|
-
if (prop.value && prop.value.type === "Identifier") {
|
|
62
|
-
const localName = prop.value.name;
|
|
63
|
-
const keyName = prop.key.type === "Identifier" ? prop.key.name : prop.key.value;
|
|
64
|
-
replacementCode += `__scope__.${localName} = ${tempVar}.${keyName}; `;
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
replacementCode += `})();\n`;
|
|
68
|
-
} else if (decl.id.type === "ArrayPattern") {
|
|
69
|
-
const initCode = decl.init ? code.slice(decl.init.start, decl.init.end) : "undefined";
|
|
70
|
-
const tempVar = `__destruct_arr_${decl.start}`;
|
|
71
|
-
replacementCode += `(() => { const ${tempVar} = ${initCode}; `;
|
|
72
|
-
decl.id.elements.forEach((elem, index) => {
|
|
73
|
-
if (elem && elem.type === "Identifier") {
|
|
74
|
-
const localName = elem.name;
|
|
75
|
-
replacementCode += `__scope__.${localName} = ${tempVar}[${index}]; `;
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
replacementCode += `})();\n`;
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
modifications.push({ start: node.start, end: node.end, replacement: replacementCode });
|
|
82
|
-
} else if (node.type === "FunctionDeclaration" && node.id) {
|
|
83
|
-
const funcName = node.id.name;
|
|
84
|
-
const funcBody = code.slice(node.start, node.end);
|
|
85
|
-
modifications.push({ start: node.start, end: node.end, replacement: `__scope__.${funcName} = ${funcBody.replace(/^function\s+\w+/, "function")}` });
|
|
86
|
-
} else if (node.type === "ClassDeclaration" && node.id) {
|
|
87
|
-
const className = node.id.name;
|
|
88
|
-
const classBody = code.slice(node.start, node.end);
|
|
89
|
-
modifications.push({ start: node.start, end: node.end, replacement: `__scope__.${className} = ${classBody.replace(/^class\s+\w+/, "class")}` });
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
modifications.sort((a, b) => b.start - a.start);
|
|
94
|
-
let transformedCode = code;
|
|
95
|
-
modifications.forEach(mod => { transformedCode = transformedCode.slice(0, mod.start) + mod.replacement + transformedCode.slice(mod.end); });
|
|
96
|
-
return transformedCode;
|
|
97
|
-
} catch (e) {
|
|
98
|
-
if (code.trim().startsWith("import ") || code.includes("\nimport ")) throw new SyntaxError("Invalid code module layout syntax.");
|
|
99
|
-
return code;
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
export const evaluateCodeAsync = async (code, TARGET, importMapConfig, scope) => {
|
|
104
|
-
const validScope = scope || Object.create(null);
|
|
105
|
-
const compiledCode = transformImportsAndScope(code, importMapConfig);
|
|
106
|
-
|
|
107
|
-
const runCell = new Function("TARGET", "__scope__", `
|
|
108
|
-
return (async () => {
|
|
109
|
-
with (__scope__) {
|
|
110
|
-
${compiledCode}
|
|
111
|
-
}
|
|
112
|
-
})();
|
|
113
|
-
`);
|
|
114
|
-
|
|
115
|
-
const result = await runCell(TARGET, validScope);
|
|
116
|
-
};
|
|
File without changes
|
|
File without changes
|