@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zikojs/notebook",
3
- "version": "0.14.2",
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": "^6.2.5",
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",
@@ -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) {
@@ -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
- scope.console = Console
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
- ${compiledCode}
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) {
@@ -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({ specifier: source });
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
- return `${CDN}${source}`;
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
- `__scope__.${namespaceId} = ` +
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
- `__scope__.${defaultImportName} = ` +
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
- `__scope__.${local} = ` +
180
+ `${importScope}.${local} = ` +
120
181
  `${tmp}[${JSON.stringify(imported)}] !== undefined ` +
121
182
  `? ${tmp}[${JSON.stringify(imported)}] ` +
122
- `: (${tmp}.default ?? ${tmp});`,
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 (
@@ -33,7 +33,9 @@ export function MarkdownView({
33
33
  content
34
34
  );
35
35
 
36
- wrap.onClick(() => actions.editMarkdown());
36
+ wrap.element.addEventListener("dblclick", () =>
37
+ actions.editMarkdown()
38
+ );
37
39
 
38
40
  return wrap.element;
39
41
  }
package/src/index.css CHANGED
@@ -23,6 +23,10 @@
23
23
  display: flex;
24
24
  flex-direction: column;
25
25
  gap: 12px;
26
+ overflow: auto;
27
+ /* max-height: 100vh; */
28
+ /* scrollbar-color: rebeccapurple transparent; */
29
+ /* scrollbar-color: var(--prompt-in) transparent; */
26
30
  }
27
31
 
28
32
  .ziko-notebook .cell {
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
- this._refreshCell(cell);
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(() => {