@zikojs/notebook 0.7.2 → 0.8.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 +1 -1
- package/src/{codeEditor.js → code-editor/index.js} +3 -3
- package/src/compiler/evaluate.js +67 -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/CellItem/index.js +127 -0
- 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 +28 -0
- package/src/components/index.css +4 -0
- package/src/components/index.js +4 -0
- package/src/index.css +7 -210
- package/src/notebook.js +34 -14
- package/src/components.js +0 -180
- package/src/transform.js +0 -116
- /package/src/{icons.js → components/icons/index.js} +0 -0
package/src/components.js
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import van from "vanjs-core";
|
|
2
|
-
import { remark } from "remark";
|
|
3
|
-
import remarkRehype from "remark-rehype";
|
|
4
|
-
import rehypeStringify from "rehype-stringify";
|
|
5
|
-
import { createCodeEditor } from "./codeEditor.js";
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
Check,
|
|
9
|
-
Copy,
|
|
10
|
-
Lock,
|
|
11
|
-
Unlock,
|
|
12
|
-
Code2,
|
|
13
|
-
FileText,
|
|
14
|
-
Pencil,
|
|
15
|
-
Play,
|
|
16
|
-
Plus,
|
|
17
|
-
Trash2,
|
|
18
|
-
PlayForward,
|
|
19
|
-
Eraser,
|
|
20
|
-
Download,
|
|
21
|
-
Loader2
|
|
22
|
-
} from './icons.js';
|
|
23
|
-
|
|
24
|
-
const { button } = van.tags;
|
|
25
|
-
|
|
26
|
-
import { tags } from 'ziko/dom'
|
|
27
|
-
|
|
28
|
-
const { div, span } = tags
|
|
29
|
-
|
|
30
|
-
export const Button = ({ class: className = "", title = "", disabled = false, onclick }, ...children) =>
|
|
31
|
-
button({ class: `btn ${className}`, title, disabled, onclick }, ...children);
|
|
32
|
-
|
|
33
|
-
export function Header({ store, actions }) {
|
|
34
|
-
const statusBadge = span({ class: "status-badge" }, Loader2(), span('Loading ...')).element;
|
|
35
|
-
const runAllBtn = Button({ class: "btn-primary", onclick: actions.runAll }, PlayForward(), "Run All");
|
|
36
|
-
const clearBtn = Button({ onclick: actions.clearOutputs }, Eraser(), "Clear Outputs");
|
|
37
|
-
const addCodeBtn = Button({ onclick: actions.addCode }, Plus(), "Add Code");
|
|
38
|
-
const addMdBtn = Button({ onclick: actions.addMarkdown }, FileText(), "Add Markdown");
|
|
39
|
-
const deleteActiveBtn = Button({ onclick: actions.deleteActive }, Trash2(), "Delete Active");
|
|
40
|
-
const exportBtn = Button({ onclick: actions.exportData }, Download(), "Export Data");
|
|
41
|
-
|
|
42
|
-
store.on('change:executionQueueState', (q) => {
|
|
43
|
-
statusBadge.style.display = q.isRunning ? "inline-flex" : "none";
|
|
44
|
-
statusBadge.lastChild.textContent = `Executing Queue (${q.currentIndex + 1}/${q.total})`;
|
|
45
|
-
[runAllBtn, clearBtn, deleteActiveBtn].forEach(b => { b.disabled = q.isRunning; });
|
|
46
|
-
const maxReached = store.get('cells').length >= store.get('maxCells');
|
|
47
|
-
addCodeBtn.disabled = q.isRunning || maxReached;
|
|
48
|
-
addMdBtn.disabled = q.isRunning || maxReached;
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
return div({ class: "header" },
|
|
52
|
-
div({ class: "brand" }, "</>", statusBadge),
|
|
53
|
-
div({ class: "toolbar" },
|
|
54
|
-
runAllBtn, clearBtn, addCodeBtn, addMdBtn, deleteActiveBtn, exportBtn
|
|
55
|
-
)
|
|
56
|
-
).element;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function MarkdownView({ cellData, actions }) {
|
|
60
|
-
const content = div().element;
|
|
61
|
-
|
|
62
|
-
// Process Markdown asynchronously using Remark + Rehype
|
|
63
|
-
remark()
|
|
64
|
-
.use(remarkRehype, { allowDangerousHtml: true })
|
|
65
|
-
.use(rehypeStringify)
|
|
66
|
-
.process(cellData.code || "*Empty Markdown Cell*")
|
|
67
|
-
.then((file) => {
|
|
68
|
-
content.innerHTML = String(file);
|
|
69
|
-
})
|
|
70
|
-
.catch((err) => {
|
|
71
|
-
content.innerHTML = `<div class="error-output">Markdown rendering error: ${err.message}</div>`;
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const wrap = div({ class: "markdown-rendered-cell markdown-body" }, content);
|
|
75
|
-
wrap.onClick(() => actions.editMarkdown())
|
|
76
|
-
return wrap.element;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function CellControls({ cellData, store, actions }) {
|
|
80
|
-
const controlsContainer = div({ class: "cell-controls" }).element;
|
|
81
|
-
|
|
82
|
-
const renderButtons = () => {
|
|
83
|
-
const isQueueRunning = store.get('executionQueueState').isRunning;
|
|
84
|
-
const isMaxCellsReached = store.get('cells').length >= store.get('maxCells');
|
|
85
|
-
const isCopied = cellData._copiedFlag === true;
|
|
86
|
-
|
|
87
|
-
controlsContainer.replaceChildren(
|
|
88
|
-
Button({
|
|
89
|
-
class: `btn-icon ${isCopied ? "btn-active" : ""}`,
|
|
90
|
-
title: isCopied ? "Copied!" : "Copy Cell Content",
|
|
91
|
-
onclick: async () => {
|
|
92
|
-
await actions.copyCode();
|
|
93
|
-
cellData._copiedFlag = true;
|
|
94
|
-
renderButtons();
|
|
95
|
-
setTimeout(() => {
|
|
96
|
-
cellData._copiedFlag = false;
|
|
97
|
-
renderButtons();
|
|
98
|
-
}, 1500);
|
|
99
|
-
}
|
|
100
|
-
}, isCopied ? Check() : Copy()),
|
|
101
|
-
|
|
102
|
-
Button({
|
|
103
|
-
class: `btn-icon ${cellData.readonly ? "btn-active" : ""}`,
|
|
104
|
-
title: cellData.readonly ? "Make Editable" : "Make Read-Only",
|
|
105
|
-
disabled: isQueueRunning,
|
|
106
|
-
onclick: actions.toggleReadonly,
|
|
107
|
-
}, cellData.readonly ? Lock() : Unlock()),
|
|
108
|
-
|
|
109
|
-
Button({
|
|
110
|
-
class: `btn-icon ${cellData.type === "markdown" ? "btn-active" : ""}`,
|
|
111
|
-
title: `Switch to ${cellData.type === "code" ? "Markdown" : "JS Code"}`,
|
|
112
|
-
disabled: isQueueRunning || cellData.readonly,
|
|
113
|
-
onclick: actions.toggleType,
|
|
114
|
-
}, cellData.type === "code" ? FileText() : Code2()),
|
|
115
|
-
|
|
116
|
-
(cellData.type === "markdown" && !cellData.isEditingMarkdown)
|
|
117
|
-
? Button({
|
|
118
|
-
class: "btn-icon",
|
|
119
|
-
title: "Edit Markdown",
|
|
120
|
-
disabled: isQueueRunning || cellData.readonly,
|
|
121
|
-
onclick: actions.editMarkdown
|
|
122
|
-
}, Pencil())
|
|
123
|
-
: Button({
|
|
124
|
-
class: "btn-icon btn-primary",
|
|
125
|
-
title: "Run & Advance (Shift+Enter)",
|
|
126
|
-
disabled: isQueueRunning,
|
|
127
|
-
onclick: () => actions.runCode(true)
|
|
128
|
-
}, Play()),
|
|
129
|
-
|
|
130
|
-
Button({
|
|
131
|
-
class: "btn-icon",
|
|
132
|
-
title: "Add Cell Below",
|
|
133
|
-
disabled: isQueueRunning || isMaxCellsReached,
|
|
134
|
-
onclick: actions.addBelow
|
|
135
|
-
}, Plus()),
|
|
136
|
-
|
|
137
|
-
Button({
|
|
138
|
-
class: "btn-icon",
|
|
139
|
-
title: "Delete Cell",
|
|
140
|
-
disabled: isQueueRunning,
|
|
141
|
-
onclick: actions.deleteCell
|
|
142
|
-
}, Trash2())
|
|
143
|
-
);
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
renderButtons();
|
|
147
|
-
store.on('change', renderButtons);
|
|
148
|
-
|
|
149
|
-
return controlsContainer;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export function CellItem({ cellData, app, store }) {
|
|
153
|
-
const outputRef = div({ class: "output-area" }).element;
|
|
154
|
-
if (cellData.outputNode) outputRef.appendChild(cellData.outputNode);
|
|
155
|
-
cellData._outputRef = outputRef;
|
|
156
|
-
|
|
157
|
-
const actions = app._cellActions(cellData);
|
|
158
|
-
cellData._actions = actions;
|
|
159
|
-
cellData._editor = createCodeEditor(app, cellData, actions);
|
|
160
|
-
|
|
161
|
-
const inPrompt = span({ class: "prompt" }).element;
|
|
162
|
-
const outPrompt = span({ class: "prompt out" }).element;
|
|
163
|
-
const bodySlot = div().style({ display : 'contents'}).element;
|
|
164
|
-
const controlsEl = CellControls({ cellData, store, actions });
|
|
165
|
-
|
|
166
|
-
const outputGrid = div({ class: "cell-output-grid" }, outPrompt, div({ class: "output-wrapper" }, outputRef)).element;
|
|
167
|
-
const inputGrid = div({ class: "cell-input-grid" }, inPrompt, bodySlot, controlsEl).element;
|
|
168
|
-
const root = div({ class: "cell" }, inputGrid, outputGrid);
|
|
169
|
-
root.onClick(() => app.setActiveCell(cellData.id));
|
|
170
|
-
// root.onclick = () => app.setActiveCell(cellData.id);
|
|
171
|
-
|
|
172
|
-
cellData._dom = root.element;
|
|
173
|
-
cellData._inPrompt = inPrompt;
|
|
174
|
-
cellData._outPrompt = outPrompt;
|
|
175
|
-
cellData._bodySlot = bodySlot;
|
|
176
|
-
cellData._outputGrid = outputGrid;
|
|
177
|
-
|
|
178
|
-
app._refreshCell(cellData);
|
|
179
|
-
return root.element;
|
|
180
|
-
}
|
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
|