@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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zikojs/notebook",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "A lightweight, extensible JavaScript notebook component with Markdown, executable cells, persistent state, and pluggable runtimes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { transformImportsAndScope } from "./transform.js";
|
|
2
|
+
|
|
3
|
+
export const evaluateCodeAsync = async (
|
|
4
|
+
code,
|
|
5
|
+
TARGET,
|
|
6
|
+
{
|
|
7
|
+
importMap = {},
|
|
8
|
+
plugins = [],
|
|
9
|
+
runtime = null,
|
|
10
|
+
} = {},
|
|
11
|
+
scope,
|
|
12
|
+
) => {
|
|
13
|
+
const validScope =
|
|
14
|
+
scope || Object.create(null);
|
|
15
|
+
|
|
16
|
+
const context = {
|
|
17
|
+
TARGET,
|
|
18
|
+
scope: validScope,
|
|
19
|
+
importMap,
|
|
20
|
+
plugins,
|
|
21
|
+
runtime,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const selectedPlugin = plugins.find(
|
|
25
|
+
p => p.name === runtime,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Runtime-specific evaluator
|
|
29
|
+
if (
|
|
30
|
+
selectedPlugin &&
|
|
31
|
+
selectedPlugin.evaluate
|
|
32
|
+
) {
|
|
33
|
+
return await selectedPlugin.evaluate(
|
|
34
|
+
code,
|
|
35
|
+
context,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let compiledCode = code;
|
|
40
|
+
|
|
41
|
+
for (const plugin of plugins) {
|
|
42
|
+
if (
|
|
43
|
+
selectedPlugin &&
|
|
44
|
+
plugin.name !== runtime
|
|
45
|
+
) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (plugin.transform) {
|
|
50
|
+
compiledCode = await plugin.transform(
|
|
51
|
+
compiledCode,
|
|
52
|
+
context,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
compiledCode =
|
|
58
|
+
transformImportsAndScope(
|
|
59
|
+
compiledCode,
|
|
60
|
+
importMap,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const runCell = new Function(
|
|
64
|
+
"TARGET",
|
|
65
|
+
"__scope__",
|
|
66
|
+
`
|
|
67
|
+
return (async () => {
|
|
68
|
+
with (__scope__) {
|
|
69
|
+
${compiledCode}
|
|
70
|
+
}
|
|
71
|
+
})();
|
|
72
|
+
`,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
let result = await runCell(
|
|
76
|
+
TARGET,
|
|
77
|
+
validScope,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
for (const plugin of plugins) {
|
|
81
|
+
if (
|
|
82
|
+
selectedPlugin &&
|
|
83
|
+
plugin.name !== runtime
|
|
84
|
+
) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (plugin.afterEvaluate) {
|
|
89
|
+
result = await plugin.afterEvaluate(
|
|
90
|
+
result,
|
|
91
|
+
context,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return result;
|
|
97
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export const resolveImport = (
|
|
2
|
+
source,
|
|
3
|
+
{
|
|
4
|
+
importMap = {},
|
|
5
|
+
plugins = [],
|
|
6
|
+
} = {},
|
|
7
|
+
) => {
|
|
8
|
+
if (importMap[source]) {
|
|
9
|
+
return importMap[source];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
for (const plugin of plugins) {
|
|
13
|
+
if (plugin.resolveImport) {
|
|
14
|
+
const resolved = plugin.resolveImport(
|
|
15
|
+
source,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
if (resolved) {
|
|
19
|
+
return resolved;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (
|
|
25
|
+
source.startsWith("http://") ||
|
|
26
|
+
source.startsWith("https://") ||
|
|
27
|
+
source.startsWith("./") ||
|
|
28
|
+
source.startsWith("../")
|
|
29
|
+
) {
|
|
30
|
+
return source;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return `https://esm.sh/${source}`;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const rewriteImportNode = (
|
|
37
|
+
node,
|
|
38
|
+
code,
|
|
39
|
+
context,
|
|
40
|
+
) => {
|
|
41
|
+
const source = resolveImport(
|
|
42
|
+
node.source.value,
|
|
43
|
+
context,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (node.specifiers.length === 0) {
|
|
47
|
+
return `await import("${source}");`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let defaultImportName = null;
|
|
51
|
+
let namespaceId = null;
|
|
52
|
+
const namedImports = [];
|
|
53
|
+
|
|
54
|
+
node.specifiers.forEach((spec) => {
|
|
55
|
+
if (spec.type === "ImportNamespaceSpecifier") {
|
|
56
|
+
namespaceId = spec.local.name;
|
|
57
|
+
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
58
|
+
defaultImportName = spec.local.name;
|
|
59
|
+
} else if (spec.type === "ImportSpecifier") {
|
|
60
|
+
namedImports.push({
|
|
61
|
+
imported: spec.imported.name,
|
|
62
|
+
local: spec.local.name,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (namespaceId) {
|
|
68
|
+
return `__scope__.${namespaceId} = await import("${source}");`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const tmp = `__mod_${node.start}`;
|
|
72
|
+
|
|
73
|
+
const lines = [
|
|
74
|
+
`const ${tmp} = await import("${source}");`,
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
if (defaultImportName) {
|
|
78
|
+
lines.push(
|
|
79
|
+
`__scope__.${defaultImportName} = ` +
|
|
80
|
+
`${tmp}.default ?? ${tmp};`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
namedImports.forEach(({ imported, local }) => {
|
|
85
|
+
lines.push(
|
|
86
|
+
`__scope__.${local} = ` +
|
|
87
|
+
`${tmp}.${imported} !== undefined ` +
|
|
88
|
+
`? ${tmp}.${imported} ` +
|
|
89
|
+
`: (${tmp}.default ?? ${tmp});`,
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return lines.join("\n");
|
|
94
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export const rewriteVariableDeclaration = (
|
|
2
|
+
node,
|
|
3
|
+
code,
|
|
4
|
+
) => {
|
|
5
|
+
let replacementCode = "";
|
|
6
|
+
|
|
7
|
+
node.declarations.forEach((decl) => {
|
|
8
|
+
const initCode = decl.init
|
|
9
|
+
? code.slice(decl.init.start, decl.init.end)
|
|
10
|
+
: "undefined";
|
|
11
|
+
|
|
12
|
+
if (decl.id.type === "Identifier") {
|
|
13
|
+
const varName = decl.id.name;
|
|
14
|
+
|
|
15
|
+
replacementCode +=
|
|
16
|
+
`__scope__.${varName} = ${initCode};\n`;
|
|
17
|
+
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (decl.id.type === "ObjectPattern") {
|
|
22
|
+
const tempVar = `__destruct_obj_${decl.start}`;
|
|
23
|
+
|
|
24
|
+
replacementCode +=
|
|
25
|
+
`(() => { const ${tempVar} = ${initCode}; `;
|
|
26
|
+
|
|
27
|
+
decl.id.properties.forEach((prop) => {
|
|
28
|
+
if (
|
|
29
|
+
prop.value &&
|
|
30
|
+
prop.value.type === "Identifier"
|
|
31
|
+
) {
|
|
32
|
+
const localName = prop.value.name;
|
|
33
|
+
|
|
34
|
+
const keyName =
|
|
35
|
+
prop.key.type === "Identifier"
|
|
36
|
+
? prop.key.name
|
|
37
|
+
: prop.key.value;
|
|
38
|
+
|
|
39
|
+
replacementCode +=
|
|
40
|
+
`__scope__.${localName} = ` +
|
|
41
|
+
`${tempVar}.${keyName}; `;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
replacementCode += `})();\n`;
|
|
46
|
+
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (decl.id.type === "ArrayPattern") {
|
|
51
|
+
const tempVar = `__destruct_arr_${decl.start}`;
|
|
52
|
+
|
|
53
|
+
replacementCode +=
|
|
54
|
+
`(() => { const ${tempVar} = ${initCode}; `;
|
|
55
|
+
|
|
56
|
+
decl.id.elements.forEach((elem, index) => {
|
|
57
|
+
if (elem && elem.type === "Identifier") {
|
|
58
|
+
replacementCode +=
|
|
59
|
+
`__scope__.${elem.name} = ` +
|
|
60
|
+
`${tempVar}[${index}]; `;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
replacementCode += `})();\n`;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return replacementCode;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const rewriteFunctionDeclaration = (
|
|
72
|
+
node,
|
|
73
|
+
code,
|
|
74
|
+
) => {
|
|
75
|
+
const name = node.id.name;
|
|
76
|
+
const body = code.slice(node.start, node.end);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
`__scope__.${name} = ` +
|
|
80
|
+
body.replace(/^function\s+\w+/, "function")
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const rewriteClassDeclaration = (
|
|
85
|
+
node,
|
|
86
|
+
code,
|
|
87
|
+
) => {
|
|
88
|
+
const name = node.id.name;
|
|
89
|
+
const body = code.slice(node.start, node.end);
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
`__scope__.${name} = ` +
|
|
93
|
+
body.replace(/^class\s+\w+/, "class")
|
|
94
|
+
);
|
|
95
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as acorn from "acorn";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
rewriteImportNode,
|
|
5
|
+
} from "./imports.js";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
rewriteVariableDeclaration,
|
|
9
|
+
rewriteFunctionDeclaration,
|
|
10
|
+
rewriteClassDeclaration,
|
|
11
|
+
} from "./scope.js";
|
|
12
|
+
|
|
13
|
+
export const transformImportsAndScope = (
|
|
14
|
+
code,
|
|
15
|
+
{
|
|
16
|
+
importMap = {},
|
|
17
|
+
plugins = [],
|
|
18
|
+
} = {},
|
|
19
|
+
) => {
|
|
20
|
+
try {
|
|
21
|
+
const ast = acorn.parse(code, {
|
|
22
|
+
ecmaVersion: "latest",
|
|
23
|
+
sourceType: "module",
|
|
24
|
+
allowReturnOutsideFunction: true,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const context = {
|
|
28
|
+
importMap,
|
|
29
|
+
plugins,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const modifications = [];
|
|
33
|
+
|
|
34
|
+
ast.body.forEach((node) => {
|
|
35
|
+
if (node.type === "ImportDeclaration") {
|
|
36
|
+
modifications.push({
|
|
37
|
+
start: node.start,
|
|
38
|
+
end: node.end,
|
|
39
|
+
replacement: rewriteImportNode(
|
|
40
|
+
node,
|
|
41
|
+
code,
|
|
42
|
+
context,
|
|
43
|
+
),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (node.type === "VariableDeclaration") {
|
|
50
|
+
modifications.push({
|
|
51
|
+
start: node.start,
|
|
52
|
+
end: node.end,
|
|
53
|
+
replacement: rewriteVariableDeclaration(
|
|
54
|
+
node,
|
|
55
|
+
code,
|
|
56
|
+
),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
node.type === "FunctionDeclaration" &&
|
|
64
|
+
node.id
|
|
65
|
+
) {
|
|
66
|
+
modifications.push({
|
|
67
|
+
start: node.start,
|
|
68
|
+
end: node.end,
|
|
69
|
+
replacement: rewriteFunctionDeclaration(
|
|
70
|
+
node,
|
|
71
|
+
code,
|
|
72
|
+
),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
node.type === "ClassDeclaration" &&
|
|
80
|
+
node.id
|
|
81
|
+
) {
|
|
82
|
+
modifications.push({
|
|
83
|
+
start: node.start,
|
|
84
|
+
end: node.end,
|
|
85
|
+
replacement: rewriteClassDeclaration(
|
|
86
|
+
node,
|
|
87
|
+
code,
|
|
88
|
+
),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
modifications.sort(
|
|
94
|
+
(a, b) => b.start - a.start,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
let transformedCode = code;
|
|
98
|
+
|
|
99
|
+
modifications.forEach((modification) => {
|
|
100
|
+
transformedCode =
|
|
101
|
+
transformedCode.slice(
|
|
102
|
+
0,
|
|
103
|
+
modification.start,
|
|
104
|
+
) +
|
|
105
|
+
modification.replacement +
|
|
106
|
+
transformedCode.slice(
|
|
107
|
+
modification.end,
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return transformedCode;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (
|
|
114
|
+
code.trim().startsWith("import ") ||
|
|
115
|
+
code.includes("\nimport ")
|
|
116
|
+
) {
|
|
117
|
+
throw new SyntaxError(
|
|
118
|
+
"Invalid code module layout syntax.",
|
|
119
|
+
{
|
|
120
|
+
cause: error,
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return code;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* --- BUTTONS --- */
|
|
2
|
+
|
|
3
|
+
.ziko-notebook .btn {
|
|
4
|
+
display: inline-flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
gap: 6px;
|
|
8
|
+
background: #ffffff;
|
|
9
|
+
border: 1px solid var(--border-color);
|
|
10
|
+
padding: 6px 10px;
|
|
11
|
+
border-radius: 4px;
|
|
12
|
+
font-size: 12px;
|
|
13
|
+
font-weight: 500;
|
|
14
|
+
color: #334155;
|
|
15
|
+
cursor: pointer;
|
|
16
|
+
line-height: 1;
|
|
17
|
+
white-space: nowrap;
|
|
18
|
+
transition: background 0.12s ease, border-color 0.12s ease;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.ziko-notebook .btn:hover {
|
|
22
|
+
background: #f1f5f9;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.ziko-notebook .btn:disabled {
|
|
26
|
+
opacity: 0.5;
|
|
27
|
+
cursor: not-allowed;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.ziko-notebook .btn-icon {
|
|
31
|
+
padding: 6px;
|
|
32
|
+
border-radius: 4px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.ziko-notebook .btn-primary {
|
|
36
|
+
background: var(--primary);
|
|
37
|
+
color: #ffffff;
|
|
38
|
+
border-color: var(--primary);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.ziko-notebook .btn-primary:hover {
|
|
42
|
+
background: var(--primary-hover);
|
|
43
|
+
border-color: var(--primary-hover);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.ziko-notebook .btn-active {
|
|
47
|
+
background: #e2e8f0;
|
|
48
|
+
border-color: #cbd5e1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ziko-notebook .icon {
|
|
52
|
+
width: 14px;
|
|
53
|
+
height: 14px;
|
|
54
|
+
stroke-width: 2;
|
|
55
|
+
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import rehypeStringify from "rehype-stringify";
|
|
5
|
-
import { createCodeEditor } from "./codeEditor.js";
|
|
6
|
-
|
|
1
|
+
import { createCodeEditor } from "../../code-editor/index.js";
|
|
2
|
+
import { tags } from 'ziko/dom'
|
|
3
|
+
import { useState } from 'ziko/hooks'
|
|
7
4
|
import {
|
|
8
5
|
Check,
|
|
9
6
|
Copy,
|
|
@@ -15,68 +12,55 @@ import {
|
|
|
15
12
|
Play,
|
|
16
13
|
Plus,
|
|
17
14
|
Trash2,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Download,
|
|
21
|
-
Loader2
|
|
22
|
-
} from './icons.js';
|
|
23
|
-
|
|
24
|
-
const { button } = van.tags;
|
|
25
|
-
|
|
26
|
-
import { tags } from 'ziko/dom'
|
|
15
|
+
} from '../icons/index.js';
|
|
16
|
+
const { div, span } = tags;
|
|
27
17
|
|
|
28
|
-
|
|
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 ...'));
|
|
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.element.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
|
-
});
|
|
18
|
+
import { Button } from "../Button/index.js";
|
|
50
19
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
div({ class: "toolbar" },
|
|
54
|
-
runAllBtn, clearBtn, addCodeBtn, addMdBtn, deleteActiveBtn, exportBtn
|
|
55
|
-
)
|
|
56
|
-
).element;
|
|
20
|
+
export const Prompt = () => {
|
|
21
|
+
return span({ class: "prompt" })
|
|
57
22
|
}
|
|
58
23
|
|
|
59
|
-
export function
|
|
60
|
-
const
|
|
24
|
+
export function CellItem({ cellData, app, store }) {
|
|
25
|
+
const outputRef = div({ class: "output-area" });
|
|
26
|
+
if (cellData.outputNode) outputRef.append(cellData.outputNode);
|
|
27
|
+
cellData._outputRef = outputRef.element;
|
|
28
|
+
|
|
29
|
+
const actions = app._cellActions(cellData);
|
|
30
|
+
cellData._actions = actions;
|
|
31
|
+
cellData._editor = createCodeEditor(app, cellData, actions);
|
|
61
32
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
content.element.innerHTML = String(file);
|
|
69
|
-
})
|
|
70
|
-
.catch((err) => {
|
|
71
|
-
content.element.innerHTML = `<div class="error-output">Markdown rendering error: ${err.message}</div>`;
|
|
72
|
-
});
|
|
33
|
+
/* Check ziko */
|
|
34
|
+
const [In, setIn] = useState(0);
|
|
35
|
+
globalThis.setIn = setIn;
|
|
36
|
+
const inPrompt = span({ class: "prompt" }, In);
|
|
37
|
+
const outPrompt = span({ class: "prompt out" });
|
|
38
|
+
const bodySlot = div().style({ display: 'contents' });
|
|
73
39
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
40
|
+
// Pass runtimePlugins from app into CellControls
|
|
41
|
+
const controlsEl = CellControls({
|
|
42
|
+
cellData,
|
|
43
|
+
store,
|
|
44
|
+
actions,
|
|
45
|
+
runtimePlugins: app.runtimePlugins
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const outputGrid = div({ class: "cell-output-grid" }, outPrompt, div({ class: "output-wrapper" }, outputRef));
|
|
49
|
+
const inputGrid = div({ class: "cell-input-grid" }, inPrompt, bodySlot, controlsEl);
|
|
50
|
+
const root = div({ class: "cell" }, inputGrid, outputGrid);
|
|
51
|
+
root.onClick(() => app.setActiveCell(cellData.id));
|
|
78
52
|
|
|
79
|
-
|
|
53
|
+
cellData._dom = root.element;
|
|
54
|
+
cellData._inPrompt = inPrompt.element;
|
|
55
|
+
cellData._outPrompt = outPrompt.element;
|
|
56
|
+
cellData._bodySlot = bodySlot.element;
|
|
57
|
+
cellData._outputGrid = outputGrid.element;
|
|
58
|
+
cellData._setIn = setIn;
|
|
59
|
+
|
|
60
|
+
app._refreshCell(cellData);
|
|
61
|
+
return root.element;
|
|
62
|
+
}
|
|
63
|
+
export function CellControls({ cellData, store, actions, runtimePlugins = [] }) {
|
|
80
64
|
const controlsContainer = div({ class: "cell-controls" }).element;
|
|
81
65
|
|
|
82
66
|
const renderButtons = () => {
|
|
@@ -84,7 +68,38 @@ export function CellControls({ cellData, store, actions }) {
|
|
|
84
68
|
const isMaxCellsReached = store.get('cells').length >= store.get('maxCells');
|
|
85
69
|
const isCopied = cellData._copiedFlag === true;
|
|
86
70
|
|
|
87
|
-
|
|
71
|
+
// Build the runtime selector dropdown for code cells using native DOM options
|
|
72
|
+
let runtimeSelectEl = null;
|
|
73
|
+
if (cellData.type === "code") {
|
|
74
|
+
const selectElement = document.createElement("select");
|
|
75
|
+
selectElement.className = "runtime-select";
|
|
76
|
+
selectElement.title = "Select Cell Runtime";
|
|
77
|
+
selectElement.disabled = cellData.readonly;
|
|
78
|
+
|
|
79
|
+
selectElement.onchange = (e) => {
|
|
80
|
+
actions.setRuntime(e.target.value === "" ? null : e.target.value);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Default JS option
|
|
84
|
+
const optJs = document.createElement("option");
|
|
85
|
+
optJs.value = "";
|
|
86
|
+
optJs.textContent = "JS";
|
|
87
|
+
selectElement.appendChild(optJs);
|
|
88
|
+
|
|
89
|
+
// Runtime plugin options
|
|
90
|
+
for (const p of runtimePlugins) {
|
|
91
|
+
const opt = document.createElement("option");
|
|
92
|
+
opt.value = p.name;
|
|
93
|
+
opt.textContent = p.name.charAt(0).toUpperCase() + p.name.slice(1);
|
|
94
|
+
selectElement.appendChild(opt);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Explicitly set the active value
|
|
98
|
+
selectElement.value = cellData.runtime || "";
|
|
99
|
+
runtimeSelectEl = selectElement;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const children = [
|
|
88
103
|
Button({
|
|
89
104
|
class: `btn-icon ${isCopied ? "btn-active" : ""}`,
|
|
90
105
|
title: isCopied ? "Copied!" : "Copy Cell Content",
|
|
@@ -140,7 +155,13 @@ export function CellControls({ cellData, store, actions }) {
|
|
|
140
155
|
disabled: isQueueRunning,
|
|
141
156
|
onclick: actions.deleteCell
|
|
142
157
|
}, Trash2())
|
|
143
|
-
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
if (runtimeSelectEl) {
|
|
161
|
+
children.unshift(runtimeSelectEl);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
controlsContainer.replaceChildren(...children);
|
|
144
165
|
};
|
|
145
166
|
|
|
146
167
|
renderButtons();
|
|
@@ -148,32 +169,3 @@ export function CellControls({ cellData, store, actions }) {
|
|
|
148
169
|
|
|
149
170
|
return controlsContainer;
|
|
150
171
|
}
|
|
151
|
-
|
|
152
|
-
export function CellItem({ cellData, app, store }) {
|
|
153
|
-
const outputRef = div({ class: "output-area" });
|
|
154
|
-
if (cellData.outputNode) outputRef.append(cellData.outputNode);
|
|
155
|
-
cellData._outputRef = outputRef.element;
|
|
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" });
|
|
162
|
-
const outPrompt = span({ class: "prompt out" });
|
|
163
|
-
const bodySlot = div().style({ display : 'contents'});
|
|
164
|
-
const controlsEl = CellControls({ cellData, store, actions });
|
|
165
|
-
|
|
166
|
-
const outputGrid = div({ class: "cell-output-grid" }, outPrompt, div({ class: "output-wrapper" }, outputRef));
|
|
167
|
-
const inputGrid = div({ class: "cell-input-grid" }, inPrompt, bodySlot, controlsEl);
|
|
168
|
-
const root = div({ class: "cell" }, inputGrid, outputGrid);
|
|
169
|
-
root.onClick(() => app.setActiveCell(cellData.id));
|
|
170
|
-
|
|
171
|
-
cellData._dom = root.element;
|
|
172
|
-
cellData._inPrompt = inPrompt.element;
|
|
173
|
-
cellData._outPrompt = outPrompt.element;
|
|
174
|
-
cellData._bodySlot = bodySlot.element;
|
|
175
|
-
cellData._outputGrid = outputGrid.element;
|
|
176
|
-
|
|
177
|
-
app._refreshCell(cellData);
|
|
178
|
-
return root.element;
|
|
179
|
-
}
|