@zikojs/notebook 0.8.0 → 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 +37 -7
- package/src/components/CellItem/index.js +53 -9
- package/src/components/Header/index.js +13 -13
- package/src/components/MarkdownView/index.js +116 -11
- package/src/components/index.css +18 -1
- package/src/index.css +1 -1
- package/src/notebook.js +47 -16
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"
|
package/src/compiler/evaluate.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
transformImportsAndScope,
|
|
3
|
-
} from "./transform.js";
|
|
1
|
+
import { transformImportsAndScope } from "./transform.js";
|
|
4
2
|
|
|
5
3
|
export const evaluateCodeAsync = async (
|
|
6
4
|
code,
|
|
@@ -8,6 +6,7 @@ export const evaluateCodeAsync = async (
|
|
|
8
6
|
{
|
|
9
7
|
importMap = {},
|
|
10
8
|
plugins = [],
|
|
9
|
+
runtime = null,
|
|
11
10
|
} = {},
|
|
12
11
|
scope,
|
|
13
12
|
) => {
|
|
@@ -19,11 +18,34 @@ export const evaluateCodeAsync = async (
|
|
|
19
18
|
scope: validScope,
|
|
20
19
|
importMap,
|
|
21
20
|
plugins,
|
|
21
|
+
runtime,
|
|
22
22
|
};
|
|
23
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
|
+
|
|
24
39
|
let compiledCode = code;
|
|
25
40
|
|
|
26
41
|
for (const plugin of plugins) {
|
|
42
|
+
if (
|
|
43
|
+
selectedPlugin &&
|
|
44
|
+
plugin.name !== runtime
|
|
45
|
+
) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
27
49
|
if (plugin.transform) {
|
|
28
50
|
compiledCode = await plugin.transform(
|
|
29
51
|
compiledCode,
|
|
@@ -32,10 +54,11 @@ export const evaluateCodeAsync = async (
|
|
|
32
54
|
}
|
|
33
55
|
}
|
|
34
56
|
|
|
35
|
-
compiledCode =
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
57
|
+
compiledCode =
|
|
58
|
+
transformImportsAndScope(
|
|
59
|
+
compiledCode,
|
|
60
|
+
importMap,
|
|
61
|
+
);
|
|
39
62
|
|
|
40
63
|
const runCell = new Function(
|
|
41
64
|
"TARGET",
|
|
@@ -55,6 +78,13 @@ export const evaluateCodeAsync = async (
|
|
|
55
78
|
);
|
|
56
79
|
|
|
57
80
|
for (const plugin of plugins) {
|
|
81
|
+
if (
|
|
82
|
+
selectedPlugin &&
|
|
83
|
+
plugin.name !== runtime
|
|
84
|
+
) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
58
88
|
if (plugin.afterEvaluate) {
|
|
59
89
|
result = await plugin.afterEvaluate(
|
|
60
90
|
result,
|
|
@@ -29,13 +29,21 @@ export function CellItem({ cellData, app, store }) {
|
|
|
29
29
|
const actions = app._cellActions(cellData);
|
|
30
30
|
cellData._actions = actions;
|
|
31
31
|
cellData._editor = createCodeEditor(app, cellData, actions);
|
|
32
|
+
|
|
32
33
|
/* Check ziko */
|
|
33
|
-
const [In, setIn] = useState(0)
|
|
34
|
-
globalThis.setIn = setIn
|
|
34
|
+
const [In, setIn] = useState(0);
|
|
35
|
+
globalThis.setIn = setIn;
|
|
35
36
|
const inPrompt = span({ class: "prompt" }, In);
|
|
36
37
|
const outPrompt = span({ class: "prompt out" });
|
|
37
|
-
const bodySlot = div().style({ display
|
|
38
|
-
|
|
38
|
+
const bodySlot = div().style({ display: 'contents' });
|
|
39
|
+
|
|
40
|
+
// Pass runtimePlugins from app into CellControls
|
|
41
|
+
const controlsEl = CellControls({
|
|
42
|
+
cellData,
|
|
43
|
+
store,
|
|
44
|
+
actions,
|
|
45
|
+
runtimePlugins: app.runtimePlugins
|
|
46
|
+
});
|
|
39
47
|
|
|
40
48
|
const outputGrid = div({ class: "cell-output-grid" }, outPrompt, div({ class: "output-wrapper" }, outputRef));
|
|
41
49
|
const inputGrid = div({ class: "cell-input-grid" }, inPrompt, bodySlot, controlsEl);
|
|
@@ -47,13 +55,12 @@ export function CellItem({ cellData, app, store }) {
|
|
|
47
55
|
cellData._outPrompt = outPrompt.element;
|
|
48
56
|
cellData._bodySlot = bodySlot.element;
|
|
49
57
|
cellData._outputGrid = outputGrid.element;
|
|
50
|
-
cellData._setIn = setIn
|
|
58
|
+
cellData._setIn = setIn;
|
|
51
59
|
|
|
52
60
|
app._refreshCell(cellData);
|
|
53
61
|
return root.element;
|
|
54
62
|
}
|
|
55
|
-
|
|
56
|
-
export function CellControls({ cellData, store, actions }) {
|
|
63
|
+
export function CellControls({ cellData, store, actions, runtimePlugins = [] }) {
|
|
57
64
|
const controlsContainer = div({ class: "cell-controls" }).element;
|
|
58
65
|
|
|
59
66
|
const renderButtons = () => {
|
|
@@ -61,7 +68,38 @@ export function CellControls({ cellData, store, actions }) {
|
|
|
61
68
|
const isMaxCellsReached = store.get('cells').length >= store.get('maxCells');
|
|
62
69
|
const isCopied = cellData._copiedFlag === true;
|
|
63
70
|
|
|
64
|
-
|
|
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 = [
|
|
65
103
|
Button({
|
|
66
104
|
class: `btn-icon ${isCopied ? "btn-active" : ""}`,
|
|
67
105
|
title: isCopied ? "Copied!" : "Copy Cell Content",
|
|
@@ -117,7 +155,13 @@ export function CellControls({ cellData, store, actions }) {
|
|
|
117
155
|
disabled: isQueueRunning,
|
|
118
156
|
onclick: actions.deleteCell
|
|
119
157
|
}, Trash2())
|
|
120
|
-
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
if (runtimeSelectEl) {
|
|
161
|
+
children.unshift(runtimeSelectEl);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
controlsContainer.replaceChildren(...children);
|
|
121
165
|
};
|
|
122
166
|
|
|
123
167
|
renderButtons();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import van from
|
|
1
|
+
import van from 'vanjs-core';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
FileText,
|
|
@@ -16,20 +16,20 @@ import { tags } from 'ziko/dom'
|
|
|
16
16
|
|
|
17
17
|
const { div, span } = tags
|
|
18
18
|
|
|
19
|
-
export const Button = ({ class: className =
|
|
19
|
+
export const Button = ({ class: className = '', title = '', disabled = false, onclick }, ...children) =>
|
|
20
20
|
button({ class: `btn ${className}`, title, disabled, onclick }, ...children);
|
|
21
21
|
|
|
22
22
|
export function Header({ store, actions }) {
|
|
23
|
-
const statusBadge = span({ class:
|
|
24
|
-
const runAllBtn = Button({ class:
|
|
25
|
-
const clearBtn = Button({ onclick: actions.clearOutputs }, Eraser()
|
|
26
|
-
const addCodeBtn = Button({ onclick: actions.addCode }, Plus()
|
|
27
|
-
const addMdBtn = Button({ onclick: actions.addMarkdown }, FileText()
|
|
28
|
-
const deleteActiveBtn = Button({ onclick: actions.deleteActive }, Trash2()
|
|
29
|
-
const exportBtn = Button({ onclick: actions.exportData }, Download()
|
|
23
|
+
const statusBadge = span({ class: 'status-badge' }, Loader2(), span('Loading ...'));
|
|
24
|
+
const runAllBtn = Button({ class: 'btn-primary', onclick: actions.runAll, title : 'Run All' }, PlayForward());
|
|
25
|
+
const clearBtn = Button({ onclick: actions.clearOutputs, title : 'Clear Outputs' }, Eraser());
|
|
26
|
+
const addCodeBtn = Button({ onclick: actions.addCode, title : 'Add Code'}, Plus());
|
|
27
|
+
const addMdBtn = Button({ onclick: actions.addMarkdown, title : 'Add Markdown' }, FileText());
|
|
28
|
+
const deleteActiveBtn = Button({ onclick: actions.deleteActive, title : 'Delete Active cell' }, Trash2());
|
|
29
|
+
const exportBtn = Button({ onclick: actions.exportData, title : 'Export Data' }, Download());
|
|
30
30
|
|
|
31
31
|
store.on('change:executionQueueState', (q) => {
|
|
32
|
-
statusBadge.style({display :q.isRunning ?
|
|
32
|
+
statusBadge.style({display :q.isRunning ? 'inline-flex' : 'none' })
|
|
33
33
|
statusBadge.element.lastChild.textContent = `Executing Queue (${q.currentIndex + 1}/${q.total})`;
|
|
34
34
|
[runAllBtn, clearBtn, deleteActiveBtn].forEach(b => { b.disabled = q.isRunning; });
|
|
35
35
|
const maxReached = store.get('cells').length >= store.get('maxCells');
|
|
@@ -37,9 +37,9 @@ export function Header({ store, actions }) {
|
|
|
37
37
|
addMdBtn.disabled = q.isRunning || maxReached;
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
return div({ class:
|
|
41
|
-
div({ class:
|
|
42
|
-
div({ class:
|
|
40
|
+
return div({ class: 'header' },
|
|
41
|
+
// div({ class: 'brand' }, '</>', statusBadge),
|
|
42
|
+
div({ class: 'toolbar' },
|
|
43
43
|
runAllBtn, clearBtn, addCodeBtn, addMdBtn, deleteActiveBtn, exportBtn
|
|
44
44
|
)
|
|
45
45
|
).element;
|
|
@@ -2,27 +2,132 @@ import { remark } from "remark";
|
|
|
2
2
|
import remarkRehype from "remark-rehype";
|
|
3
3
|
import rehypeStringify from "rehype-stringify";
|
|
4
4
|
|
|
5
|
+
import { tags } from "ziko/dom";
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
const { div } = tags;
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
export function MarkdownView({
|
|
10
|
+
cellData,
|
|
11
|
+
actions,
|
|
12
|
+
remarkPlugins = [],
|
|
13
|
+
rehypePlugins = [],
|
|
14
|
+
}) {
|
|
11
15
|
const content = div();
|
|
12
|
-
|
|
13
|
-
// Process Markdown asynchronously using Remark + Rehype
|
|
16
|
+
|
|
14
17
|
remark()
|
|
18
|
+
.use(remarkPlugins)
|
|
15
19
|
.use(remarkRehype, { allowDangerousHtml: true })
|
|
20
|
+
.use(rehypePlugins)
|
|
16
21
|
.use(rehypeStringify)
|
|
17
22
|
.process(cellData.code || "*Empty Markdown Cell*")
|
|
18
23
|
.then((file) => {
|
|
19
24
|
content.element.innerHTML = String(file);
|
|
20
25
|
})
|
|
21
26
|
.catch((err) => {
|
|
22
|
-
content.element.innerHTML =
|
|
27
|
+
content.element.innerHTML =
|
|
28
|
+
`<div class="error-output">Markdown rendering error: ${err.message}</div>`;
|
|
23
29
|
});
|
|
24
|
-
|
|
25
|
-
const wrap = div(
|
|
26
|
-
|
|
30
|
+
|
|
31
|
+
const wrap = div(
|
|
32
|
+
{ class: "markdown-rendered-cell markdown-body" },
|
|
33
|
+
content
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
wrap.onClick(() => actions.editMarkdown());
|
|
37
|
+
|
|
27
38
|
return wrap.element;
|
|
28
|
-
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
// import { remark } from "remark";
|
|
44
|
+
// import remarkRehype from "remark-rehype";
|
|
45
|
+
// import rehypeStringify from "rehype-stringify";
|
|
46
|
+
|
|
47
|
+
// import { tags } from "ziko/dom";
|
|
48
|
+
|
|
49
|
+
// const { div } = tags;
|
|
50
|
+
|
|
51
|
+
// /**
|
|
52
|
+
// * Activate elements inserted through innerHTML.
|
|
53
|
+
// *
|
|
54
|
+
// * Scripts created by innerHTML are inert, so recreate them
|
|
55
|
+
// * as real DOM nodes. This also handles external scripts.
|
|
56
|
+
// */
|
|
57
|
+
// async function activateHTML(container) {
|
|
58
|
+
// const scripts = [
|
|
59
|
+
// ...container.querySelectorAll("script"),
|
|
60
|
+
// ];
|
|
61
|
+
|
|
62
|
+
// for (const oldScript of scripts) {
|
|
63
|
+
// const script = document.createElement("script");
|
|
64
|
+
|
|
65
|
+
// for (const attribute of oldScript.attributes) {
|
|
66
|
+
// script.setAttribute(
|
|
67
|
+
// attribute.name,
|
|
68
|
+
// attribute.value
|
|
69
|
+
// );
|
|
70
|
+
// }
|
|
71
|
+
|
|
72
|
+
// script.textContent = oldScript.textContent;
|
|
73
|
+
|
|
74
|
+
// oldScript.replaceWith(script);
|
|
75
|
+
|
|
76
|
+
// if (script.src) {
|
|
77
|
+
// await new Promise((resolve, reject) => {
|
|
78
|
+
// script.addEventListener(
|
|
79
|
+
// "load",
|
|
80
|
+
// resolve,
|
|
81
|
+
// { once: true }
|
|
82
|
+
// );
|
|
83
|
+
|
|
84
|
+
// script.addEventListener(
|
|
85
|
+
// "error",
|
|
86
|
+
// reject,
|
|
87
|
+
// { once: true }
|
|
88
|
+
// );
|
|
89
|
+
// });
|
|
90
|
+
// }
|
|
91
|
+
// }
|
|
92
|
+
// }
|
|
93
|
+
|
|
94
|
+
// export function MarkdownView({
|
|
95
|
+
// cellData,
|
|
96
|
+
// actions,
|
|
97
|
+
// remarkPlugins = [],
|
|
98
|
+
// rehypePlugins = [],
|
|
99
|
+
// }) {
|
|
100
|
+
// const content = div();
|
|
101
|
+
|
|
102
|
+
// remark()
|
|
103
|
+
// .use(remarkPlugins)
|
|
104
|
+
// .use(remarkRehype, {
|
|
105
|
+
// allowDangerousHtml: true,
|
|
106
|
+
// })
|
|
107
|
+
// .use(rehypePlugins)
|
|
108
|
+
// .use(rehypeStringify)
|
|
109
|
+
// .process(cellData.code || "*Empty Markdown Cell*")
|
|
110
|
+
// .then(async (file) => {
|
|
111
|
+
// content.element.innerHTML = String(file);
|
|
112
|
+
|
|
113
|
+
// await activateHTML(content.element);
|
|
114
|
+
// })
|
|
115
|
+
// .catch((err) => {
|
|
116
|
+
// content.element.innerHTML = `
|
|
117
|
+
// <div class="error-output">
|
|
118
|
+
// Markdown rendering error: ${err.message}
|
|
119
|
+
// </div>
|
|
120
|
+
// `;
|
|
121
|
+
// });
|
|
122
|
+
|
|
123
|
+
// const wrap = div(
|
|
124
|
+
// {
|
|
125
|
+
// class: "markdown-rendered-cell markdown-body",
|
|
126
|
+
// },
|
|
127
|
+
// content
|
|
128
|
+
// );
|
|
129
|
+
|
|
130
|
+
// wrap.onClick(() => actions.editMarkdown());
|
|
131
|
+
|
|
132
|
+
// return wrap.element;
|
|
133
|
+
// }
|
package/src/components/index.css
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
@import url('./ErrorOutput/index.css');
|
|
2
2
|
@import url('./Button/index.css');
|
|
3
3
|
@import url('./Header/index.css');
|
|
4
|
-
@import url('./MarkdownView//index.css');
|
|
4
|
+
@import url('./MarkdownView//index.css');
|
|
5
|
+
|
|
6
|
+
.ziko-notebook .runtime-select {
|
|
7
|
+
font-family: inherit;
|
|
8
|
+
font-size: 11px;
|
|
9
|
+
padding: 2px 4px;
|
|
10
|
+
border: 1px solid var(--border-color);
|
|
11
|
+
border-radius: 4px;
|
|
12
|
+
background: #ffffff;
|
|
13
|
+
color: #1e293b;
|
|
14
|
+
cursor: pointer;
|
|
15
|
+
height: 24px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.ziko-notebook .runtime-select:focus {
|
|
19
|
+
outline: none;
|
|
20
|
+
border-color: var(--primary);
|
|
21
|
+
}
|
package/src/index.css
CHANGED
package/src/notebook.js
CHANGED
|
@@ -21,12 +21,15 @@ export class UINotebook extends UIElement{
|
|
|
21
21
|
cells: initialCells = [],
|
|
22
22
|
importMap = {},
|
|
23
23
|
runCells = true,
|
|
24
|
-
codeMirrorConfig = {},
|
|
25
|
-
codeMirrorPlugins = [],
|
|
26
24
|
minLines = 3,
|
|
27
25
|
maxCells = Infinity,
|
|
28
26
|
showHeader = true,
|
|
29
|
-
|
|
27
|
+
codeMirrorConfig = {},
|
|
28
|
+
codeMirrorPlugins = [],
|
|
29
|
+
runtimePlugins = [],
|
|
30
|
+
remarkPlugins = [],
|
|
31
|
+
rehypePlugins = []
|
|
32
|
+
|
|
30
33
|
} = {}) {
|
|
31
34
|
super({ element : 'div'})
|
|
32
35
|
this.setAttr({class : 'ziko-notebook'})
|
|
@@ -41,6 +44,8 @@ export class UINotebook extends UIElement{
|
|
|
41
44
|
this.runtimePlugins = runtimePlugins;
|
|
42
45
|
this.codeMirrorConfig = codeMirrorConfig;
|
|
43
46
|
this.codeMirrorPlugins = codeMirrorPlugins;
|
|
47
|
+
this.remarkPlugins = remarkPlugins;
|
|
48
|
+
this.rehypePlugins = rehypePlugins;
|
|
44
49
|
this.minLines = minLines;
|
|
45
50
|
this.maxCells = maxCells;
|
|
46
51
|
|
|
@@ -53,6 +58,7 @@ export class UINotebook extends UIElement{
|
|
|
53
58
|
type: cell.type || "code",
|
|
54
59
|
code: cell.code || "",
|
|
55
60
|
readonly: cell.readonly ?? false,
|
|
61
|
+
runtime: cell.runtime || null,
|
|
56
62
|
isEditingMarkdown: cell.readonly ? false : (cell.isEditingMarkdown ?? true),
|
|
57
63
|
execCount: cell.execCount ?? null,
|
|
58
64
|
outputNode: cell.outputNode || null,
|
|
@@ -142,12 +148,13 @@ export class UINotebook extends UIElement{
|
|
|
142
148
|
{
|
|
143
149
|
importMap: app.importMap,
|
|
144
150
|
plugins: app.runtimePlugins,
|
|
151
|
+
runtime: cellData.runtime // <-- Passed dynamically from the cell configuration
|
|
145
152
|
},
|
|
146
153
|
scope,
|
|
147
154
|
);
|
|
148
155
|
} catch (err) {
|
|
149
156
|
hasError = true;
|
|
150
|
-
ErrorOutput(err).mount(cellData._outputRef)
|
|
157
|
+
ErrorOutput(err).mount(cellData._outputRef);
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
const counter = this.store.get('executionCounter');
|
|
@@ -192,7 +199,11 @@ export class UINotebook extends UIElement{
|
|
|
192
199
|
app._refreshCell(cellData);
|
|
193
200
|
},
|
|
194
201
|
addBelow: () => app.addCell(cellData.type, null, cellData.id),
|
|
195
|
-
deleteCell: () => app.deleteCell(cellData.id)
|
|
202
|
+
deleteCell: () => app.deleteCell(cellData.id),
|
|
203
|
+
setRuntime: (newRuntime) => {
|
|
204
|
+
cellData.runtime = newRuntime;
|
|
205
|
+
app._refreshCell(cellData);
|
|
206
|
+
},
|
|
196
207
|
};
|
|
197
208
|
}
|
|
198
209
|
|
|
@@ -212,7 +223,12 @@ export class UINotebook extends UIElement{
|
|
|
212
223
|
|
|
213
224
|
cellData._editor.sync(isActive, isMarkdownView);
|
|
214
225
|
cellData._bodySlot.replaceChildren(
|
|
215
|
-
isMarkdownView ? MarkdownView({
|
|
226
|
+
isMarkdownView ? MarkdownView({
|
|
227
|
+
cellData,
|
|
228
|
+
actions: cellData._actions,
|
|
229
|
+
remarkPlugins : this.remarkPlugins,
|
|
230
|
+
rehypePlugins : this.rehypePlugins
|
|
231
|
+
}) : cellData._editor.dom
|
|
216
232
|
);
|
|
217
233
|
|
|
218
234
|
const showOutput = cellData.type === "code" && cellData.hasDomOutput;
|
|
@@ -226,20 +242,34 @@ export class UINotebook extends UIElement{
|
|
|
226
242
|
}
|
|
227
243
|
|
|
228
244
|
setActiveCell(id) {
|
|
229
|
-
|
|
230
|
-
|
|
245
|
+
const cells = this.store.get('cells');
|
|
246
|
+
const target = cells.find(c => c.id === id);
|
|
231
247
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
c.isEditingMarkdown = false;
|
|
236
|
-
this._refreshCell(c);
|
|
237
|
-
}
|
|
238
|
-
});
|
|
248
|
+
if (!target) return;
|
|
249
|
+
|
|
250
|
+
const currentActiveId = this.store.get('activeCellId');
|
|
239
251
|
|
|
240
|
-
|
|
252
|
+
if (currentActiveId === id) {
|
|
253
|
+
return;
|
|
241
254
|
}
|
|
242
255
|
|
|
256
|
+
// Only one cell can be active.
|
|
257
|
+
cells.forEach(cell => {
|
|
258
|
+
if (cell.id !== id && cell.type === "markdown") {
|
|
259
|
+
cell.isEditingMarkdown = false;
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
this.store.set('activeCellId', id);
|
|
264
|
+
|
|
265
|
+
// Refresh every cell so the previous active cell loses "active".
|
|
266
|
+
cells.forEach(cell => {
|
|
267
|
+
if (cell._dom) {
|
|
268
|
+
this._refreshCell(cell);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
243
273
|
focusCell(targetId) {
|
|
244
274
|
// Close editing on other markdown cells before focusing
|
|
245
275
|
this.store.get('cells').forEach(c => {
|
|
@@ -268,6 +298,7 @@ export class UINotebook extends UIElement{
|
|
|
268
298
|
const newCell = {
|
|
269
299
|
id: newId, type, code: initialCode !== null ? initialCode : defaultText, readonly,
|
|
270
300
|
isEditingMarkdown: !readonly && type === "markdown" && initialCode === null,
|
|
301
|
+
runtime: null,
|
|
271
302
|
execCount: null, outputNode: null, hasDomOutput: false, _copiedFlag: false,
|
|
272
303
|
_dom: null, _editor: null, _actions: null,
|
|
273
304
|
_inPrompt: null, _outPrompt: null, _bodySlot: null, _outputGrid: null, _outputRef: null
|