@zikojs/notebook 0.14.2 → 0.15.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 -4
- package/src/code-editor/index.js +53 -0
- package/src/compiler/evaluate.js +12 -3
- package/src/compiler/imports.js +69 -6
- package/src/compiler/transform.js +4 -0
- package/src/components/MarkdownView/index.js +3 -1
- package/src/index.css +4 -0
- package/src/notebook.js +25 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zikojs/notebook",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.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": {
|
|
@@ -41,16 +41,15 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@codemirror/autocomplete": "^6.20.3",
|
|
43
43
|
"@codemirror/commands": "^6.11.0",
|
|
44
|
-
"@codemirror/lang-javascript": "
|
|
44
|
+
"@codemirror/lang-javascript": "latest",
|
|
45
45
|
"@codemirror/lang-markdown": "^6.5.2",
|
|
46
46
|
"@codemirror/language": "^6.12.4",
|
|
47
47
|
"@codemirror/state": "^6.7.4",
|
|
48
48
|
"@codemirror/view": "^6.43.11",
|
|
49
|
-
"@zikojs/lucide": "^0.2.2",
|
|
50
49
|
"@zikojs/console": "^0.8.0",
|
|
50
|
+
"@zikojs/lucide": "^0.2.2",
|
|
51
51
|
"acorn": "^8.18.0",
|
|
52
52
|
"codemirror": "^6.0.2",
|
|
53
|
-
"lucide": "^1.44.0",
|
|
54
53
|
"rehype-stringify": "^10.0.1",
|
|
55
54
|
"remark": "^15.0.1",
|
|
56
55
|
"remark-rehype": "^11.1.2",
|
package/src/code-editor/index.js
CHANGED
|
@@ -74,6 +74,43 @@ export function createCodeEditor(app, cellData, actions) {
|
|
|
74
74
|
if (view) return;
|
|
75
75
|
|
|
76
76
|
const shortcutKeymap = keymap.of([
|
|
77
|
+
{
|
|
78
|
+
key: "Ctrl-ArrowDown",
|
|
79
|
+
run: () => actions.moveToAdjacentCell(1),
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
key: "Ctrl-ArrowUp",
|
|
83
|
+
run: () => actions.moveToAdjacentCell(-1),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
key: "ArrowDown",
|
|
87
|
+
run: (editorView) => {
|
|
88
|
+
const selection = editorView.state.selection.main;
|
|
89
|
+
const line = editorView.state.doc.lineAt(selection.head);
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
selection.empty &&
|
|
93
|
+
line.number === editorView.state.doc.lines
|
|
94
|
+
) {
|
|
95
|
+
return actions.moveToAdjacentCell(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return false;
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
key: "ArrowUp",
|
|
103
|
+
run: (editorView) => {
|
|
104
|
+
const selection = editorView.state.selection.main;
|
|
105
|
+
const line = editorView.state.doc.lineAt(selection.head);
|
|
106
|
+
|
|
107
|
+
if (selection.empty && line.number === 1) {
|
|
108
|
+
return actions.moveToAdjacentCell(-1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return false;
|
|
112
|
+
},
|
|
113
|
+
},
|
|
77
114
|
{
|
|
78
115
|
key: "Shift-Enter",
|
|
79
116
|
run: () => {
|
|
@@ -195,6 +232,22 @@ export function createCodeEditor(app, cellData, actions) {
|
|
|
195
232
|
|
|
196
233
|
getValue: () => (view ? view.state.doc.toString() : cellData.code),
|
|
197
234
|
|
|
235
|
+
focusAt: (position) => {
|
|
236
|
+
requestAnimationFrame(() => {
|
|
237
|
+
if (!view) return;
|
|
238
|
+
|
|
239
|
+
const cursor = Math.max(
|
|
240
|
+
0,
|
|
241
|
+
Math.min(position, view.state.doc.length),
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
view.dispatch({
|
|
245
|
+
selection: { anchor: cursor },
|
|
246
|
+
});
|
|
247
|
+
view.focus();
|
|
248
|
+
});
|
|
249
|
+
},
|
|
250
|
+
|
|
198
251
|
sync: (isActive, isMarkdownView) => {
|
|
199
252
|
if (!isMarkdownView) {
|
|
200
253
|
if (!view) {
|
package/src/compiler/evaluate.js
CHANGED
|
@@ -17,12 +17,14 @@ export const evaluateCodeAsync = async (
|
|
|
17
17
|
importMap = {},
|
|
18
18
|
plugins = [],
|
|
19
19
|
runtime = null,
|
|
20
|
+
cdnFallback = true,
|
|
20
21
|
} = {},
|
|
21
22
|
scope,
|
|
22
23
|
) => {
|
|
23
24
|
const validScope = scope || Object.create(null);
|
|
25
|
+
const importScope = Object.create(null);
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
validScope.console = Console;
|
|
26
28
|
|
|
27
29
|
const context = {
|
|
28
30
|
TARGET,
|
|
@@ -30,6 +32,7 @@ export const evaluateCodeAsync = async (
|
|
|
30
32
|
importMap,
|
|
31
33
|
plugins,
|
|
32
34
|
runtime,
|
|
35
|
+
cdnFallback
|
|
33
36
|
};
|
|
34
37
|
|
|
35
38
|
const selectedPlugin = plugins.find(
|
|
@@ -70,6 +73,8 @@ export const evaluateCodeAsync = async (
|
|
|
70
73
|
{
|
|
71
74
|
importMap,
|
|
72
75
|
plugins,
|
|
76
|
+
cdnFallback,
|
|
77
|
+
importScopeIdentifier: "__imports__",
|
|
73
78
|
},
|
|
74
79
|
);
|
|
75
80
|
// console.log(compiledCode)
|
|
@@ -77,6 +82,7 @@ export const evaluateCodeAsync = async (
|
|
|
77
82
|
const runCell = new Function(
|
|
78
83
|
"TARGET",
|
|
79
84
|
"__scope__",
|
|
85
|
+
"__imports__",
|
|
80
86
|
`
|
|
81
87
|
// Bind the active TARGET for this cell evaluation
|
|
82
88
|
if (typeof __Ziko__ !== "undefined" && __Ziko__.__Config__) {
|
|
@@ -89,9 +95,11 @@ export const evaluateCodeAsync = async (
|
|
|
89
95
|
return (async () => {
|
|
90
96
|
with (__scope__) {
|
|
91
97
|
return await (async () => {
|
|
92
|
-
|
|
98
|
+
with (__imports__) {
|
|
99
|
+
${compiledCode}
|
|
100
|
+
}
|
|
93
101
|
})();
|
|
94
|
-
|
|
102
|
+
}
|
|
95
103
|
})();
|
|
96
104
|
`,
|
|
97
105
|
);
|
|
@@ -99,6 +107,7 @@ export const evaluateCodeAsync = async (
|
|
|
99
107
|
let result = await runCell(
|
|
100
108
|
TARGET,
|
|
101
109
|
validScope,
|
|
110
|
+
importScope,
|
|
102
111
|
);
|
|
103
112
|
|
|
104
113
|
for (const plugin of plugins) {
|
package/src/compiler/imports.js
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
|
|
2
2
|
const CDN = "https://esm.sh/";
|
|
3
|
+
const MANIFEST_FILE = "ziko-notebook-imports.json";
|
|
4
|
+
const isProductionBuild =
|
|
5
|
+
typeof import.meta.env !== "undefined" && import.meta.env.PROD;
|
|
6
|
+
|
|
7
|
+
let buildImportMapPromise;
|
|
8
|
+
|
|
9
|
+
const loadBuildImportMap = () => {
|
|
10
|
+
if (!isProductionBuild || typeof document === "undefined") {
|
|
11
|
+
return Promise.resolve({});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!buildImportMapPromise) {
|
|
15
|
+
const manifestUrl = new URL(MANIFEST_FILE, document.baseURI);
|
|
16
|
+
|
|
17
|
+
buildImportMapPromise = fetch(manifestUrl).then(async (response) => {
|
|
18
|
+
if (response.status === 404) {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Failed to load the notebook import map: ${response.status} ${response.statusText}`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const imports = await response.json();
|
|
29
|
+
|
|
30
|
+
if (!imports || typeof imports !== "object" || Array.isArray(imports)) {
|
|
31
|
+
throw new TypeError("The notebook import map must be a JSON object.");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return imports;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return buildImportMapPromise;
|
|
39
|
+
};
|
|
3
40
|
|
|
4
41
|
const isURL = (source) =>
|
|
5
42
|
/^(?:https?:\/\/|data:|blob:)/i.test(source);
|
|
@@ -16,8 +53,10 @@ export const resolveImport = async (
|
|
|
16
53
|
importMap = {},
|
|
17
54
|
plugins = [],
|
|
18
55
|
resolverEndpoint = "/__notebook_resolve",
|
|
56
|
+
cdnFallback = true,
|
|
19
57
|
} = {},
|
|
20
58
|
) => {
|
|
59
|
+
// Preserve explicit mappings, including package subpaths.
|
|
21
60
|
if (importMap[source]) {
|
|
22
61
|
return importMap[source];
|
|
23
62
|
}
|
|
@@ -26,6 +65,7 @@ export const resolveImport = async (
|
|
|
26
65
|
return source;
|
|
27
66
|
}
|
|
28
67
|
|
|
68
|
+
// Preserve runtime-specific and custom resolution hooks.
|
|
29
69
|
for (const plugin of plugins) {
|
|
30
70
|
if (typeof plugin.resolveImport !== "function") {
|
|
31
71
|
continue;
|
|
@@ -38,8 +78,17 @@ export const resolveImport = async (
|
|
|
38
78
|
}
|
|
39
79
|
}
|
|
40
80
|
|
|
81
|
+
const buildImportMap = await loadBuildImportMap();
|
|
82
|
+
|
|
83
|
+
if (typeof buildImportMap[source] === "string") {
|
|
84
|
+
return buildImportMap[source];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Ask the host dev server to resolve installed packages.
|
|
41
88
|
try {
|
|
42
|
-
const params = new URLSearchParams({
|
|
89
|
+
const params = new URLSearchParams({
|
|
90
|
+
specifier: source,
|
|
91
|
+
});
|
|
43
92
|
|
|
44
93
|
const response = await fetch(
|
|
45
94
|
`${resolverEndpoint}?${params.toString()}`,
|
|
@@ -56,9 +105,19 @@ export const resolveImport = async (
|
|
|
56
105
|
// The Vite endpoint may not be available outside development.
|
|
57
106
|
}
|
|
58
107
|
|
|
59
|
-
|
|
108
|
+
if (cdnFallback) {
|
|
109
|
+
return `${CDN}${source}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Cannot resolve "${source}" locally. ` +
|
|
114
|
+
`Install the package or provide an importMap entry. ` +
|
|
115
|
+
`CDN fallback is disabled.`,
|
|
116
|
+
);
|
|
60
117
|
};
|
|
61
118
|
|
|
119
|
+
// Keep your existing rewriteImportNode implementation below.
|
|
120
|
+
|
|
62
121
|
export const rewriteImportNode = async (
|
|
63
122
|
node,
|
|
64
123
|
code,
|
|
@@ -68,6 +127,8 @@ export const rewriteImportNode = async (
|
|
|
68
127
|
node.source.value,
|
|
69
128
|
context,
|
|
70
129
|
);
|
|
130
|
+
const importScope =
|
|
131
|
+
context.importScopeIdentifier || "__scope__";
|
|
71
132
|
|
|
72
133
|
// Safely quote the URL in generated JavaScript.
|
|
73
134
|
const url = JSON.stringify(source);
|
|
@@ -96,7 +157,7 @@ export const rewriteImportNode = async (
|
|
|
96
157
|
|
|
97
158
|
if (namespaceId) {
|
|
98
159
|
return (
|
|
99
|
-
|
|
160
|
+
`${importScope}.${namespaceId} = ` +
|
|
100
161
|
`await import(${url});`
|
|
101
162
|
);
|
|
102
163
|
}
|
|
@@ -109,17 +170,19 @@ export const rewriteImportNode = async (
|
|
|
109
170
|
|
|
110
171
|
if (defaultImportName) {
|
|
111
172
|
lines.push(
|
|
112
|
-
|
|
173
|
+
`${importScope}.${defaultImportName} = ` +
|
|
113
174
|
`${tmp}.default ?? ${tmp};`,
|
|
114
175
|
);
|
|
115
176
|
}
|
|
116
177
|
|
|
117
178
|
for (const { imported, local } of namedImports) {
|
|
118
179
|
lines.push(
|
|
119
|
-
|
|
180
|
+
`${importScope}.${local} = ` +
|
|
120
181
|
`${tmp}[${JSON.stringify(imported)}] !== undefined ` +
|
|
121
182
|
`? ${tmp}[${JSON.stringify(imported)}] ` +
|
|
122
|
-
`: (${tmp}.default
|
|
183
|
+
`: (${tmp}.default?.[${JSON.stringify(imported)}] !== undefined ` +
|
|
184
|
+
`? ${tmp}.default[${JSON.stringify(imported)}] ` +
|
|
185
|
+
`: (${tmp}.default ?? ${tmp}));`,
|
|
123
186
|
);
|
|
124
187
|
}
|
|
125
188
|
|
|
@@ -17,6 +17,7 @@ export const transformImportsAndScope = async (
|
|
|
17
17
|
importMap = {},
|
|
18
18
|
plugins = [],
|
|
19
19
|
resolverEndpoint,
|
|
20
|
+
importScopeIdentifier = "__scope__",
|
|
20
21
|
} = {},
|
|
21
22
|
) => {
|
|
22
23
|
try {
|
|
@@ -30,6 +31,7 @@ export const transformImportsAndScope = async (
|
|
|
30
31
|
importMap,
|
|
31
32
|
plugins,
|
|
32
33
|
resolverEndpoint,
|
|
34
|
+
importScopeIdentifier,
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
const modifications = [];
|
|
@@ -95,6 +97,8 @@ export const transformImportsAndScope = async (
|
|
|
95
97
|
transformedCode.slice(modification.end);
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
console.log(transformedCode)
|
|
101
|
+
|
|
98
102
|
return transformedCode;
|
|
99
103
|
} catch (error) {
|
|
100
104
|
if (
|
package/src/index.css
CHANGED
package/src/notebook.js
CHANGED
|
@@ -33,6 +33,7 @@ export class UINotebook extends UIElement {
|
|
|
33
33
|
constructor({
|
|
34
34
|
cells: initialCells = [],
|
|
35
35
|
importMap = {},
|
|
36
|
+
cdnFallback = true,
|
|
36
37
|
runCells = true,
|
|
37
38
|
minLines = 3,
|
|
38
39
|
maxCells = Infinity,
|
|
@@ -58,6 +59,7 @@ export class UINotebook extends UIElement {
|
|
|
58
59
|
this.resetScope();
|
|
59
60
|
|
|
60
61
|
this.importMap = importMap;
|
|
62
|
+
this.cdnFallback = cdnFallback;
|
|
61
63
|
this.runtimePlugins = runtimePlugins;
|
|
62
64
|
this.codeMirrorConfig = codeMirrorConfig;
|
|
63
65
|
this.codeMirrorPlugins = codeMirrorPlugins;
|
|
@@ -312,6 +314,23 @@ export class UINotebook extends UIElement {
|
|
|
312
314
|
return {
|
|
313
315
|
setActive: () => app.setActiveCell(cellData.id),
|
|
314
316
|
|
|
317
|
+
moveToAdjacentCell: (direction) => {
|
|
318
|
+
const cells = app.store.get("cells");
|
|
319
|
+
const index = cells.findIndex(
|
|
320
|
+
(cell) => cell.id === cellData.id
|
|
321
|
+
);
|
|
322
|
+
const target = cells[index + direction];
|
|
323
|
+
|
|
324
|
+
if (!target) return false;
|
|
325
|
+
|
|
326
|
+
app.focusCell(target.id);
|
|
327
|
+
target._editor.focusAt(
|
|
328
|
+
direction > 0 ? 0 : target._editor.getValue().length
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
return true;
|
|
332
|
+
},
|
|
333
|
+
|
|
315
334
|
runCode: async (autoNext = true) => {
|
|
316
335
|
// If this cell's code is still loading, wait for it.
|
|
317
336
|
await app._resolveCellCode(cellData);
|
|
@@ -349,6 +368,7 @@ export class UINotebook extends UIElement {
|
|
|
349
368
|
cellData._outputRef,
|
|
350
369
|
{
|
|
351
370
|
importMap: app.importMap,
|
|
371
|
+
cdnFallback: app.cdnFallback,
|
|
352
372
|
plugins: app.runtimePlugins,
|
|
353
373
|
runtime: cellData.runtime,
|
|
354
374
|
},
|
|
@@ -542,7 +562,11 @@ export class UINotebook extends UIElement {
|
|
|
542
562
|
}
|
|
543
563
|
|
|
544
564
|
this.store.set("activeCellId", id);
|
|
545
|
-
|
|
565
|
+
cells.forEach((currentCell) => {
|
|
566
|
+
if (currentCell._dom) {
|
|
567
|
+
this._refreshCell(currentCell);
|
|
568
|
+
}
|
|
569
|
+
});
|
|
546
570
|
|
|
547
571
|
// Scroll the target cell into view when it is outside the viewport.
|
|
548
572
|
requestAnimationFrame(() => {
|