@zikojs/notebook 0.7.3 → 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/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.js → components/CellItem/index.js} +38 -90
- 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 +31 -12
- 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
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
transformImportsAndScope,
|
|
3
|
+
} from "./transform.js";
|
|
4
|
+
|
|
5
|
+
export const evaluateCodeAsync = async (
|
|
6
|
+
code,
|
|
7
|
+
TARGET,
|
|
8
|
+
{
|
|
9
|
+
importMap = {},
|
|
10
|
+
plugins = [],
|
|
11
|
+
} = {},
|
|
12
|
+
scope,
|
|
13
|
+
) => {
|
|
14
|
+
const validScope =
|
|
15
|
+
scope || Object.create(null);
|
|
16
|
+
|
|
17
|
+
const context = {
|
|
18
|
+
TARGET,
|
|
19
|
+
scope: validScope,
|
|
20
|
+
importMap,
|
|
21
|
+
plugins,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
let compiledCode = code;
|
|
25
|
+
|
|
26
|
+
for (const plugin of plugins) {
|
|
27
|
+
if (plugin.transform) {
|
|
28
|
+
compiledCode = await plugin.transform(
|
|
29
|
+
compiledCode,
|
|
30
|
+
context,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
compiledCode = transformImportsAndScope(
|
|
36
|
+
compiledCode,
|
|
37
|
+
importMap,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const runCell = new Function(
|
|
41
|
+
"TARGET",
|
|
42
|
+
"__scope__",
|
|
43
|
+
`
|
|
44
|
+
return (async () => {
|
|
45
|
+
with (__scope__) {
|
|
46
|
+
${compiledCode}
|
|
47
|
+
}
|
|
48
|
+
})();
|
|
49
|
+
`,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
let result = await runCell(
|
|
53
|
+
TARGET,
|
|
54
|
+
validScope,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
for (const plugin of plugins) {
|
|
58
|
+
if (plugin.afterEvaluate) {
|
|
59
|
+
result = await plugin.afterEvaluate(
|
|
60
|
+
result,
|
|
61
|
+
context,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return result;
|
|
67
|
+
};
|
|
@@ -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,65 +12,45 @@ import {
|
|
|
15
12
|
Play,
|
|
16
13
|
Plus,
|
|
17
14
|
Trash2,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Download,
|
|
21
|
-
Loader2
|
|
22
|
-
} from './icons.js';
|
|
15
|
+
} from '../icons/index.js';
|
|
16
|
+
const { div, span } = tags;
|
|
23
17
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
import { tags } from 'ziko/dom'
|
|
18
|
+
import { Button } from "../Button/index.js";
|
|
27
19
|
|
|
28
|
-
const
|
|
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
|
-
});
|
|
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;
|
|
20
|
+
export const Prompt = () => {
|
|
21
|
+
return span({ class: "prompt" })
|
|
57
22
|
}
|
|
58
23
|
|
|
59
|
-
export function
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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);
|
|
32
|
+
/* Check ziko */
|
|
33
|
+
const [In, setIn] = useState(0)
|
|
34
|
+
globalThis.setIn = setIn
|
|
35
|
+
const inPrompt = span({ class: "prompt" }, In);
|
|
36
|
+
const outPrompt = span({ class: "prompt out" });
|
|
37
|
+
const bodySlot = div().style({ display : 'contents'});
|
|
38
|
+
const controlsEl = CellControls({ cellData, store, actions });
|
|
39
|
+
|
|
40
|
+
const outputGrid = div({ class: "cell-output-grid" }, outPrompt, div({ class: "output-wrapper" }, outputRef));
|
|
41
|
+
const inputGrid = div({ class: "cell-input-grid" }, inPrompt, bodySlot, controlsEl);
|
|
42
|
+
const root = div({ class: "cell" }, inputGrid, outputGrid);
|
|
43
|
+
root.onClick(() => app.setActiveCell(cellData.id));
|
|
44
|
+
|
|
45
|
+
cellData._dom = root.element;
|
|
46
|
+
cellData._inPrompt = inPrompt.element;
|
|
47
|
+
cellData._outPrompt = outPrompt.element;
|
|
48
|
+
cellData._bodySlot = bodySlot.element;
|
|
49
|
+
cellData._outputGrid = outputGrid.element;
|
|
50
|
+
cellData._setIn = setIn
|
|
51
|
+
|
|
52
|
+
app._refreshCell(cellData);
|
|
53
|
+
return root.element;
|
|
77
54
|
}
|
|
78
55
|
|
|
79
56
|
export function CellControls({ cellData, store, actions }) {
|
|
@@ -148,32 +125,3 @@ export function CellControls({ cellData, store, actions }) {
|
|
|
148
125
|
|
|
149
126
|
return controlsContainer;
|
|
150
127
|
}
|
|
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
|
-
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/* --- HEADER & TOOLBAR --- */
|
|
2
|
+
|
|
3
|
+
.ziko-notebook .header {
|
|
4
|
+
border-bottom: 1px solid var(--border-color);
|
|
5
|
+
background: #ffffff;
|
|
6
|
+
padding: 10px 16px;
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
justify-content: space-between;
|
|
10
|
+
gap: 12px;
|
|
11
|
+
flex-wrap: wrap;
|
|
12
|
+
position: sticky;
|
|
13
|
+
top: 0;
|
|
14
|
+
z-index: 100;
|
|
15
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
|
16
|
+
width: 100%;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.ziko-notebook .brand {
|
|
20
|
+
font-weight: 600;
|
|
21
|
+
font-size: 16px;
|
|
22
|
+
color: #0f172a;
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: 12px;
|
|
26
|
+
flex-shrink: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.ziko-notebook .toolbar {
|
|
30
|
+
display: flex;
|
|
31
|
+
gap: 6px;
|
|
32
|
+
align-items: center;
|
|
33
|
+
flex-wrap: wrap;
|
|
34
|
+
max-width: 100%;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.ziko-notebook .status-badge {
|
|
38
|
+
font-size: 12px;
|
|
39
|
+
font-weight: 500;
|
|
40
|
+
color: var(--primary);
|
|
41
|
+
background: #e0f2fe;
|
|
42
|
+
padding: 4px 8px;
|
|
43
|
+
border-radius: 4px;
|
|
44
|
+
display: inline-flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
gap: 6px;
|
|
47
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import van from "vanjs-core";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
FileText,
|
|
5
|
+
Plus,
|
|
6
|
+
Trash2,
|
|
7
|
+
PlayForward,
|
|
8
|
+
Eraser,
|
|
9
|
+
Download,
|
|
10
|
+
Loader2
|
|
11
|
+
} from '../icons/index.js';
|
|
12
|
+
|
|
13
|
+
const { button } = van.tags;
|
|
14
|
+
|
|
15
|
+
import { tags } from 'ziko/dom'
|
|
16
|
+
|
|
17
|
+
const { div, span } = tags
|
|
18
|
+
|
|
19
|
+
export const Button = ({ class: className = "", title = "", disabled = false, onclick }, ...children) =>
|
|
20
|
+
button({ class: `btn ${className}`, title, disabled, onclick }, ...children);
|
|
21
|
+
|
|
22
|
+
export function Header({ store, actions }) {
|
|
23
|
+
const statusBadge = span({ class: "status-badge" }, Loader2(), span('Loading ...'));
|
|
24
|
+
const runAllBtn = Button({ class: "btn-primary", onclick: actions.runAll }, PlayForward(), "Run All");
|
|
25
|
+
const clearBtn = Button({ onclick: actions.clearOutputs }, Eraser(), "Clear Outputs");
|
|
26
|
+
const addCodeBtn = Button({ onclick: actions.addCode }, Plus(), "Add Code");
|
|
27
|
+
const addMdBtn = Button({ onclick: actions.addMarkdown }, FileText(), "Add Markdown");
|
|
28
|
+
const deleteActiveBtn = Button({ onclick: actions.deleteActive }, Trash2(), "Delete Active");
|
|
29
|
+
const exportBtn = Button({ onclick: actions.exportData }, Download(), "Export Data");
|
|
30
|
+
|
|
31
|
+
store.on('change:executionQueueState', (q) => {
|
|
32
|
+
statusBadge.style({display :q.isRunning ? "inline-flex" : "none" })
|
|
33
|
+
statusBadge.element.lastChild.textContent = `Executing Queue (${q.currentIndex + 1}/${q.total})`;
|
|
34
|
+
[runAllBtn, clearBtn, deleteActiveBtn].forEach(b => { b.disabled = q.isRunning; });
|
|
35
|
+
const maxReached = store.get('cells').length >= store.get('maxCells');
|
|
36
|
+
addCodeBtn.disabled = q.isRunning || maxReached;
|
|
37
|
+
addMdBtn.disabled = q.isRunning || maxReached;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return div({ class: "header" },
|
|
41
|
+
div({ class: "brand" }, "</>", statusBadge),
|
|
42
|
+
div({ class: "toolbar" },
|
|
43
|
+
runAllBtn, clearBtn, addCodeBtn, addMdBtn, deleteActiveBtn, exportBtn
|
|
44
|
+
)
|
|
45
|
+
).element;
|
|
46
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* --- MARKDOWN --- */
|
|
2
|
+
|
|
3
|
+
.ziko-notebook .markdown-rendered-cell {
|
|
4
|
+
grid-column: 1 / 3;
|
|
5
|
+
padding: 8px 12px;
|
|
6
|
+
cursor: pointer;
|
|
7
|
+
min-height: 38px;
|
|
8
|
+
border: 1px dashed transparent;
|
|
9
|
+
border-radius: 4px;
|
|
10
|
+
transition: background 0.15s ease, border-color 0.15s ease;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.ziko-notebook .markdown-rendered-cell:hover {
|
|
14
|
+
background: #f8fafc;
|
|
15
|
+
border-color: #cbd5e1;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.ziko-notebook .cell.active .markdown-rendered-cell {
|
|
19
|
+
border-color: #94a3b8;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.ziko-notebook .markdown-body {
|
|
23
|
+
line-height: 1.6;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.ziko-notebook .markdown-body h1,
|
|
27
|
+
.ziko-notebook .markdown-body h2,
|
|
28
|
+
.ziko-notebook .markdown-body h3 {
|
|
29
|
+
margin-top: 8px;
|
|
30
|
+
margin-bottom: 8px;
|
|
31
|
+
font-weight: 600;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.ziko-notebook .markdown-body h1 {
|
|
35
|
+
font-size: 1.5em;
|
|
36
|
+
border-bottom: 1px solid var(--border-color);
|
|
37
|
+
padding-bottom: 4px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.ziko-notebook .markdown-body h2 {
|
|
41
|
+
font-size: 1.25em;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.ziko-notebook .markdown-body h3 {
|
|
45
|
+
font-size: 1.1em;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.ziko-notebook .markdown-body p {
|
|
49
|
+
margin-bottom: 8px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.ziko-notebook .markdown-body p:last-child {
|
|
53
|
+
margin-bottom: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.ziko-notebook .markdown-body ul,
|
|
57
|
+
.ziko-notebook .markdown-body ol {
|
|
58
|
+
padding-left: 20px;
|
|
59
|
+
margin-bottom: 8px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.ziko-notebook .markdown-body code {
|
|
63
|
+
background: #f1f5f9;
|
|
64
|
+
padding: 2px 4px;
|
|
65
|
+
border-radius: 4px;
|
|
66
|
+
font-family: ui-monospace, SFMono-Regular, monospace;
|
|
67
|
+
font-size: 0.9em;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.ziko-notebook .markdown-body pre {
|
|
71
|
+
background: #1e293b;
|
|
72
|
+
color: #f8fafc;
|
|
73
|
+
padding: 10px;
|
|
74
|
+
border-radius: 6px;
|
|
75
|
+
overflow-x: auto;
|
|
76
|
+
margin: 8px 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.ziko-notebook .markdown-body pre code {
|
|
80
|
+
background: transparent;
|
|
81
|
+
color: inherit;
|
|
82
|
+
padding: 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.ziko-notebook .markdown-body blockquote {
|
|
86
|
+
border-left: 4px solid var(--primary);
|
|
87
|
+
padding-left: 10px;
|
|
88
|
+
color: #64748b;
|
|
89
|
+
margin: 8px 0;
|
|
90
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { remark } from "remark";
|
|
2
|
+
import remarkRehype from "remark-rehype";
|
|
3
|
+
import rehypeStringify from "rehype-stringify";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import { tags } from 'ziko/dom'
|
|
7
|
+
|
|
8
|
+
const { div } = tags
|
|
9
|
+
|
|
10
|
+
export function MarkdownView({ cellData, actions }) {
|
|
11
|
+
const content = div();
|
|
12
|
+
|
|
13
|
+
// Process Markdown asynchronously using Remark + Rehype
|
|
14
|
+
remark()
|
|
15
|
+
.use(remarkRehype, { allowDangerousHtml: true })
|
|
16
|
+
.use(rehypeStringify)
|
|
17
|
+
.process(cellData.code || "*Empty Markdown Cell*")
|
|
18
|
+
.then((file) => {
|
|
19
|
+
content.element.innerHTML = String(file);
|
|
20
|
+
})
|
|
21
|
+
.catch((err) => {
|
|
22
|
+
content.element.innerHTML = `<div class="error-output">Markdown rendering error: ${err.message}</div>`;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const wrap = div({ class: "markdown-rendered-cell markdown-body" }, content);
|
|
26
|
+
wrap.onClick(() => actions.editMarkdown())
|
|
27
|
+
return wrap.element;
|
|
28
|
+
}
|
package/src/index.css
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@import url('./components/index.css');
|
|
2
|
+
|
|
1
3
|
.ziko-notebook {
|
|
2
4
|
--primary: #0284c7;
|
|
3
5
|
--primary-hover: #0369a1;
|
|
@@ -10,53 +12,6 @@
|
|
|
10
12
|
color: #1e293b;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
/* --- HEADER & TOOLBAR --- */
|
|
14
|
-
|
|
15
|
-
.ziko-notebook .header {
|
|
16
|
-
border-bottom: 1px solid var(--border-color);
|
|
17
|
-
background: #ffffff;
|
|
18
|
-
padding: 10px 16px;
|
|
19
|
-
display: flex;
|
|
20
|
-
align-items: center;
|
|
21
|
-
justify-content: space-between;
|
|
22
|
-
gap: 12px;
|
|
23
|
-
flex-wrap: wrap;
|
|
24
|
-
position: sticky;
|
|
25
|
-
top: 0;
|
|
26
|
-
z-index: 100;
|
|
27
|
-
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
|
28
|
-
width: 100%;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.ziko-notebook .brand {
|
|
32
|
-
font-weight: 600;
|
|
33
|
-
font-size: 16px;
|
|
34
|
-
color: #0f172a;
|
|
35
|
-
display: flex;
|
|
36
|
-
align-items: center;
|
|
37
|
-
gap: 12px;
|
|
38
|
-
flex-shrink: 0;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.ziko-notebook .toolbar {
|
|
42
|
-
display: flex;
|
|
43
|
-
gap: 6px;
|
|
44
|
-
align-items: center;
|
|
45
|
-
flex-wrap: wrap;
|
|
46
|
-
max-width: 100%;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.ziko-notebook .status-badge {
|
|
50
|
-
font-size: 12px;
|
|
51
|
-
font-weight: 500;
|
|
52
|
-
color: var(--primary);
|
|
53
|
-
background: #e0f2fe;
|
|
54
|
-
padding: 4px 8px;
|
|
55
|
-
border-radius: 4px;
|
|
56
|
-
display: inline-flex;
|
|
57
|
-
align-items: center;
|
|
58
|
-
gap: 6px;
|
|
59
|
-
}
|
|
60
15
|
|
|
61
16
|
/* --- NOTEBOOK & CELLS --- */
|
|
62
17
|
|
|
@@ -133,12 +88,6 @@
|
|
|
133
88
|
border-color: #cbd5e1;
|
|
134
89
|
}
|
|
135
90
|
|
|
136
|
-
/*
|
|
137
|
-
* CodeMirror is an external component.
|
|
138
|
-
*
|
|
139
|
-
* Don't globally style .CodeMirror.
|
|
140
|
-
* Scope only the CodeMirror instances owned by the notebook.
|
|
141
|
-
*/
|
|
142
91
|
.ziko-notebook .input-wrapper .CodeMirror {
|
|
143
92
|
font-family: "Fira Code", SFMono-Regular, Consolas, monospace;
|
|
144
93
|
font-size: 13px;
|
|
@@ -162,7 +111,11 @@
|
|
|
162
111
|
gap: 4px;
|
|
163
112
|
flex-shrink: 0;
|
|
164
113
|
flex-wrap: wrap;
|
|
165
|
-
max-width:
|
|
114
|
+
max-width: 60px;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.ziko-notebook .cm-line{
|
|
118
|
+
line-height: 1.3 !important;
|
|
166
119
|
}
|
|
167
120
|
|
|
168
121
|
/* --- OUTPUT --- */
|
|
@@ -177,163 +130,7 @@
|
|
|
177
130
|
word-break: break-word;
|
|
178
131
|
}
|
|
179
132
|
|
|
180
|
-
.ziko-notebook .error-output {
|
|
181
|
-
color: #e11d48;
|
|
182
|
-
font-family: ui-monospace, SFMono-Regular, monospace;
|
|
183
|
-
white-space: pre-wrap;
|
|
184
|
-
font-size: 12px;
|
|
185
|
-
background: #fff1f2;
|
|
186
|
-
padding: 8px;
|
|
187
|
-
border-radius: 4px;
|
|
188
|
-
border: 1px solid #fecdd3;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/* --- MARKDOWN --- */
|
|
192
|
-
|
|
193
|
-
.ziko-notebook .markdown-rendered-cell {
|
|
194
|
-
grid-column: 1 / 3;
|
|
195
|
-
padding: 8px 12px;
|
|
196
|
-
cursor: pointer;
|
|
197
|
-
min-height: 38px;
|
|
198
|
-
border: 1px dashed transparent;
|
|
199
|
-
border-radius: 4px;
|
|
200
|
-
transition: background 0.15s ease, border-color 0.15s ease;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
.ziko-notebook .markdown-rendered-cell:hover {
|
|
204
|
-
background: #f8fafc;
|
|
205
|
-
border-color: #cbd5e1;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.ziko-notebook .cell.active .markdown-rendered-cell {
|
|
209
|
-
border-color: #94a3b8;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
.ziko-notebook .markdown-body {
|
|
213
|
-
line-height: 1.6;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
.ziko-notebook .markdown-body h1,
|
|
217
|
-
.ziko-notebook .markdown-body h2,
|
|
218
|
-
.ziko-notebook .markdown-body h3 {
|
|
219
|
-
margin-top: 8px;
|
|
220
|
-
margin-bottom: 8px;
|
|
221
|
-
font-weight: 600;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
.ziko-notebook .markdown-body h1 {
|
|
225
|
-
font-size: 1.5em;
|
|
226
|
-
border-bottom: 1px solid var(--border-color);
|
|
227
|
-
padding-bottom: 4px;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
.ziko-notebook .markdown-body h2 {
|
|
231
|
-
font-size: 1.25em;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
.ziko-notebook .markdown-body h3 {
|
|
235
|
-
font-size: 1.1em;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
.ziko-notebook .markdown-body p {
|
|
239
|
-
margin-bottom: 8px;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
.ziko-notebook .markdown-body p:last-child {
|
|
243
|
-
margin-bottom: 0;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
.ziko-notebook .markdown-body ul,
|
|
247
|
-
.ziko-notebook .markdown-body ol {
|
|
248
|
-
padding-left: 20px;
|
|
249
|
-
margin-bottom: 8px;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.ziko-notebook .markdown-body code {
|
|
253
|
-
background: #f1f5f9;
|
|
254
|
-
padding: 2px 4px;
|
|
255
|
-
border-radius: 4px;
|
|
256
|
-
font-family: ui-monospace, SFMono-Regular, monospace;
|
|
257
|
-
font-size: 0.9em;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
.ziko-notebook .markdown-body pre {
|
|
261
|
-
background: #1e293b;
|
|
262
|
-
color: #f8fafc;
|
|
263
|
-
padding: 10px;
|
|
264
|
-
border-radius: 6px;
|
|
265
|
-
overflow-x: auto;
|
|
266
|
-
margin: 8px 0;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
.ziko-notebook .markdown-body pre code {
|
|
270
|
-
background: transparent;
|
|
271
|
-
color: inherit;
|
|
272
|
-
padding: 0;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
.ziko-notebook .markdown-body blockquote {
|
|
276
|
-
border-left: 4px solid var(--primary);
|
|
277
|
-
padding-left: 10px;
|
|
278
|
-
color: #64748b;
|
|
279
|
-
margin: 8px 0;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/* --- BUTTONS --- */
|
|
283
133
|
|
|
284
|
-
.ziko-notebook .btn {
|
|
285
|
-
display: inline-flex;
|
|
286
|
-
align-items: center;
|
|
287
|
-
justify-content: center;
|
|
288
|
-
gap: 6px;
|
|
289
|
-
background: #ffffff;
|
|
290
|
-
border: 1px solid var(--border-color);
|
|
291
|
-
padding: 6px 10px;
|
|
292
|
-
border-radius: 4px;
|
|
293
|
-
font-size: 12px;
|
|
294
|
-
font-weight: 500;
|
|
295
|
-
color: #334155;
|
|
296
|
-
cursor: pointer;
|
|
297
|
-
line-height: 1;
|
|
298
|
-
white-space: nowrap;
|
|
299
|
-
transition: background 0.12s ease, border-color 0.12s ease;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
.ziko-notebook .btn:hover {
|
|
303
|
-
background: #f1f5f9;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
.ziko-notebook .btn:disabled {
|
|
307
|
-
opacity: 0.5;
|
|
308
|
-
cursor: not-allowed;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
.ziko-notebook .btn-icon {
|
|
312
|
-
padding: 6px;
|
|
313
|
-
border-radius: 4px;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
.ziko-notebook .btn-primary {
|
|
317
|
-
background: var(--primary);
|
|
318
|
-
color: #ffffff;
|
|
319
|
-
border-color: var(--primary);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
.ziko-notebook .btn-primary:hover {
|
|
323
|
-
background: var(--primary-hover);
|
|
324
|
-
border-color: var(--primary-hover);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
.ziko-notebook .btn-active {
|
|
328
|
-
background: #e2e8f0;
|
|
329
|
-
border-color: #cbd5e1;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
.ziko-notebook .icon {
|
|
333
|
-
width: 14px;
|
|
334
|
-
height: 14px;
|
|
335
|
-
stroke-width: 2;
|
|
336
|
-
}
|
|
337
134
|
|
|
338
135
|
/* --- MOBILE --- */
|
|
339
136
|
|
package/src/notebook.js
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
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 = [],
|
|
@@ -14,7 +25,8 @@ export class UINotebook extends UIElement{
|
|
|
14
25
|
codeMirrorPlugins = [],
|
|
15
26
|
minLines = 3,
|
|
16
27
|
maxCells = Infinity,
|
|
17
|
-
showHeader = true
|
|
28
|
+
showHeader = true,
|
|
29
|
+
runtimePlugins = []
|
|
18
30
|
} = {}) {
|
|
19
31
|
super({ element : 'div'})
|
|
20
32
|
this.setAttr({class : 'ziko-notebook'})
|
|
@@ -26,6 +38,7 @@ export class UINotebook extends UIElement{
|
|
|
26
38
|
this.resetScope();
|
|
27
39
|
|
|
28
40
|
this.importMap = importMap;
|
|
41
|
+
this.runtimePlugins = runtimePlugins;
|
|
29
42
|
this.codeMirrorConfig = codeMirrorConfig;
|
|
30
43
|
this.codeMirrorPlugins = codeMirrorPlugins;
|
|
31
44
|
this.minLines = minLines;
|
|
@@ -123,10 +136,18 @@ export class UINotebook extends UIElement{
|
|
|
123
136
|
|
|
124
137
|
try {
|
|
125
138
|
const scope = app.getScope();
|
|
126
|
-
await evaluateCodeAsync(
|
|
139
|
+
await evaluateCodeAsync(
|
|
140
|
+
code,
|
|
141
|
+
cellData._outputRef,
|
|
142
|
+
{
|
|
143
|
+
importMap: app.importMap,
|
|
144
|
+
plugins: app.runtimePlugins,
|
|
145
|
+
},
|
|
146
|
+
scope,
|
|
147
|
+
);
|
|
127
148
|
} catch (err) {
|
|
128
149
|
hasError = true;
|
|
129
|
-
|
|
150
|
+
ErrorOutput(err).mount(cellData._outputRef)
|
|
130
151
|
}
|
|
131
152
|
|
|
132
153
|
const counter = this.store.get('executionCounter');
|
|
@@ -140,19 +161,17 @@ export class UINotebook extends UIElement{
|
|
|
140
161
|
cellData._outputRef.appendChild(container);
|
|
141
162
|
|
|
142
163
|
if (autoNext && !hasError) app.nextCell(cellData.id, "code");
|
|
143
|
-
|
|
164
|
+
app._refreshCell(cellData);
|
|
144
165
|
},
|
|
145
166
|
copyCode: async () => {
|
|
146
167
|
const textToCopy = cellData._editor.getValue() || cellData.code;
|
|
147
168
|
try {
|
|
148
169
|
await navigator.clipboard.writeText(textToCopy);
|
|
149
170
|
} catch (err) {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
document.body.appendChild(textArea);
|
|
153
|
-
textArea.select();
|
|
171
|
+
const temporary = tags.textarea(textToCopy).mount(document.body)
|
|
172
|
+
temporary.element.select()
|
|
154
173
|
document.execCommand("copy");
|
|
155
|
-
|
|
174
|
+
temporary.unmount()
|
|
156
175
|
}
|
|
157
176
|
},
|
|
158
177
|
toggleReadonly: () => {
|
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
|