@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/package.json
CHANGED
|
@@ -13,7 +13,7 @@ const languageFor = (type) =>
|
|
|
13
13
|
type === "markdown" ? markdown() : javascript();
|
|
14
14
|
|
|
15
15
|
export function createCodeEditor(app, cellData, actions) {
|
|
16
|
-
const editorDom = div({ class: "input-wrapper" })
|
|
16
|
+
const editorDom = div({ class: "input-wrapper" });
|
|
17
17
|
|
|
18
18
|
const languageCompartment = new Compartment();
|
|
19
19
|
const readOnlyCompartment = new Compartment();
|
|
@@ -143,7 +143,7 @@ export function createCodeEditor(app, cellData, actions) {
|
|
|
143
143
|
|
|
144
144
|
view = new EditorView({
|
|
145
145
|
state,
|
|
146
|
-
parent: editorDom
|
|
146
|
+
parent: editorDom.element
|
|
147
147
|
});
|
|
148
148
|
|
|
149
149
|
const lineHeight = view.defaultLineHeight || 19;
|
|
@@ -159,7 +159,7 @@ export function createCodeEditor(app, cellData, actions) {
|
|
|
159
159
|
};
|
|
160
160
|
|
|
161
161
|
return {
|
|
162
|
-
dom: editorDom,
|
|
162
|
+
dom: editorDom.element,
|
|
163
163
|
|
|
164
164
|
getValue: () =>
|
|
165
165
|
view
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createCodeEditor } from "../../code-editor/index.js";
|
|
2
|
+
import { tags } from 'ziko/dom'
|
|
3
|
+
import { useState } from 'ziko/hooks'
|
|
4
|
+
import {
|
|
5
|
+
Check,
|
|
6
|
+
Copy,
|
|
7
|
+
Lock,
|
|
8
|
+
Unlock,
|
|
9
|
+
Code2,
|
|
10
|
+
FileText,
|
|
11
|
+
Pencil,
|
|
12
|
+
Play,
|
|
13
|
+
Plus,
|
|
14
|
+
Trash2,
|
|
15
|
+
} from '../icons/index.js';
|
|
16
|
+
const { div, span } = tags;
|
|
17
|
+
|
|
18
|
+
import { Button } from "../Button/index.js";
|
|
19
|
+
|
|
20
|
+
export const Prompt = () => {
|
|
21
|
+
return span({ class: "prompt" })
|
|
22
|
+
}
|
|
23
|
+
|
|
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;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function CellControls({ cellData, store, actions }) {
|
|
57
|
+
const controlsContainer = div({ class: "cell-controls" }).element;
|
|
58
|
+
|
|
59
|
+
const renderButtons = () => {
|
|
60
|
+
const isQueueRunning = store.get('executionQueueState').isRunning;
|
|
61
|
+
const isMaxCellsReached = store.get('cells').length >= store.get('maxCells');
|
|
62
|
+
const isCopied = cellData._copiedFlag === true;
|
|
63
|
+
|
|
64
|
+
controlsContainer.replaceChildren(
|
|
65
|
+
Button({
|
|
66
|
+
class: `btn-icon ${isCopied ? "btn-active" : ""}`,
|
|
67
|
+
title: isCopied ? "Copied!" : "Copy Cell Content",
|
|
68
|
+
onclick: async () => {
|
|
69
|
+
await actions.copyCode();
|
|
70
|
+
cellData._copiedFlag = true;
|
|
71
|
+
renderButtons();
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
cellData._copiedFlag = false;
|
|
74
|
+
renderButtons();
|
|
75
|
+
}, 1500);
|
|
76
|
+
}
|
|
77
|
+
}, isCopied ? Check() : Copy()),
|
|
78
|
+
|
|
79
|
+
Button({
|
|
80
|
+
class: `btn-icon ${cellData.readonly ? "btn-active" : ""}`,
|
|
81
|
+
title: cellData.readonly ? "Make Editable" : "Make Read-Only",
|
|
82
|
+
disabled: isQueueRunning,
|
|
83
|
+
onclick: actions.toggleReadonly,
|
|
84
|
+
}, cellData.readonly ? Lock() : Unlock()),
|
|
85
|
+
|
|
86
|
+
Button({
|
|
87
|
+
class: `btn-icon ${cellData.type === "markdown" ? "btn-active" : ""}`,
|
|
88
|
+
title: `Switch to ${cellData.type === "code" ? "Markdown" : "JS Code"}`,
|
|
89
|
+
disabled: isQueueRunning || cellData.readonly,
|
|
90
|
+
onclick: actions.toggleType,
|
|
91
|
+
}, cellData.type === "code" ? FileText() : Code2()),
|
|
92
|
+
|
|
93
|
+
(cellData.type === "markdown" && !cellData.isEditingMarkdown)
|
|
94
|
+
? Button({
|
|
95
|
+
class: "btn-icon",
|
|
96
|
+
title: "Edit Markdown",
|
|
97
|
+
disabled: isQueueRunning || cellData.readonly,
|
|
98
|
+
onclick: actions.editMarkdown
|
|
99
|
+
}, Pencil())
|
|
100
|
+
: Button({
|
|
101
|
+
class: "btn-icon btn-primary",
|
|
102
|
+
title: "Run & Advance (Shift+Enter)",
|
|
103
|
+
disabled: isQueueRunning,
|
|
104
|
+
onclick: () => actions.runCode(true)
|
|
105
|
+
}, Play()),
|
|
106
|
+
|
|
107
|
+
Button({
|
|
108
|
+
class: "btn-icon",
|
|
109
|
+
title: "Add Cell Below",
|
|
110
|
+
disabled: isQueueRunning || isMaxCellsReached,
|
|
111
|
+
onclick: actions.addBelow
|
|
112
|
+
}, Plus()),
|
|
113
|
+
|
|
114
|
+
Button({
|
|
115
|
+
class: "btn-icon",
|
|
116
|
+
title: "Delete Cell",
|
|
117
|
+
disabled: isQueueRunning,
|
|
118
|
+
onclick: actions.deleteCell
|
|
119
|
+
}, Trash2())
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
renderButtons();
|
|
124
|
+
store.on('change', renderButtons);
|
|
125
|
+
|
|
126
|
+
return controlsContainer;
|
|
127
|
+
}
|
|
@@ -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
|
+
}
|