@observablehq/notebook-kit 1.0.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/LICENSE +13 -0
- package/README.md +7 -0
- package/dist/bin/build.d.ts +2 -0
- package/dist/bin/build.js +63 -0
- package/dist/bin/download.d.ts +2 -0
- package/dist/bin/download.js +49 -0
- package/dist/bin/notebooks.d.ts +2 -0
- package/dist/bin/notebooks.js +33 -0
- package/dist/bin/preview.d.ts +2 -0
- package/dist/bin/preview.js +48 -0
- package/dist/package.json +73 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +5 -0
- package/dist/src/javascript/assignments.d.ts +2 -0
- package/dist/src/javascript/assignments.js +37 -0
- package/dist/src/javascript/assignments.test.d.ts +1 -0
- package/dist/src/javascript/assignments.test.js +33 -0
- package/dist/src/javascript/awaits.d.ts +2 -0
- package/dist/src/javascript/awaits.js +23 -0
- package/dist/src/javascript/awaits.test.d.ts +1 -0
- package/dist/src/javascript/awaits.test.js +22 -0
- package/dist/src/javascript/declarations.d.ts +2 -0
- package/dist/src/javascript/declarations.js +45 -0
- package/dist/src/javascript/files.d.ts +3 -0
- package/dist/src/javascript/files.js +18 -0
- package/dist/src/javascript/globals.d.ts +1 -0
- package/dist/src/javascript/globals.js +86 -0
- package/dist/src/javascript/imports/jsr.d.ts +2 -0
- package/dist/src/javascript/imports/jsr.js +6 -0
- package/dist/src/javascript/imports/npm.d.ts +2 -0
- package/dist/src/javascript/imports/npm.js +47 -0
- package/dist/src/javascript/imports/npm.test.d.ts +1 -0
- package/dist/src/javascript/imports/npm.test.js +32 -0
- package/dist/src/javascript/imports/observable.d.ts +8 -0
- package/dist/src/javascript/imports/observable.js +64 -0
- package/dist/src/javascript/imports/observable.test.d.ts +1 -0
- package/dist/src/javascript/imports/observable.test.js +13 -0
- package/dist/src/javascript/imports.d.ts +21 -0
- package/dist/src/javascript/imports.js +146 -0
- package/dist/src/javascript/observable.d.ts +2 -0
- package/dist/src/javascript/observable.js +72 -0
- package/dist/src/javascript/parse.d.ts +11 -0
- package/dist/src/javascript/parse.js +53 -0
- package/dist/src/javascript/references.d.ts +8 -0
- package/dist/src/javascript/references.js +129 -0
- package/dist/src/javascript/references.test.d.ts +1 -0
- package/dist/src/javascript/references.test.js +38 -0
- package/dist/src/javascript/sourcemap.d.ts +21 -0
- package/dist/src/javascript/sourcemap.js +143 -0
- package/dist/src/javascript/sourcemap.test.d.ts +1 -0
- package/dist/src/javascript/sourcemap.test.js +88 -0
- package/dist/src/javascript/strings.d.ts +8 -0
- package/dist/src/javascript/strings.js +42 -0
- package/dist/src/javascript/strings.test.d.ts +1 -0
- package/dist/src/javascript/strings.test.js +31 -0
- package/dist/src/javascript/syntaxError.d.ts +2 -0
- package/dist/src/javascript/syntaxError.js +5 -0
- package/dist/src/javascript/template.d.ts +3 -0
- package/dist/src/javascript/template.js +141 -0
- package/dist/src/javascript/template.test.d.ts +1 -0
- package/dist/src/javascript/template.test.js +32 -0
- package/dist/src/javascript/transpile.d.ts +25 -0
- package/dist/src/javascript/transpile.js +42 -0
- package/dist/src/javascript/transpile.test.d.ts +1 -0
- package/dist/src/javascript/transpile.test.js +29 -0
- package/dist/src/javascript/walk.d.ts +5 -0
- package/dist/src/javascript/walk.js +13 -0
- package/dist/src/lib/notebook.d.ts +35 -0
- package/dist/src/lib/notebook.js +19 -0
- package/dist/src/lib/notebook.test.d.ts +1 -0
- package/dist/src/lib/notebook.test.js +26 -0
- package/dist/src/lib/serialize.d.ts +5 -0
- package/dist/src/lib/serialize.js +97 -0
- package/dist/src/lib/serialize.test.d.ts +1 -0
- package/dist/src/lib/serialize.test.js +125 -0
- package/dist/src/lib/text.d.ts +1 -0
- package/dist/src/lib/text.js +3 -0
- package/dist/src/runtime/define.d.ts +28 -0
- package/dist/src/runtime/define.js +62 -0
- package/dist/src/runtime/display.d.ts +16 -0
- package/dist/src/runtime/display.js +60 -0
- package/dist/src/runtime/index.d.ts +11 -0
- package/dist/src/runtime/index.js +14 -0
- package/dist/src/runtime/inspect.d.ts +3 -0
- package/dist/src/runtime/inspect.js +43 -0
- package/dist/src/runtime/stdlib/assets.d.ts +4 -0
- package/dist/src/runtime/stdlib/assets.js +110 -0
- package/dist/src/runtime/stdlib/assets.test.d.ts +1 -0
- package/dist/src/runtime/stdlib/assets.test.js +78 -0
- package/dist/src/runtime/stdlib/dom/canvas.d.ts +1 -0
- package/dist/src/runtime/stdlib/dom/canvas.js +6 -0
- package/dist/src/runtime/stdlib/dom/context2d.d.ts +1 -0
- package/dist/src/runtime/stdlib/dom/context2d.js +9 -0
- package/dist/src/runtime/stdlib/dom/index.d.ts +5 -0
- package/dist/src/runtime/stdlib/dom/index.js +5 -0
- package/dist/src/runtime/stdlib/dom/svg.d.ts +1 -0
- package/dist/src/runtime/stdlib/dom/svg.js +7 -0
- package/dist/src/runtime/stdlib/dom/text.d.ts +1 -0
- package/dist/src/runtime/stdlib/dom/text.js +3 -0
- package/dist/src/runtime/stdlib/dom/uid.d.ts +8 -0
- package/dist/src/runtime/stdlib/dom/uid.js +25 -0
- package/dist/src/runtime/stdlib/dot.d.ts +2 -0
- package/dist/src/runtime/stdlib/dot.js +34 -0
- package/dist/src/runtime/stdlib/duckdb.d.ts +24 -0
- package/dist/src/runtime/stdlib/duckdb.js +379 -0
- package/dist/src/runtime/stdlib/fileAttachment.d.ts +71 -0
- package/dist/src/runtime/stdlib/fileAttachment.js +199 -0
- package/dist/src/runtime/stdlib/generators/index.d.ts +5 -0
- package/dist/src/runtime/stdlib/generators/index.js +5 -0
- package/dist/src/runtime/stdlib/generators/input.d.ts +1 -0
- package/dist/src/runtime/stdlib/generators/input.js +45 -0
- package/dist/src/runtime/stdlib/generators/now.d.ts +1 -0
- package/dist/src/runtime/stdlib/generators/now.js +5 -0
- package/dist/src/runtime/stdlib/generators/observe.d.ts +1 -0
- package/dist/src/runtime/stdlib/generators/observe.js +31 -0
- package/dist/src/runtime/stdlib/generators/queue.d.ts +1 -0
- package/dist/src/runtime/stdlib/generators/queue.js +27 -0
- package/dist/src/runtime/stdlib/generators/width.d.ts +1 -0
- package/dist/src/runtime/stdlib/generators/width.js +13 -0
- package/dist/src/runtime/stdlib/highlight.d.ts +2 -0
- package/dist/src/runtime/stdlib/highlight.js +76 -0
- package/dist/src/runtime/stdlib/index.d.ts +56 -0
- package/dist/src/runtime/stdlib/index.js +23 -0
- package/dist/src/runtime/stdlib/inputs.css +15 -0
- package/dist/src/runtime/stdlib/inputs.d.ts +2 -0
- package/dist/src/runtime/stdlib/inputs.js +2 -0
- package/dist/src/runtime/stdlib/leaflet.d.ts +1 -0
- package/dist/src/runtime/stdlib/leaflet.js +7 -0
- package/dist/src/runtime/stdlib/mapboxgl.d.ts +1 -0
- package/dist/src/runtime/stdlib/mapboxgl.js +5 -0
- package/dist/src/runtime/stdlib/md.d.ts +5 -0
- package/dist/src/runtime/stdlib/md.js +72 -0
- package/dist/src/runtime/stdlib/mermaid.d.ts +2 -0
- package/dist/src/runtime/stdlib/mermaid.js +11 -0
- package/dist/src/runtime/stdlib/mutable.d.ts +8 -0
- package/dist/src/runtime/stdlib/mutable.js +30 -0
- package/dist/src/runtime/stdlib/observer.d.ts +16 -0
- package/dist/src/runtime/stdlib/observer.js +42 -0
- package/dist/src/runtime/stdlib/recommendedLibraries.d.ts +25 -0
- package/dist/src/runtime/stdlib/recommendedLibraries.js +26 -0
- package/dist/src/runtime/stdlib/require.d.ts +4 -0
- package/dist/src/runtime/stdlib/require.js +40 -0
- package/dist/src/runtime/stdlib/sampleDatasets.d.ts +12 -0
- package/dist/src/runtime/stdlib/sampleDatasets.js +31 -0
- package/dist/src/runtime/stdlib/sql.d.ts +5 -0
- package/dist/src/runtime/stdlib/sql.js +5 -0
- package/dist/src/runtime/stdlib/template.d.ts +7 -0
- package/dist/src/runtime/stdlib/template.js +2 -0
- package/dist/src/runtime/stdlib/tex.d.ts +7 -0
- package/dist/src/runtime/stdlib/tex.js +18 -0
- package/dist/src/runtime/stdlib/vega-lite.d.ts +1 -0
- package/dist/src/runtime/stdlib/vega-lite.js +4 -0
- package/dist/src/styles/abstract-dark.css +14 -0
- package/dist/src/styles/abstract-light.css +14 -0
- package/dist/src/styles/global.css +266 -0
- package/dist/src/styles/highlight.css +47 -0
- package/dist/src/styles/index.css +14 -0
- package/dist/src/styles/inspector.css +89 -0
- package/dist/src/styles/plot.css +7 -0
- package/dist/src/styles/syntax-dark.css +12 -0
- package/dist/src/styles/syntax-light.css +12 -0
- package/dist/src/styles/theme-air.css +7 -0
- package/dist/src/styles/theme-coffee.css +7 -0
- package/dist/src/styles/theme-cotton.css +7 -0
- package/dist/src/styles/theme-deep-space.css +16 -0
- package/dist/src/styles/theme-glacier.css +7 -0
- package/dist/src/styles/theme-ink.css +7 -0
- package/dist/src/styles/theme-midnight.css +7 -0
- package/dist/src/styles/theme-near-midnight.css +7 -0
- package/dist/src/styles/theme-ocean-floor.css +7 -0
- package/dist/src/styles/theme-parchment.css +7 -0
- package/dist/src/styles/theme-slate.css +7 -0
- package/dist/src/styles/theme-stark.css +16 -0
- package/dist/src/styles/theme-sun-faded.css +7 -0
- package/dist/src/templates/default.html +31 -0
- package/dist/src/vite/config.d.ts +2 -0
- package/dist/src/vite/config.js +30 -0
- package/dist/src/vite/index.d.ts +2 -0
- package/dist/src/vite/index.js +2 -0
- package/dist/src/vite/observable.d.ts +12 -0
- package/dist/src/vite/observable.js +176 -0
- package/package.json +73 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type NotebookTheme = "air" | "coffee" | "cotton" | "deep-space" | "glacier" | "ink" | "midnight" | "near-midnight" | "ocean-floor" | "parchment" | "slate" | "stark" | "sun-faded";
|
|
2
|
+
export interface NotebookSpec {
|
|
3
|
+
/** the notebook’s cells, in top-to-bottom document order */
|
|
4
|
+
cells?: CellSpec[];
|
|
5
|
+
/** the notebook title, if any; extracted from the first h1 */
|
|
6
|
+
title?: string;
|
|
7
|
+
/** the notebook theme; defaults to "air" */
|
|
8
|
+
theme?: NotebookTheme;
|
|
9
|
+
/** if true, don’t allow editing */
|
|
10
|
+
readOnly?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface Notebook extends NotebookSpec {
|
|
13
|
+
cells: Cell[];
|
|
14
|
+
title: NonNullable<NotebookSpec["title"]>;
|
|
15
|
+
theme: NonNullable<NotebookSpec["theme"]>;
|
|
16
|
+
readOnly: NonNullable<NotebookSpec["readOnly"]>;
|
|
17
|
+
}
|
|
18
|
+
export interface CellSpec {
|
|
19
|
+
/** the unique identifier for this cell */
|
|
20
|
+
id: number;
|
|
21
|
+
/** the committed cell value; defaults to empty */
|
|
22
|
+
value?: string;
|
|
23
|
+
/** the mode; affects how the value is evaluated; defaults to js */
|
|
24
|
+
mode?: "js" | "ojs" | "md" | "html" | "tex" | "dot" | "sql";
|
|
25
|
+
/** if true, the editor will stay open when not focused; defaults to false */
|
|
26
|
+
pinned?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface Cell extends CellSpec {
|
|
29
|
+
value: NonNullable<CellSpec["value"]>;
|
|
30
|
+
mode: NonNullable<CellSpec["mode"]>;
|
|
31
|
+
pinned: NonNullable<CellSpec["pinned"]>;
|
|
32
|
+
}
|
|
33
|
+
export declare function toNotebook({ cells, title, theme, readOnly }: NotebookSpec): Notebook;
|
|
34
|
+
export declare function toCell({ id, value, mode, pinned }: CellSpec): Cell;
|
|
35
|
+
export declare function defaultPinned(mode: Cell["mode"]): boolean;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function toNotebook({ cells = [], title = "Untitled", theme = "air", readOnly = false }) {
|
|
2
|
+
return {
|
|
3
|
+
cells: cells.map(toCell),
|
|
4
|
+
title,
|
|
5
|
+
theme,
|
|
6
|
+
readOnly
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function toCell({ id, value = "", mode = "js", pinned = defaultPinned(mode) }) {
|
|
10
|
+
return {
|
|
11
|
+
id,
|
|
12
|
+
value,
|
|
13
|
+
mode,
|
|
14
|
+
pinned
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function defaultPinned(mode) {
|
|
18
|
+
return mode === "js" || mode === "ojs";
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { assert, test } from "vitest";
|
|
2
|
+
import { toCell, toNotebook } from "./notebook.js";
|
|
3
|
+
test("converts a notebook spec to a notebook", () => {
|
|
4
|
+
assert.deepStrictEqual(toNotebook({}), {
|
|
5
|
+
title: "Untitled",
|
|
6
|
+
theme: "air",
|
|
7
|
+
cells: [],
|
|
8
|
+
readOnly: false
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
test("converts a cell spec to a cell", () => {
|
|
12
|
+
assert.deepStrictEqual(toCell({ id: 1 }), {
|
|
13
|
+
id: 1,
|
|
14
|
+
value: "",
|
|
15
|
+
mode: "js",
|
|
16
|
+
pinned: true
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
test("computes an appropriate default pinned based on the cell mode", () => {
|
|
20
|
+
assert.deepStrictEqual(toCell({ id: 1, mode: "md" }), {
|
|
21
|
+
id: 1,
|
|
22
|
+
value: "",
|
|
23
|
+
mode: "md",
|
|
24
|
+
pinned: false
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { toNotebook } from "./notebook.js";
|
|
2
|
+
import { isEmpty } from "./text.js";
|
|
3
|
+
export function serialize(notebook) {
|
|
4
|
+
const _notebook = document.createElement("notebook");
|
|
5
|
+
_notebook.setAttribute("theme", notebook.theme);
|
|
6
|
+
if (notebook.readOnly)
|
|
7
|
+
_notebook.setAttribute("readonly", "");
|
|
8
|
+
_notebook.appendChild(document.createTextNode("\n "));
|
|
9
|
+
const _title = document.createElement("title");
|
|
10
|
+
_title.textContent = notebook.title;
|
|
11
|
+
_notebook.appendChild(_title);
|
|
12
|
+
for (const cell of notebook.cells) {
|
|
13
|
+
_notebook.appendChild(document.createTextNode("\n "));
|
|
14
|
+
const _cell = document.createElement("script");
|
|
15
|
+
_cell.id = String(cell.id);
|
|
16
|
+
_cell.type = serializeMode(cell.mode);
|
|
17
|
+
_cell.textContent = indent(cell.value.replace(/<(?=\\*\/script(\s|>))/gi, "<\\"));
|
|
18
|
+
if (cell.pinned)
|
|
19
|
+
_cell.setAttribute("pinned", "");
|
|
20
|
+
_notebook.appendChild(_cell);
|
|
21
|
+
}
|
|
22
|
+
_notebook.appendChild(document.createTextNode("\n"));
|
|
23
|
+
return `<!doctype html>\n${_notebook.outerHTML}\n`;
|
|
24
|
+
}
|
|
25
|
+
export function deserialize(data, { parser = new DOMParser() } = {}) {
|
|
26
|
+
const document = parser.parseFromString(data, "text/html");
|
|
27
|
+
const _notebook = document.querySelector("notebook");
|
|
28
|
+
const theme = deserializeTheme(_notebook?.getAttribute("theme"));
|
|
29
|
+
const readOnly = _notebook?.hasAttribute("readonly");
|
|
30
|
+
const title = document.querySelector("title")?.textContent ?? undefined;
|
|
31
|
+
let maxCellId = 0;
|
|
32
|
+
const cellIds = new Set();
|
|
33
|
+
const cells = Array.from(document.querySelectorAll("notebook script"), (cell) => {
|
|
34
|
+
let id = Math.floor(Number(cell.id));
|
|
35
|
+
if (!isFinite(id) || !(id > 0) || cellIds.has(id))
|
|
36
|
+
id = ++maxCellId;
|
|
37
|
+
else if (id > maxCellId)
|
|
38
|
+
maxCellId = id;
|
|
39
|
+
cellIds.add(id);
|
|
40
|
+
const pinned = cell.hasAttribute("pinned");
|
|
41
|
+
const value = dedent(cell.textContent?.replace(/<\\(?=\\*\/script(\s|>))/gi, "<") ?? "");
|
|
42
|
+
const mode = deserializeMode(cell.getAttribute("type"));
|
|
43
|
+
return { id, pinned, mode, value };
|
|
44
|
+
});
|
|
45
|
+
return toNotebook({ title, theme, readOnly, cells });
|
|
46
|
+
}
|
|
47
|
+
function serializeMode(mode) {
|
|
48
|
+
switch (mode) {
|
|
49
|
+
case "md":
|
|
50
|
+
return "text/markdown";
|
|
51
|
+
case "html":
|
|
52
|
+
return "text/html";
|
|
53
|
+
case "tex":
|
|
54
|
+
return "application/x-tex";
|
|
55
|
+
case "sql":
|
|
56
|
+
return "application/sql";
|
|
57
|
+
case "dot":
|
|
58
|
+
return "text/vnd.graphviz";
|
|
59
|
+
case "ojs":
|
|
60
|
+
return "application/vnd.observable.javascript";
|
|
61
|
+
default:
|
|
62
|
+
return "module";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function deserializeMode(mode) {
|
|
66
|
+
switch (mode) {
|
|
67
|
+
case "text/markdown":
|
|
68
|
+
return "md";
|
|
69
|
+
case "text/html":
|
|
70
|
+
return "html";
|
|
71
|
+
case "application/x-tex":
|
|
72
|
+
return "tex";
|
|
73
|
+
case "application/sql":
|
|
74
|
+
return "sql";
|
|
75
|
+
case "text/vnd.graphviz":
|
|
76
|
+
return "dot";
|
|
77
|
+
case "application/vnd.observable.javascript":
|
|
78
|
+
return "ojs";
|
|
79
|
+
default:
|
|
80
|
+
return "js";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function deserializeTheme(theme) {
|
|
84
|
+
return theme ?? "air";
|
|
85
|
+
}
|
|
86
|
+
function dedent(text) {
|
|
87
|
+
const lines = text.split(/\r\n?|\n/);
|
|
88
|
+
if (isEmpty(lines[lines.length - 1]))
|
|
89
|
+
lines.pop();
|
|
90
|
+
if (isEmpty(lines[0]))
|
|
91
|
+
lines.shift();
|
|
92
|
+
return lines.map((l) => l.replace(/^ {4}/, "")).join("\n");
|
|
93
|
+
}
|
|
94
|
+
function indent(text) {
|
|
95
|
+
const lines = text.split(/\r\n?|\n/);
|
|
96
|
+
return `\n${lines.map((l) => (l.trim() ? ` ${l}` : "")).join("\n")}\n `;
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { assert, test } from "vitest";
|
|
3
|
+
import { toNotebook } from "./notebook.js";
|
|
4
|
+
import { deserialize, serialize } from "./serialize.js";
|
|
5
|
+
test("serializes unpinned cells", () => {
|
|
6
|
+
const notebook1 = toNotebook({
|
|
7
|
+
cells: [
|
|
8
|
+
{ id: 1, mode: "md", pinned: false, value: "# Hello, world!" },
|
|
9
|
+
{ id: 2, pinned: false, value: "1 + 2" }
|
|
10
|
+
]
|
|
11
|
+
});
|
|
12
|
+
const notebook2 = toNotebook({
|
|
13
|
+
cells: [
|
|
14
|
+
{ id: 1, mode: "md", pinned: false, value: "# Hello, world!" },
|
|
15
|
+
{ id: 2, mode: "js", pinned: false, value: "1 + 2" }
|
|
16
|
+
]
|
|
17
|
+
});
|
|
18
|
+
assert.deepStrictEqual(deserialize(serialize(notebook1)), notebook2);
|
|
19
|
+
});
|
|
20
|
+
test("serializes pinned cells", () => {
|
|
21
|
+
const notebook1 = toNotebook({
|
|
22
|
+
cells: [
|
|
23
|
+
{ id: 1, mode: "md", pinned: true, value: "# Hello, world!" },
|
|
24
|
+
{ id: 2, pinned: true, value: "1 + 2" }
|
|
25
|
+
]
|
|
26
|
+
});
|
|
27
|
+
const notebook2 = toNotebook({
|
|
28
|
+
cells: [
|
|
29
|
+
{ id: 1, mode: "md", pinned: true, value: "# Hello, world!" },
|
|
30
|
+
{ id: 2, mode: "js", pinned: true, value: "1 + 2" }
|
|
31
|
+
]
|
|
32
|
+
});
|
|
33
|
+
assert.deepStrictEqual(deserialize(serialize(notebook1)), notebook2);
|
|
34
|
+
});
|
|
35
|
+
test("serializes notebook titles", () => {
|
|
36
|
+
const notebook1 = toNotebook({
|
|
37
|
+
title: "Hello, world!",
|
|
38
|
+
cells: [
|
|
39
|
+
{ id: 1, mode: "md", pinned: false, value: "# Hello, world!" },
|
|
40
|
+
{ id: 2, pinned: true, value: "1 + 2" }
|
|
41
|
+
]
|
|
42
|
+
});
|
|
43
|
+
const notebook2 = toNotebook({
|
|
44
|
+
title: "Hello, world!",
|
|
45
|
+
cells: [
|
|
46
|
+
{ id: 1, mode: "md", pinned: false, value: "# Hello, world!" },
|
|
47
|
+
{ id: 2, mode: "js", pinned: true, value: "1 + 2" }
|
|
48
|
+
]
|
|
49
|
+
});
|
|
50
|
+
assert.deepStrictEqual(deserialize(serialize(notebook1)), notebook2);
|
|
51
|
+
});
|
|
52
|
+
test("serialization preserves indentation", () => {
|
|
53
|
+
const notebook1 = toNotebook({
|
|
54
|
+
title: "Hello, world!",
|
|
55
|
+
cells: [{ id: 2, pinned: true, value: `{\n 1;\n 2;\n}` }]
|
|
56
|
+
});
|
|
57
|
+
const notebook2 = toNotebook({
|
|
58
|
+
title: "Hello, world!",
|
|
59
|
+
cells: [{ id: 2, mode: "js", pinned: true, value: `{\n 1;\n 2;\n}` }]
|
|
60
|
+
});
|
|
61
|
+
assert.deepStrictEqual(deserialize(serialize(notebook1)), notebook2);
|
|
62
|
+
});
|
|
63
|
+
test("serialization escapes </script>, in various forms", () => {
|
|
64
|
+
const notebook1 = toNotebook({
|
|
65
|
+
title: "Hello, world!",
|
|
66
|
+
cells: [
|
|
67
|
+
{ id: 2, pinned: true, value: `'</script>'` },
|
|
68
|
+
{ id: 3, pinned: true, value: `'</script '` },
|
|
69
|
+
{ id: 4, pinned: true, value: `'</SCRIPT '` },
|
|
70
|
+
{ id: 5, pinned: true, value: `'</sCrIpT '` },
|
|
71
|
+
{ id: 6, pinned: true, value: `'<\\/script>'` },
|
|
72
|
+
{ id: 7, pinned: true, value: `'<\\/script '` },
|
|
73
|
+
{ id: 8, pinned: true, value: `'<\\\\/SCRIPT '` },
|
|
74
|
+
{ id: 9, pinned: true, value: `'<\\\\/sCrIpT '` }
|
|
75
|
+
]
|
|
76
|
+
});
|
|
77
|
+
const notebook2 = toNotebook({
|
|
78
|
+
title: "Hello, world!",
|
|
79
|
+
cells: [
|
|
80
|
+
{ id: 2, mode: "js", pinned: true, value: `'</script>'` },
|
|
81
|
+
{ id: 3, mode: "js", pinned: true, value: `'</script '` },
|
|
82
|
+
{ id: 4, mode: "js", pinned: true, value: `'</SCRIPT '` },
|
|
83
|
+
{ id: 5, mode: "js", pinned: true, value: `'</sCrIpT '` },
|
|
84
|
+
{ id: 6, mode: "js", pinned: true, value: `'<\\/script>'` },
|
|
85
|
+
{ id: 7, mode: "js", pinned: true, value: `'<\\/script '` },
|
|
86
|
+
{ id: 8, mode: "js", pinned: true, value: `'<\\\\/SCRIPT '` },
|
|
87
|
+
{ id: 9, mode: "js", pinned: true, value: `'<\\\\/sCrIpT '` },
|
|
88
|
+
]
|
|
89
|
+
});
|
|
90
|
+
const html = serialize(notebook1);
|
|
91
|
+
assert.strictEqual(html.indexOf("'</script"), -1);
|
|
92
|
+
assert.deepStrictEqual(deserialize(html), notebook2);
|
|
93
|
+
});
|
|
94
|
+
test("serialization enforces unique ids", () => {
|
|
95
|
+
const notebook1 = toNotebook({
|
|
96
|
+
cells: [
|
|
97
|
+
{ id: 2, value: "one" },
|
|
98
|
+
{ id: 2, value: "two" }
|
|
99
|
+
]
|
|
100
|
+
});
|
|
101
|
+
const notebook2 = toNotebook({
|
|
102
|
+
cells: [
|
|
103
|
+
{ id: 2, value: "one" },
|
|
104
|
+
{ id: 3, value: "two" }
|
|
105
|
+
]
|
|
106
|
+
});
|
|
107
|
+
assert.deepStrictEqual(deserialize(serialize(notebook1)), notebook2);
|
|
108
|
+
});
|
|
109
|
+
test("deserialization populates missing ids", () => {
|
|
110
|
+
const notebook1 = toNotebook({
|
|
111
|
+
cells: [
|
|
112
|
+
{ value: "one" }, // missing id
|
|
113
|
+
{ id: 3, value: "three" },
|
|
114
|
+
{ value: "four" } // missing id
|
|
115
|
+
]
|
|
116
|
+
});
|
|
117
|
+
const notebook2 = toNotebook({
|
|
118
|
+
cells: [
|
|
119
|
+
{ id: 1, value: "one" },
|
|
120
|
+
{ id: 3, value: "three" },
|
|
121
|
+
{ id: 4, value: "four" }
|
|
122
|
+
]
|
|
123
|
+
});
|
|
124
|
+
assert.deepStrictEqual(deserialize(serialize(notebook1)), notebook2);
|
|
125
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isEmpty(text: string): boolean;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Variable, VariableDefinition } from "@observablehq/runtime";
|
|
2
|
+
import type { DisplayState } from "./display.js";
|
|
3
|
+
import { observe } from "./display.js";
|
|
4
|
+
export type DefineState = DisplayState & {
|
|
5
|
+
/** the runtime variables associated with this cell */
|
|
6
|
+
variables: Variable[];
|
|
7
|
+
};
|
|
8
|
+
export type Definition = {
|
|
9
|
+
/** the unique cell id; a positive integer */
|
|
10
|
+
id: number;
|
|
11
|
+
/** the cell’s definition function */
|
|
12
|
+
body: VariableDefinition;
|
|
13
|
+
/** the names of this cell’s inputs (unbound references), if any */
|
|
14
|
+
inputs?: string[];
|
|
15
|
+
/** the names of this cell’s outputs (top-level declarations), if any */
|
|
16
|
+
outputs?: string[];
|
|
17
|
+
/** the singular output name of this cell, if any; an alternative to outputs */
|
|
18
|
+
output?: string;
|
|
19
|
+
/** whether to display this cell’s singular output automatically */
|
|
20
|
+
autodisplay?: boolean;
|
|
21
|
+
/** whether this cell’s singular output is a view */
|
|
22
|
+
autoview?: boolean;
|
|
23
|
+
/** whether this cell’s singular output is a mutable */
|
|
24
|
+
automutable?: boolean;
|
|
25
|
+
/** an asset mapping to apply to any autodisplayed assets (e.g., images and videos) */
|
|
26
|
+
assets?: Map<string, string>;
|
|
27
|
+
};
|
|
28
|
+
export declare function define(state: DefineState, definition: Definition, observer?: typeof observe): void;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { clear, display, observe } from "./display.js";
|
|
2
|
+
import { main } from "./index.js";
|
|
3
|
+
import { input } from "./stdlib/generators/index.js";
|
|
4
|
+
import { Mutator } from "./stdlib/mutable.js";
|
|
5
|
+
export function define(state, definition, observer = observe) {
|
|
6
|
+
const { id, body, inputs = [], outputs = [], output, autodisplay, autoview, automutable } = definition;
|
|
7
|
+
const variables = state.variables;
|
|
8
|
+
const v = main.variable(observer(state, definition), { shadow: {} });
|
|
9
|
+
const vid = output ?? (outputs.length ? `cell ${id}` : null);
|
|
10
|
+
if (inputs.includes("display") || inputs.includes("view")) {
|
|
11
|
+
let displayVersion = -1; // the variable._version of currently-displayed values
|
|
12
|
+
const vd = new v.constructor(2, v._module);
|
|
13
|
+
vd.define(inputs.filter((i) => i !== "display" && i !== "view"), () => {
|
|
14
|
+
const version = v._version; // capture version on input change
|
|
15
|
+
return (value) => {
|
|
16
|
+
if (version < displayVersion)
|
|
17
|
+
throw new Error("stale display");
|
|
18
|
+
else if (state.variables[0] !== v)
|
|
19
|
+
throw new Error("stale display");
|
|
20
|
+
else if (version > displayVersion)
|
|
21
|
+
clear(state);
|
|
22
|
+
displayVersion = version;
|
|
23
|
+
display(state, value);
|
|
24
|
+
return value;
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
v._shadow.set("display", vd);
|
|
28
|
+
if (inputs.includes("view")) {
|
|
29
|
+
const vv = new v.constructor(2, v._module, null, { shadow: {} });
|
|
30
|
+
vv._shadow.set("display", vd);
|
|
31
|
+
vv.define(["display"], (display) => (value) => input(display(value)));
|
|
32
|
+
v._shadow.set("view", vv);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (!autodisplay) {
|
|
36
|
+
clear(state);
|
|
37
|
+
}
|
|
38
|
+
variables.push(v.define(vid, inputs, body));
|
|
39
|
+
if (output != null) {
|
|
40
|
+
if (autoview) {
|
|
41
|
+
const o = unprefix(output, "viewof$");
|
|
42
|
+
variables.push(main.define(o, [output], input));
|
|
43
|
+
}
|
|
44
|
+
else if (automutable) {
|
|
45
|
+
const o = unprefix(output, "mutable ");
|
|
46
|
+
const x = `cell ${id}`;
|
|
47
|
+
v.define(o, [x], ([mutable]) => mutable); // observe live value
|
|
48
|
+
variables.push(main.define(output, inputs, body), // initial value
|
|
49
|
+
main.define(x, [output], Mutator), main.define(`mutable$${o}`, [x], ([, mutator]) => mutator));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
for (const o of outputs) {
|
|
54
|
+
variables.push(main.variable(true).define(o, [vid], (exports) => exports[o]));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function unprefix(name, prefix) {
|
|
59
|
+
if (!name.startsWith(prefix))
|
|
60
|
+
throw new Error(`expected ${prefix}: ${name}`);
|
|
61
|
+
return name.slice(prefix.length);
|
|
62
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Definition } from "./define.js";
|
|
2
|
+
export type DisplayState = {
|
|
3
|
+
/** the HTML element in which to render this cell’s display */
|
|
4
|
+
root: HTMLDivElement;
|
|
5
|
+
/** for inspected values, any expanded paths; see getExpanded */
|
|
6
|
+
expanded: (number[][] | undefined)[];
|
|
7
|
+
};
|
|
8
|
+
export declare function display(state: DisplayState, value: unknown): void;
|
|
9
|
+
export declare function clear(state: DisplayState): void;
|
|
10
|
+
export declare function observe(state: DisplayState, { autodisplay, assets }: Definition): {
|
|
11
|
+
_error: boolean;
|
|
12
|
+
_node: HTMLDivElement;
|
|
13
|
+
pending(): void;
|
|
14
|
+
fulfilled(value: unknown): void;
|
|
15
|
+
rejected(error: unknown): void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { inspect, inspectError, getExpanded } from "./inspect.js";
|
|
2
|
+
import { mapAssets } from "./stdlib/assets.js";
|
|
3
|
+
export function display(state, value) {
|
|
4
|
+
const { root, expanded } = state;
|
|
5
|
+
const node = isDisplayable(value, root) ? value : inspect(value, expanded[root.childNodes.length]); // prettier-ignore
|
|
6
|
+
displayNode(state, node);
|
|
7
|
+
}
|
|
8
|
+
function displayNode(state, node) {
|
|
9
|
+
if (node.nodeType === 11) {
|
|
10
|
+
let child;
|
|
11
|
+
while ((child = node.firstChild)) {
|
|
12
|
+
state.root.appendChild(child);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
state.root.appendChild(node);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function displayError(state, value) {
|
|
20
|
+
displayNode(state, inspectError(value));
|
|
21
|
+
}
|
|
22
|
+
// Note: Element.prototype is instanceof Node, but cannot be inserted! This
|
|
23
|
+
// excludes DocumentFragment since appending a fragment “dissolves” (mutates)
|
|
24
|
+
// the fragment, and we wish for the inspector to not have side-effects.
|
|
25
|
+
function isDisplayable(value, root) {
|
|
26
|
+
return ((value instanceof Element || value instanceof Text) &&
|
|
27
|
+
value instanceof value.constructor &&
|
|
28
|
+
(!value.parentNode || root.contains(value)));
|
|
29
|
+
}
|
|
30
|
+
export function clear(state) {
|
|
31
|
+
state.expanded = Array.from(state.root.childNodes, getExpanded);
|
|
32
|
+
while (state.root.lastChild)
|
|
33
|
+
state.root.lastChild.remove();
|
|
34
|
+
}
|
|
35
|
+
export function observe(state, { autodisplay, assets }) {
|
|
36
|
+
return {
|
|
37
|
+
_error: false,
|
|
38
|
+
_node: state.root, // _node for visibility promise
|
|
39
|
+
pending() {
|
|
40
|
+
if (this._error) {
|
|
41
|
+
this._error = false;
|
|
42
|
+
clear(state);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
fulfilled(value) {
|
|
46
|
+
if (autodisplay) {
|
|
47
|
+
clear(state);
|
|
48
|
+
if (assets && value instanceof Element)
|
|
49
|
+
mapAssets(value, assets);
|
|
50
|
+
display(state, value);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
rejected(error) {
|
|
54
|
+
console.error(error);
|
|
55
|
+
this._error = true;
|
|
56
|
+
clear(state);
|
|
57
|
+
displayError(state, error);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Module } from "@observablehq/runtime";
|
|
2
|
+
import { Runtime } from "@observablehq/runtime";
|
|
3
|
+
import { fileAttachments } from "./stdlib/fileAttachment.js";
|
|
4
|
+
export * from "./define.js";
|
|
5
|
+
export * from "./display.js";
|
|
6
|
+
export * from "./inspect.js";
|
|
7
|
+
export * from "./stdlib/index.js";
|
|
8
|
+
export declare const runtime: Runtime & {
|
|
9
|
+
fileAttachments: typeof fileAttachments;
|
|
10
|
+
};
|
|
11
|
+
export declare const main: Module;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Runtime } from "@observablehq/runtime";
|
|
2
|
+
import { fileAttachments } from "./stdlib/fileAttachment.js";
|
|
3
|
+
import { library } from "./stdlib/index.js";
|
|
4
|
+
export * from "./define.js";
|
|
5
|
+
export * from "./display.js";
|
|
6
|
+
export * from "./inspect.js";
|
|
7
|
+
export * from "./stdlib/index.js";
|
|
8
|
+
export const runtime = Object.assign(new Runtime({ ...library, __ojs_runtime: () => runtime }), { fileAttachments });
|
|
9
|
+
export const main = runtime.main = runtime.module();
|
|
10
|
+
main.constructor.prototype.defines = function (name) {
|
|
11
|
+
return (this._scope.has(name) ||
|
|
12
|
+
this._builtins.has(name) ||
|
|
13
|
+
this._runtime._builtin._scope.has(name));
|
|
14
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Inspector } from "@observablehq/inspector";
|
|
2
|
+
export function inspect(value, expanded) {
|
|
3
|
+
const node = document.createElement("div");
|
|
4
|
+
new Inspector(node).fulfilled(value); // TODO name?
|
|
5
|
+
if (expanded) {
|
|
6
|
+
for (const path of expanded) {
|
|
7
|
+
let child = node;
|
|
8
|
+
for (const i of path)
|
|
9
|
+
child = child?.childNodes[i];
|
|
10
|
+
child?.dispatchEvent(new Event("mouseup")); // restore expanded state
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return node;
|
|
14
|
+
}
|
|
15
|
+
export function inspectError(value) {
|
|
16
|
+
const node = document.createElement("div");
|
|
17
|
+
new Inspector(node).rejected(value);
|
|
18
|
+
return node;
|
|
19
|
+
}
|
|
20
|
+
export function getExpanded(node) {
|
|
21
|
+
if (!isInspector(node))
|
|
22
|
+
return;
|
|
23
|
+
const expanded = node.querySelectorAll(".observablehq--expanded");
|
|
24
|
+
if (expanded.length)
|
|
25
|
+
return Array.from(expanded, (e) => getNodePath(node, e));
|
|
26
|
+
}
|
|
27
|
+
function isElement(node) {
|
|
28
|
+
return node.nodeType === 1;
|
|
29
|
+
}
|
|
30
|
+
function isInspector(node) {
|
|
31
|
+
return isElement(node) && node.classList.contains("observablehq");
|
|
32
|
+
}
|
|
33
|
+
function getNodePath(node, descendant) {
|
|
34
|
+
const path = [];
|
|
35
|
+
while (descendant !== node) {
|
|
36
|
+
path.push(getChildIndex(descendant));
|
|
37
|
+
descendant = descendant.parentNode;
|
|
38
|
+
}
|
|
39
|
+
return path.reverse();
|
|
40
|
+
}
|
|
41
|
+
function getChildIndex(node) {
|
|
42
|
+
return Array.prototype.indexOf.call(node.parentNode.childNodes, node);
|
|
43
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Populates the asset keys from the specified root. */
|
|
2
|
+
export declare function collectAssets(assets: Set<string>, root: Element): void;
|
|
3
|
+
/** Mutates the specified root to apply the specified asset mapping. */
|
|
4
|
+
export declare function mapAssets(root: Element, assets: Map<string, string>): void;
|