@canvist/headless 0.0.0-stage → 0.2.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/dist/calls.d.ts +15 -0
- package/dist/calls.js +20 -0
- package/dist/create.d.ts +17 -0
- package/dist/create.js +29 -0
- package/dist/engine.d.ts +125 -0
- package/dist/engine.js +176 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +5 -0
- package/dist/extensions.generated.d.ts +1022 -0
- package/dist/extensions.generated.js +744 -0
- package/dist/extras.generated.d.ts +5803 -0
- package/dist/extras.generated.js +2600 -0
- package/dist/full.d.ts +21 -0
- package/dist/full.js +63 -0
- package/dist/gemoji.generated.d.ts +29 -0
- package/dist/gemoji.generated.js +30 -0
- package/dist/load.d.ts +21 -0
- package/dist/load.js +41 -0
- package/dist/mod.d.ts +25 -0
- package/dist/mod.js +24 -0
- package/dist/native.d.ts +115 -0
- package/dist/native.js +235 -0
- package/dist/scene-types.d.ts +223 -0
- package/dist/scene-types.js +1 -0
- package/dist/scene.d.ts +105 -0
- package/dist/scene.js +153 -0
- package/dist/ssr.d.ts +66 -0
- package/dist/ssr.js +98 -0
- package/dist/types.d.ts +232 -0
- package/dist/types.js +1 -0
- package/package.json +89 -5
- package/readme.md +96 -0
- package/wasm/canvist_headless_wasm.d.ts +216 -0
- package/wasm/canvist_headless_wasm.js +910 -0
- package/wasm/canvist_headless_wasm_bg.wasm +0 -0
- package/wasm/canvist_headless_wasm_bg.wasm.d.ts +44 -0
- package/wasm-full/canvist_headless_full.d.ts +223 -0
- package/wasm-full/canvist_headless_full.js +923 -0
- package/wasm-full/canvist_headless_full_bg.wasm +0 -0
- package/wasm-full/canvist_headless_full_bg.wasm.d.ts +45 -0
- package/wasm-scene/canvist_headless_scene.d.ts +332 -0
- package/wasm-scene/canvist_headless_scene.js +1231 -0
- package/wasm-scene/canvist_headless_scene_bg.wasm +0 -0
- package/wasm-scene/canvist_headless_scene_bg.wasm.d.ts +66 -0
- package/wasm-ssr/canvist_headless_ssr.d.ts +250 -0
- package/wasm-ssr/canvist_headless_ssr.js +1025 -0
- package/wasm-ssr/canvist_headless_ssr_bg.wasm +0 -0
- package/wasm-ssr/canvist_headless_ssr_bg.wasm.d.ts +50 -0
- package/README.md +0 -3
package/dist/scene.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* canvist canvas scenes on a server: load scene JSON or Yjs state, run
|
|
3
|
+
* canvas commands and inspector edits, and export SVG and PNG with the
|
|
4
|
+
* renderer the canvas draws with.
|
|
5
|
+
*
|
|
6
|
+
* Scenes live in their own WebAssembly module, so the document entry point
|
|
7
|
+
* stays small.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { createSceneEngine } from "@canvist/headless/scene";
|
|
11
|
+
*
|
|
12
|
+
* const engine = await createSceneEngine();
|
|
13
|
+
* using doc = engine.fromJson({
|
|
14
|
+
* type: "canvist-scene",
|
|
15
|
+
* version: 1,
|
|
16
|
+
* nodes: [{
|
|
17
|
+
* type: "page",
|
|
18
|
+
* children: [{
|
|
19
|
+
* type: "rectangle",
|
|
20
|
+
* props: { size: [120, 80], fills: [{ type: "solid", color: "#3b2cf5" }] },
|
|
21
|
+
* }],
|
|
22
|
+
* }],
|
|
23
|
+
* });
|
|
24
|
+
* const png = doc.exportPng({ scale: 2 });
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
import init, { WasmSceneDoc, WasmSceneEngine, } from "../wasm-scene/canvist_headless_scene.js";
|
|
30
|
+
import { bundled } from "./load.js";
|
|
31
|
+
export { isCanvistError } from "./engine.js";
|
|
32
|
+
const BUNDLED = new URL("../wasm-scene/canvist_headless_scene_bg.wasm", import.meta.url);
|
|
33
|
+
let loading;
|
|
34
|
+
/**
|
|
35
|
+
* Load the scene WebAssembly module once. Later calls return the first
|
|
36
|
+
* call's promise. Without a source, Node, Deno, and Bun read the bundled
|
|
37
|
+
* file; runtimes without a file system pass the module they imported.
|
|
38
|
+
*/
|
|
39
|
+
export function loadScene(source) {
|
|
40
|
+
loading ??= (async () => {
|
|
41
|
+
await init({ module_or_path: source ?? await bundled(BUNDLED) });
|
|
42
|
+
})();
|
|
43
|
+
return loading;
|
|
44
|
+
}
|
|
45
|
+
/** Load the scene module and create an engine with every built-in canvas extension. */
|
|
46
|
+
export async function createSceneEngine(options = {}) {
|
|
47
|
+
await loadScene(options.wasm);
|
|
48
|
+
return new SceneEngine(options.client ?? 1);
|
|
49
|
+
}
|
|
50
|
+
/** A set of canvas extensions that creates scenes. Create one with {@link createSceneEngine}. */
|
|
51
|
+
export class SceneEngine {
|
|
52
|
+
#wasm;
|
|
53
|
+
#client;
|
|
54
|
+
/** @internal Use {@link createSceneEngine}, which loads the module first. */
|
|
55
|
+
constructor(client) {
|
|
56
|
+
this.#client = client;
|
|
57
|
+
this.#wasm = new WasmSceneEngine(client);
|
|
58
|
+
}
|
|
59
|
+
/** Tools, commands, node types, and inspector sections, as Rust reports them. */
|
|
60
|
+
metadata() {
|
|
61
|
+
return JSON.parse(this.#wasm.metadata());
|
|
62
|
+
}
|
|
63
|
+
/** A scene with one empty page. */
|
|
64
|
+
create() {
|
|
65
|
+
return new SceneDoc(this.#wasm.create(), this.#client);
|
|
66
|
+
}
|
|
67
|
+
/** A scene from scene JSON, nested or flat. */
|
|
68
|
+
fromJson(json) {
|
|
69
|
+
return new SceneDoc(this.#wasm.fromJson(JSON.stringify(json)), this.#client);
|
|
70
|
+
}
|
|
71
|
+
/** A scene joined from a peer's v1 Yjs state. It is shared, so it produces updates. */
|
|
72
|
+
fromYjs(state) {
|
|
73
|
+
return new SceneDoc(this.#wasm.fromYjs(state), this.#client);
|
|
74
|
+
}
|
|
75
|
+
/** Release the engine's memory now rather than when it is collected. */
|
|
76
|
+
[Symbol.dispose]() {
|
|
77
|
+
this.#wasm.free();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** One scene. Every edit is one undo step. */
|
|
81
|
+
export class SceneDoc {
|
|
82
|
+
#wasm;
|
|
83
|
+
#client;
|
|
84
|
+
/** @internal Scenes come from a {@link SceneEngine}. */
|
|
85
|
+
constructor(wasm, client) {
|
|
86
|
+
this.#wasm = wasm;
|
|
87
|
+
this.#client = client;
|
|
88
|
+
}
|
|
89
|
+
/** The scene in the canonical flat form. */
|
|
90
|
+
toJson() {
|
|
91
|
+
return JSON.parse(this.#wasm.toJson());
|
|
92
|
+
}
|
|
93
|
+
/** Run canvas command `name` (see {@link SceneEngine.metadata}) against the selection. */
|
|
94
|
+
run(name, args = {}) {
|
|
95
|
+
this.#wasm.run(name, JSON.stringify(args));
|
|
96
|
+
}
|
|
97
|
+
/** Select nodes by id, for commands and inspector fields. */
|
|
98
|
+
select(ids) {
|
|
99
|
+
this.#wasm.select([...ids]);
|
|
100
|
+
}
|
|
101
|
+
/** The selected nodes' ids. */
|
|
102
|
+
selection() {
|
|
103
|
+
return this.#wasm.selection();
|
|
104
|
+
}
|
|
105
|
+
/** The inspector for the selection: sections of fields and their values. */
|
|
106
|
+
inspect() {
|
|
107
|
+
return JSON.parse(this.#wasm.inspect());
|
|
108
|
+
}
|
|
109
|
+
/** Set inspector field `key` on every selected node. */
|
|
110
|
+
setField(key, value) {
|
|
111
|
+
this.#wasm.setField(key, JSON.stringify(value));
|
|
112
|
+
}
|
|
113
|
+
/** Undo the latest change; returns whether there was one. */
|
|
114
|
+
undo() {
|
|
115
|
+
return this.#wasm.undo();
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Provide an image asset's pixels, `width` by `height` RGBA8. Exports
|
|
119
|
+
* draw an image without pixels as a grey placeholder; SVG exports
|
|
120
|
+
* reference provided images by asset id.
|
|
121
|
+
*/
|
|
122
|
+
addImage(asset, width, height, rgba) {
|
|
123
|
+
this.#wasm.addImage(asset, width, height, rgba);
|
|
124
|
+
}
|
|
125
|
+
/** The scene, or the nodes `options` names, as an SVG document. */
|
|
126
|
+
exportSvg(options = {}) {
|
|
127
|
+
return this.#wasm.exportSvg(JSON.stringify(options));
|
|
128
|
+
}
|
|
129
|
+
/** The scene, or the nodes `options` names, as a PNG image. */
|
|
130
|
+
exportPng(options = {}) {
|
|
131
|
+
return this.#wasm.exportPng(JSON.stringify(options));
|
|
132
|
+
}
|
|
133
|
+
/** Share the scene as Yjs client `clientId` (the engine's client by default). */
|
|
134
|
+
share(clientId = this.#client) {
|
|
135
|
+
this.#wasm.share(clientId);
|
|
136
|
+
}
|
|
137
|
+
/** The whole shared scene as one v1 Yjs update. */
|
|
138
|
+
encodeState() {
|
|
139
|
+
return this.#wasm.encodeState();
|
|
140
|
+
}
|
|
141
|
+
/** Merge a peer's v1 update; returns whether the scene changed. */
|
|
142
|
+
applyUpdate(update) {
|
|
143
|
+
return this.#wasm.applyUpdate(update);
|
|
144
|
+
}
|
|
145
|
+
/** The v1 updates this scene produced since the last call. */
|
|
146
|
+
takeUpdates() {
|
|
147
|
+
return this.#wasm.takeUpdates();
|
|
148
|
+
}
|
|
149
|
+
/** Release the scene's memory now rather than when it is collected. */
|
|
150
|
+
[Symbol.dispose]() {
|
|
151
|
+
this.#wasm.free();
|
|
152
|
+
}
|
|
153
|
+
}
|
package/dist/ssr.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type WasmSource } from "./load.ts";
|
|
2
|
+
import type { Content, Extensions, StarterKit, ThemeOptions } from "./types.ts";
|
|
3
|
+
export { type CanvistError, isCanvistError } from "./engine.ts";
|
|
4
|
+
export type { WasmSource } from "./load.ts";
|
|
5
|
+
export type { Content, StarterKit, ThemeOptions } from "./types.ts";
|
|
6
|
+
/**
|
|
7
|
+
* Load the rendering WebAssembly module once. Later calls return the first
|
|
8
|
+
* call's promise. Without a source, Node, Deno, and Bun read the bundled
|
|
9
|
+
* file; runtimes without a file system pass the module they imported.
|
|
10
|
+
*/
|
|
11
|
+
export declare function loadRenderer(source?: WasmSource): Promise<void>;
|
|
12
|
+
/** Options for {@link createRenderer}. */
|
|
13
|
+
export interface RendererOptions<E extends Extensions> {
|
|
14
|
+
/** The extensions documents use, TipTap's StarterKit by default. Pass the editor's. */
|
|
15
|
+
readonly extensions?: E;
|
|
16
|
+
/** The theme. Pass the browser editor's `theme`, or the HTML won't line up with it. */
|
|
17
|
+
readonly theme?: ThemeOptions;
|
|
18
|
+
/**
|
|
19
|
+
* TrueType or OpenType font files to measure SVG text with: the files
|
|
20
|
+
* the page's `@font-face` rules load. HTML is laid out by the browser
|
|
21
|
+
* and needs none.
|
|
22
|
+
*/
|
|
23
|
+
readonly fonts?: readonly (ArrayBuffer | ArrayBufferView)[];
|
|
24
|
+
/** Where the rendering module comes from; see {@link loadRenderer}. */
|
|
25
|
+
readonly wasm?: WasmSource;
|
|
26
|
+
}
|
|
27
|
+
/** How wide to render. */
|
|
28
|
+
export interface RenderOptions {
|
|
29
|
+
/**
|
|
30
|
+
* The content width in CSS pixels. It must equal the width of the
|
|
31
|
+
* element the browser editor hydrates in.
|
|
32
|
+
*/
|
|
33
|
+
readonly width: number;
|
|
34
|
+
}
|
|
35
|
+
/** Server-rendered HTML and the stylesheet it needs. */
|
|
36
|
+
export interface RenderedHtml {
|
|
37
|
+
/** A `<div class="cv-doc">` fragment. Put it inside the element `createEditor` hydrates. */
|
|
38
|
+
readonly html: string;
|
|
39
|
+
/** The stylesheet, scoped under `.cv-doc`. */
|
|
40
|
+
readonly css: string;
|
|
41
|
+
}
|
|
42
|
+
/** Load the rendering module and create a renderer. */
|
|
43
|
+
export declare function createRenderer<const E extends Extensions = StarterKit>(options?: RendererOptions<E>): Promise<Renderer<E>>;
|
|
44
|
+
/**
|
|
45
|
+
* Renders documents to HTML, CSS, and SVG. It caches text measurements,
|
|
46
|
+
* so keep one per server and reuse it. Create one with
|
|
47
|
+
* {@link createRenderer}.
|
|
48
|
+
*/
|
|
49
|
+
export declare class Renderer<E extends Extensions = StarterKit> {
|
|
50
|
+
#private;
|
|
51
|
+
/** @internal Use {@link createRenderer}, which loads the module first. */
|
|
52
|
+
constructor(extensions: E, theme: ThemeOptions);
|
|
53
|
+
/**
|
|
54
|
+
* Register a TrueType or OpenType font for SVG text, and return the
|
|
55
|
+
* family names it added, such as `["Inter"]`.
|
|
56
|
+
*/
|
|
57
|
+
registerFont(data: ArrayBuffer | ArrayBufferView): string[];
|
|
58
|
+
/** `content` as semantic HTML and its stylesheet. */
|
|
59
|
+
html(content: Content<E>, options: RenderOptions): RenderedHtml;
|
|
60
|
+
/** `content` as a complete HTML page with no body margin, ready to serve. */
|
|
61
|
+
page(content: Content<E>, options: RenderOptions): string;
|
|
62
|
+
/** The whole of `content` as an SVG image. */
|
|
63
|
+
svg(content: Content<E>, options: RenderOptions): string;
|
|
64
|
+
/** Release the renderer's memory now rather than when it is collected. */
|
|
65
|
+
[Symbol.dispose](): void;
|
|
66
|
+
}
|
package/dist/ssr.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side rendering for canvist documents: semantic HTML and a
|
|
3
|
+
* stylesheet laid out exactly as `@canvist/web` paints the document, so a
|
|
4
|
+
* page can show the text before any JavaScript runs and the live editor
|
|
5
|
+
* can take its place without moving a pixel. SVG images of the whole
|
|
6
|
+
* document, for previews and thumbnails, come from the same layout.
|
|
7
|
+
*
|
|
8
|
+
* Rendering lives in its own WebAssembly module, so the document entry
|
|
9
|
+
* point stays small. It measures text with its own copy of each font,
|
|
10
|
+
* never the system's: the bundled Atkinson Hyperlegible stands in for
|
|
11
|
+
* every family until you register yours.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { createRenderer } from "@canvist/headless/ssr";
|
|
15
|
+
*
|
|
16
|
+
* const renderer = await createRenderer({ theme: { scheme: "dark" } });
|
|
17
|
+
* const { html, css } = renderer.html(
|
|
18
|
+
* { format: "markdown", input: "# Release notes" },
|
|
19
|
+
* { width: 640 },
|
|
20
|
+
* );
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
import init, { WasmRenderer } from "../wasm-ssr/canvist_headless_ssr.js";
|
|
26
|
+
import { starterKit } from "./extensions.generated.js";
|
|
27
|
+
import { bundled } from "./load.js";
|
|
28
|
+
export { isCanvistError } from "./engine.js";
|
|
29
|
+
const BUNDLED = new URL("../wasm-ssr/canvist_headless_ssr_bg.wasm", import.meta.url);
|
|
30
|
+
let loading;
|
|
31
|
+
/**
|
|
32
|
+
* Load the rendering WebAssembly module once. Later calls return the first
|
|
33
|
+
* call's promise. Without a source, Node, Deno, and Bun read the bundled
|
|
34
|
+
* file; runtimes without a file system pass the module they imported.
|
|
35
|
+
*/
|
|
36
|
+
export function loadRenderer(source) {
|
|
37
|
+
loading ??= (async () => {
|
|
38
|
+
await init({ module_or_path: source ?? await bundled(BUNDLED) });
|
|
39
|
+
})();
|
|
40
|
+
return loading;
|
|
41
|
+
}
|
|
42
|
+
/** Load the rendering module and create a renderer. */
|
|
43
|
+
export async function createRenderer(options = {}) {
|
|
44
|
+
await loadRenderer(options.wasm);
|
|
45
|
+
const renderer = new Renderer((options.extensions ?? starterKit), options.theme ?? {});
|
|
46
|
+
for (const font of options.fonts ?? []) {
|
|
47
|
+
renderer.registerFont(font);
|
|
48
|
+
}
|
|
49
|
+
return renderer;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Renders documents to HTML, CSS, and SVG. It caches text measurements,
|
|
53
|
+
* so keep one per server and reuse it. Create one with
|
|
54
|
+
* {@link createRenderer}.
|
|
55
|
+
*/
|
|
56
|
+
export class Renderer {
|
|
57
|
+
#wasm;
|
|
58
|
+
/** @internal Use {@link createRenderer}, which loads the module first. */
|
|
59
|
+
constructor(extensions, theme) {
|
|
60
|
+
this.#wasm = new WasmRenderer(extensions.map((extension) => extension.name), JSON.stringify(theme));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Register a TrueType or OpenType font for SVG text, and return the
|
|
64
|
+
* family names it added, such as `["Inter"]`.
|
|
65
|
+
*/
|
|
66
|
+
registerFont(data) {
|
|
67
|
+
return this.#wasm.registerFont(bytesOf(data));
|
|
68
|
+
}
|
|
69
|
+
/** `content` as semantic HTML and its stylesheet. */
|
|
70
|
+
html(content, options) {
|
|
71
|
+
const [html = "", css = ""] = this.#wasm.html(...encoded(content), options.width);
|
|
72
|
+
return { html, css };
|
|
73
|
+
}
|
|
74
|
+
/** `content` as a complete HTML page with no body margin, ready to serve. */
|
|
75
|
+
page(content, options) {
|
|
76
|
+
const { html, css } = this.html(content, options);
|
|
77
|
+
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><style>${css}</style></head><body style="margin:0">${html}</body></html>`;
|
|
78
|
+
}
|
|
79
|
+
/** The whole of `content` as an SVG image. */
|
|
80
|
+
svg(content, options) {
|
|
81
|
+
return this.#wasm.svg(...encoded(content), options.width);
|
|
82
|
+
}
|
|
83
|
+
/** Release the renderer's memory now rather than when it is collected. */
|
|
84
|
+
[Symbol.dispose]() {
|
|
85
|
+
this.#wasm.free();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** `content` as the `[format, input]` pair the module reads. */
|
|
89
|
+
function encoded(content) {
|
|
90
|
+
return "format" in content
|
|
91
|
+
? [content.format, content.input]
|
|
92
|
+
: ["json", JSON.stringify(content)];
|
|
93
|
+
}
|
|
94
|
+
function bytesOf(data) {
|
|
95
|
+
return data instanceof ArrayBuffer
|
|
96
|
+
? new Uint8Array(data)
|
|
97
|
+
: new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
98
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types derived from extension metadata.
|
|
3
|
+
*
|
|
4
|
+
* Every extension descriptor is the metadata `canvist_core` produces,
|
|
5
|
+
* kept as a literal type. The types here read it, so a document's commands,
|
|
6
|
+
* queries, argument types, and JSON node names follow from the extensions
|
|
7
|
+
* an engine registers, with nothing written by hand per extension.
|
|
8
|
+
* Descriptions are comments in `extensions.generated.ts`, so they never
|
|
9
|
+
* ship in a browser bundle; `engine.metadata()` returns them as data.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import type { core, starterKit } from "./extensions.generated.ts";
|
|
14
|
+
/** The type of an argument, attribute, or query result. */
|
|
15
|
+
export type ValueSpec = {
|
|
16
|
+
readonly type: "bool";
|
|
17
|
+
} | {
|
|
18
|
+
readonly type: "int";
|
|
19
|
+
readonly min?: number;
|
|
20
|
+
readonly max?: number;
|
|
21
|
+
} | {
|
|
22
|
+
readonly type: "float";
|
|
23
|
+
} | {
|
|
24
|
+
readonly type: "string";
|
|
25
|
+
readonly enum?: readonly string[];
|
|
26
|
+
} | {
|
|
27
|
+
readonly type: "nullable";
|
|
28
|
+
readonly of: ValueSpec;
|
|
29
|
+
} | {
|
|
30
|
+
readonly type: "list";
|
|
31
|
+
readonly of: ValueSpec;
|
|
32
|
+
} | {
|
|
33
|
+
readonly type: "record";
|
|
34
|
+
readonly fields: readonly FieldSpec[];
|
|
35
|
+
};
|
|
36
|
+
/** A named, typed value. A `default` makes an argument optional. */
|
|
37
|
+
export interface FieldSpec {
|
|
38
|
+
readonly name: string;
|
|
39
|
+
/** What it means: in `engine.metadata()`, not in the generated descriptors. */
|
|
40
|
+
readonly doc?: string;
|
|
41
|
+
readonly type: ValueSpec;
|
|
42
|
+
readonly default?: unknown;
|
|
43
|
+
}
|
|
44
|
+
/** A block, mark, or inline embed an extension adds to the schema. */
|
|
45
|
+
export interface NodeSpec {
|
|
46
|
+
readonly kind: "block" | "mark" | "embed";
|
|
47
|
+
readonly name: string;
|
|
48
|
+
readonly attrs: readonly FieldSpec[];
|
|
49
|
+
}
|
|
50
|
+
/** A command an extension registers. */
|
|
51
|
+
export interface CommandSpec {
|
|
52
|
+
readonly name: string;
|
|
53
|
+
/** What it does: in `engine.metadata()`, not in the generated descriptors. */
|
|
54
|
+
readonly doc?: string;
|
|
55
|
+
readonly args: readonly FieldSpec[];
|
|
56
|
+
}
|
|
57
|
+
/** A query an extension registers. */
|
|
58
|
+
export interface QuerySpec {
|
|
59
|
+
readonly name: string;
|
|
60
|
+
/** What it answers: in `engine.metadata()`, not in the generated descriptors. */
|
|
61
|
+
readonly doc?: string;
|
|
62
|
+
readonly args: readonly FieldSpec[];
|
|
63
|
+
readonly returns: ValueSpec;
|
|
64
|
+
}
|
|
65
|
+
/** What one extension adds: its metadata. */
|
|
66
|
+
export interface ExtensionDescriptor {
|
|
67
|
+
readonly name: string;
|
|
68
|
+
readonly nodes: readonly NodeSpec[];
|
|
69
|
+
readonly commands: readonly CommandSpec[];
|
|
70
|
+
readonly queries: readonly QuerySpec[];
|
|
71
|
+
}
|
|
72
|
+
/** A list of extensions. */
|
|
73
|
+
export type Extensions = readonly ExtensionDescriptor[];
|
|
74
|
+
/** Integer ranges this short become unions of their members. */
|
|
75
|
+
type MaxRangeLength = 64;
|
|
76
|
+
/** `Min | Min + 1 | ... | Max`, or `number` for long or open ranges. */
|
|
77
|
+
type IntRange<Min extends number, Max extends number, Counter extends readonly unknown[] = [], Started extends boolean = false> = Counter["length"] extends MaxRangeLength ? number : Counter["length"] extends Max ? Counter["length"] : Started extends true ? Counter["length"] | IntRange<Min, Max, [...Counter, unknown], true> : Counter["length"] extends Min ? Counter["length"] | IntRange<Min, Max, [...Counter, unknown], true> : IntRange<Min, Max, [...Counter, unknown], false>;
|
|
78
|
+
/** The TypeScript type of a value with spec `S`. */
|
|
79
|
+
export type ValueOf<S> = S extends {
|
|
80
|
+
readonly type: "bool";
|
|
81
|
+
} ? boolean : S extends {
|
|
82
|
+
readonly type: "int";
|
|
83
|
+
readonly min: infer Min extends number;
|
|
84
|
+
readonly max: infer Max extends number;
|
|
85
|
+
} ? IntRange<Min, Max> : S extends {
|
|
86
|
+
readonly type: "int";
|
|
87
|
+
} | {
|
|
88
|
+
readonly type: "float";
|
|
89
|
+
} ? number : S extends {
|
|
90
|
+
readonly type: "string";
|
|
91
|
+
readonly enum: readonly (infer Option)[];
|
|
92
|
+
} ? Option : S extends {
|
|
93
|
+
readonly type: "string";
|
|
94
|
+
} ? string : S extends {
|
|
95
|
+
readonly type: "nullable";
|
|
96
|
+
readonly of: infer Inner;
|
|
97
|
+
} ? ValueOf<Inner> | null : S extends {
|
|
98
|
+
readonly type: "list";
|
|
99
|
+
readonly of: infer Inner;
|
|
100
|
+
} ? readonly ValueOf<Inner>[] : S extends {
|
|
101
|
+
readonly type: "record";
|
|
102
|
+
readonly fields: infer Fields extends readonly FieldSpec[];
|
|
103
|
+
} ? ArgsOf<Fields> : never;
|
|
104
|
+
type Simplify<T> = {
|
|
105
|
+
[Key in keyof T]: T[Key];
|
|
106
|
+
} & {};
|
|
107
|
+
/** The object of named arguments `Fields` describe. */
|
|
108
|
+
export type ArgsOf<Fields extends readonly FieldSpec[]> = Simplify<{
|
|
109
|
+
[Field in Fields[number] as Field extends {
|
|
110
|
+
readonly default: unknown;
|
|
111
|
+
} ? never : Field["name"]]: ValueOf<Field["type"]>;
|
|
112
|
+
} & {
|
|
113
|
+
[Field in Fields[number] as Field extends {
|
|
114
|
+
readonly default: unknown;
|
|
115
|
+
} ? Field["name"] : never]?: ValueOf<Field["type"]>;
|
|
116
|
+
}>;
|
|
117
|
+
/** The parameters of a command or query: none, optional, or required. */
|
|
118
|
+
export type ParamsOf<Fields extends readonly FieldSpec[]> = Fields extends readonly [] ? [] : Exclude<Fields[number], {
|
|
119
|
+
readonly default: unknown;
|
|
120
|
+
}> extends never ? [args?: ArgsOf<Fields>] : [args: ArgsOf<Fields>];
|
|
121
|
+
type CommandsIn<E extends Extensions> = E[number]["commands"][number];
|
|
122
|
+
type QueriesIn<E extends Extensions> = E[number]["queries"][number];
|
|
123
|
+
type NodesIn<E extends Extensions> = E[number]["nodes"][number];
|
|
124
|
+
/** Every command the extensions `E` register, by name. Each returns whether it applied. */
|
|
125
|
+
export type Commands<E extends Extensions> = {
|
|
126
|
+
readonly [Command in CommandsIn<E> as Command["name"]]: (...args: ParamsOf<Command["args"]>) => boolean;
|
|
127
|
+
};
|
|
128
|
+
/** Every query the extensions `E` register, by name. */
|
|
129
|
+
export type Queries<E extends Extensions> = {
|
|
130
|
+
readonly [Query in QueriesIn<E> as Query["name"]]: (...args: ParamsOf<Query["args"]>) => ValueOf<Query["returns"]>;
|
|
131
|
+
};
|
|
132
|
+
/** The name of a command the extensions `E` register. */
|
|
133
|
+
export type CommandName<E extends Extensions> = CommandsIn<E>["name"];
|
|
134
|
+
/** The name of a query the extensions `E` register. */
|
|
135
|
+
export type QueryName<E extends Extensions> = QueriesIn<E>["name"];
|
|
136
|
+
/** The parameters of the command named `N`. */
|
|
137
|
+
export type CommandParams<E extends Extensions, N extends CommandName<E>> = N extends CommandName<E> ? Commands<E>[N] extends (...args: infer Params) => boolean ? Params : never : never;
|
|
138
|
+
/** The parameters of the query named `N`. */
|
|
139
|
+
export type QueryParams<E extends Extensions, N extends QueryName<E>> = N extends QueryName<E> ? Queries<E>[N] extends (...args: infer Params) => unknown ? Params : never : never;
|
|
140
|
+
/** The answer of the query named `N`. */
|
|
141
|
+
export type QueryResult<E extends Extensions, N extends QueryName<E>> = Queries<E>[N] extends (...args: never) => infer Result ? Result : never;
|
|
142
|
+
/** The names of the blocks, marks, or embeds `E` add. */
|
|
143
|
+
export type NodeName<E extends Extensions, Kind extends NodeSpec["kind"]> = Extract<NodesIn<E>, {
|
|
144
|
+
readonly kind: Kind;
|
|
145
|
+
}>["name"];
|
|
146
|
+
type AttrsJson<Node extends NodeSpec> = Simplify<{
|
|
147
|
+
readonly [Field in Node["attrs"][number] as Field["name"]]?: ValueOf<Field["type"]>;
|
|
148
|
+
} & {
|
|
149
|
+
readonly id?: string;
|
|
150
|
+
}>;
|
|
151
|
+
/** A mark in ProseMirror JSON, for the marks `E` add. */
|
|
152
|
+
export type MarkJson<E extends Extensions> = {
|
|
153
|
+
[Node in Extract<NodesIn<E>, {
|
|
154
|
+
readonly kind: "mark";
|
|
155
|
+
}> as Node["name"]]: {
|
|
156
|
+
readonly type: Node["name"];
|
|
157
|
+
readonly attrs?: AttrsJson<Node>;
|
|
158
|
+
};
|
|
159
|
+
}[NodeName<E, "mark">];
|
|
160
|
+
/** Inline content in ProseMirror JSON: text, or the embeds `E` add. */
|
|
161
|
+
export type InlineJson<E extends Extensions> = {
|
|
162
|
+
readonly type: "text";
|
|
163
|
+
readonly text: string;
|
|
164
|
+
readonly marks?: readonly MarkJson<E>[];
|
|
165
|
+
} | {
|
|
166
|
+
[Node in Extract<NodesIn<E>, {
|
|
167
|
+
readonly kind: "embed";
|
|
168
|
+
}> as Node["name"]]: {
|
|
169
|
+
readonly type: Node["name"];
|
|
170
|
+
readonly attrs?: AttrsJson<Node>;
|
|
171
|
+
readonly marks?: readonly MarkJson<E>[];
|
|
172
|
+
};
|
|
173
|
+
}[NodeName<E, "embed">];
|
|
174
|
+
/** A block in ProseMirror JSON, for the blocks `E` add. */
|
|
175
|
+
export type BlockJson<E extends Extensions> = {
|
|
176
|
+
[Node in Extract<NodesIn<E>, {
|
|
177
|
+
readonly kind: "block";
|
|
178
|
+
}> as Node["name"]]: {
|
|
179
|
+
readonly type: Node["name"];
|
|
180
|
+
readonly attrs?: AttrsJson<Node>;
|
|
181
|
+
readonly content?: readonly (BlockJson<E> | InlineJson<E>)[];
|
|
182
|
+
};
|
|
183
|
+
}[NodeName<E, "block">];
|
|
184
|
+
/** A ProseMirror or TipTap document using only what `E` adds. */
|
|
185
|
+
export interface DocJson<E extends Extensions> {
|
|
186
|
+
readonly type: "doc";
|
|
187
|
+
readonly content?: readonly BlockJson<E>[];
|
|
188
|
+
}
|
|
189
|
+
/** A format documents are read from and written to. */
|
|
190
|
+
export type Format = "json" | "html" | "markdown" | "text";
|
|
191
|
+
/** The extensions every engine has, plus the ones it was created with. */
|
|
192
|
+
export type Registered<E extends Extensions> = readonly [...typeof core, ...E];
|
|
193
|
+
/** The extensions of TipTap's StarterKit. */
|
|
194
|
+
export type StarterKit = typeof starterKit;
|
|
195
|
+
/** A document to start from: text in a format, or ProseMirror JSON. */
|
|
196
|
+
export type Content<E extends Extensions> = {
|
|
197
|
+
readonly format: Format;
|
|
198
|
+
readonly input: string;
|
|
199
|
+
} | DocJson<Registered<E>>;
|
|
200
|
+
/**
|
|
201
|
+
* The StarterKit theme's options. The browser editor (`@canvist/web`) and
|
|
202
|
+
* the server renderer (`@canvist/headless/ssr`) take the same ones, and
|
|
203
|
+
* must be given the same values for server-rendered HTML to hydrate
|
|
204
|
+
* without moving.
|
|
205
|
+
*/
|
|
206
|
+
export interface ThemeOptions {
|
|
207
|
+
/** Light or dark colours. Default `"light"`. */
|
|
208
|
+
readonly scheme?: "light" | "dark";
|
|
209
|
+
/** The CSS `font-family` list for body text and headings. */
|
|
210
|
+
readonly bodyFamily?: string;
|
|
211
|
+
/** The CSS `font-family` list for code. */
|
|
212
|
+
readonly codeFamily?: string;
|
|
213
|
+
/** The body font size in pixels. Default 16. */
|
|
214
|
+
readonly size?: number;
|
|
215
|
+
/** The body line height as a multiple of the font size. Default 1.5. */
|
|
216
|
+
readonly lineHeight?: number;
|
|
217
|
+
/** CSS `letter-spacing` of body text, in pixels. Default 0. */
|
|
218
|
+
readonly letterSpacing?: number;
|
|
219
|
+
}
|
|
220
|
+
/** A point in a text block: a path from the root and a UTF-16 offset. */
|
|
221
|
+
export interface Position {
|
|
222
|
+
readonly path: readonly number[];
|
|
223
|
+
readonly offset: number;
|
|
224
|
+
}
|
|
225
|
+
/** A text selection, or a caret when the anchor and head are equal. */
|
|
226
|
+
export interface Range {
|
|
227
|
+
readonly anchor: Position;
|
|
228
|
+
readonly head: Position;
|
|
229
|
+
}
|
|
230
|
+
/** The stable name of each kind of failure, shared with Rust and Dart. */
|
|
231
|
+
export type ErrorCode = "config" | "parse" | "schema" | "unknownName" | "invalidArgs" | "invalidPosition" | "invalidKey" | "editor" | "snapshot" | "notShared" | "alreadyShared" | "collab" | "render";
|
|
232
|
+
export {};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,90 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@canvist/headless",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "canvist's document model and editing engine with no DOM: ProseMirror JSON, HTML, and Markdown conversion, typed commands, Yjs collaboration, and server-side rendering for Node, Deno, Bun, and Workers.",
|
|
5
|
+
"license": "Unlicense",
|
|
6
|
+
"homepage": "https://github.com/canvist/canvist/tree/main/packages/canvist-headless#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/canvist/canvist.git",
|
|
10
|
+
"directory": "packages/canvist-headless"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/canvist/canvist/issues",
|
|
14
|
+
"email": "support@canvist.dev"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/mod.d.ts",
|
|
20
|
+
"default": "./dist/mod.js"
|
|
21
|
+
},
|
|
22
|
+
"./extensions": {
|
|
23
|
+
"types": "./dist/extensions.generated.d.ts",
|
|
24
|
+
"default": "./dist/extensions.generated.js"
|
|
25
|
+
},
|
|
26
|
+
"./extras": {
|
|
27
|
+
"types": "./dist/extras.generated.d.ts",
|
|
28
|
+
"default": "./dist/extras.generated.js"
|
|
29
|
+
},
|
|
30
|
+
"./full": {
|
|
31
|
+
"types": "./dist/full.d.ts",
|
|
32
|
+
"default": "./dist/full.js"
|
|
33
|
+
},
|
|
34
|
+
"./native": {
|
|
35
|
+
"types": "./dist/native.d.ts",
|
|
36
|
+
"default": "./dist/native.js"
|
|
37
|
+
},
|
|
38
|
+
"./scene": {
|
|
39
|
+
"types": "./dist/scene.d.ts",
|
|
40
|
+
"default": "./dist/scene.js"
|
|
41
|
+
},
|
|
42
|
+
"./ssr": {
|
|
43
|
+
"types": "./dist/ssr.d.ts",
|
|
44
|
+
"default": "./dist/ssr.js"
|
|
45
|
+
},
|
|
46
|
+
"./wasm/canvist_headless_wasm_bg.wasm": "./wasm/canvist_headless_wasm_bg.wasm",
|
|
47
|
+
"./wasm-full/canvist_headless_full_bg.wasm": "./wasm-full/canvist_headless_full_bg.wasm",
|
|
48
|
+
"./wasm-scene/canvist_headless_scene_bg.wasm": "./wasm-scene/canvist_headless_scene_bg.wasm",
|
|
49
|
+
"./wasm-ssr/canvist_headless_ssr_bg.wasm": "./wasm-ssr/canvist_headless_ssr_bg.wasm"
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"dist",
|
|
53
|
+
"wasm/*.js",
|
|
54
|
+
"wasm/*.d.ts",
|
|
55
|
+
"wasm/*.wasm",
|
|
56
|
+
"wasm-full/*.js",
|
|
57
|
+
"wasm-full/*.d.ts",
|
|
58
|
+
"wasm-full/*.wasm",
|
|
59
|
+
"wasm-scene/*.js",
|
|
60
|
+
"wasm-scene/*.d.ts",
|
|
61
|
+
"wasm-scene/*.wasm",
|
|
62
|
+
"wasm-ssr/*.js",
|
|
63
|
+
"wasm-ssr/*.d.ts",
|
|
64
|
+
"wasm-ssr/*.wasm"
|
|
65
|
+
],
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=22"
|
|
68
|
+
},
|
|
69
|
+
"scripts": {
|
|
70
|
+
"build:wasm": "npm run build:wasm:document && npm run build:wasm:full && npm run build:wasm:scene && npm run build:wasm:ssr",
|
|
71
|
+
"build:wasm:document": "cd ../.. && wasm-pack build crates/canvist_headless_wasm --target web --profile wasm-release --out-dir ../../packages/canvist-headless/wasm --no-pack && rm packages/canvist-headless/wasm/.gitignore",
|
|
72
|
+
"build:wasm:full": "cd ../.. && wasm-pack build crates/canvist_headless_wasm --target web --profile wasm-release --out-dir ../../packages/canvist-headless/wasm-full --out-name canvist_headless_full --no-pack -- --features full && rm packages/canvist-headless/wasm-full/.gitignore",
|
|
73
|
+
"build:wasm:scene": "cd ../.. && wasm-pack build crates/canvist_headless_wasm --target web --profile wasm-release --out-dir ../../packages/canvist-headless/wasm-scene --out-name canvist_headless_scene --no-pack -- --features scene && rm packages/canvist-headless/wasm-scene/.gitignore",
|
|
74
|
+
"build:wasm:ssr": "cd ../.. && wasm-pack build crates/canvist_headless_wasm --target web --profile wasm-release --out-dir ../../packages/canvist-headless/wasm-ssr --out-name canvist_headless_ssr --no-pack -- --features ssr && rm packages/canvist-headless/wasm-ssr/.gitignore",
|
|
75
|
+
"generate": "node scripts/generate.ts",
|
|
76
|
+
"check:generated": "node scripts/generate.ts --check",
|
|
77
|
+
"check:types": "tsc --noEmit",
|
|
78
|
+
"test": "node --test tests/*.test.ts",
|
|
79
|
+
"bench": "node bench/overhead.ts",
|
|
80
|
+
"build": "tsc -p tsconfig.build.json",
|
|
81
|
+
"prepack": "npm run build"
|
|
82
|
+
},
|
|
83
|
+
"devDependencies": {
|
|
84
|
+
"@types/node": "^24.0.0",
|
|
85
|
+
"typescript": "^7.0.2",
|
|
86
|
+
"y-protocols": "^1.0.7",
|
|
87
|
+
"yjs": "^13.6.33",
|
|
88
|
+
"lib0": "^0.2.115"
|
|
89
|
+
}
|
|
90
|
+
}
|