@quillmark/wasm 0.112.0 → 0.114.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/CHANGELOG.md +1612 -0
- package/README.md +91 -66
- package/core/wasm.d.ts +153 -183
- package/core/wasm.js +283 -391
- package/core/wasm_bg.wasm +0 -0
- package/core/wasm_bg.wasm.d.ts +6 -11
- package/package.json +2 -2
- package/{backends/pdfform → render}/wasm.d.ts +204 -265
- package/{backends/pdfform → render}/wasm.js +364 -450
- package/{backends/typst → render}/wasm_bg.wasm +0 -0
- package/{backends/typst → render}/wasm_bg.wasm.d.ts +8 -15
- package/runtime/runtime.d.ts +88 -179
- package/runtime/runtime.js +144 -585
- package/backends/pdfform/wasm_bg.wasm +0 -0
- package/backends/pdfform/wasm_bg.wasm.d.ts +0 -107
- package/backends/typst/wasm.d.ts +0 -1511
- package/backends/typst/wasm.js +0 -3008
package/runtime/runtime.js
CHANGED
|
@@ -1,34 +1,13 @@
|
|
|
1
1
|
/* @ts-self-types="./runtime.d.ts" */
|
|
2
2
|
//
|
|
3
|
-
//
|
|
3
|
+
// The canonical consumer API, and the package's sole export. Its contract is
|
|
4
|
+
// `runtime.d.ts`, which a consumer's TypeScript reads through the package's
|
|
5
|
+
// `exports.types` map; the multi-binary design it implements is
|
|
6
|
+
// `prose/canon/BINDINGS.md`.
|
|
4
7
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// A handle from one memory cannot be used by another; this module hides that
|
|
9
|
-
// seam behind `await init()` and a static `Engine`.
|
|
10
|
-
//
|
|
11
|
-
// - `Quill` and `Document` ARE the core build's classes, handed out by the
|
|
12
|
-
// gate below, never subclasses or wrappers: that identity is what makes
|
|
13
|
-
// `instanceof` the whole membership test, so a handle either belongs to this
|
|
14
|
-
// copy or to another copy, and the second is always a consumer bug.
|
|
15
|
-
// `runtime.test.js` guards it (`Quill === CoreQuill`). No backend is loaded
|
|
16
|
-
// to use them, so the editor path never pays for a multi-MB binary.
|
|
17
|
-
//
|
|
18
|
-
// - `Engine` is the render dispatcher. It routes on `quill.backendId`, lazily
|
|
19
|
-
// imports that backend's build, clones the canonical `Quill`/`Document` into
|
|
20
|
-
// the backend's memory as data (`toTree`→`fromTree`, `toJson`→`fromJson`),
|
|
21
|
-
// renders, and never lets the backend handles escape.
|
|
22
|
-
//
|
|
23
|
-
// CLONE LIFETIMES: the per-call `Document` clone is transient, since
|
|
24
|
-
// documents are small and mutate freely. The `Quill` clone is CACHED,
|
|
25
|
-
// because re-cloning re-serializes its whole file tree, copies it into
|
|
26
|
-
// backend memory, and re-parses + re-validates the bundle every call. Each
|
|
27
|
-
// `Engine` memoizes it in a `WeakMap` keyed on the canonical `Quill`, so
|
|
28
|
-
// dropping the core quill makes the entry collectable and `--weak-refs`
|
|
29
|
-
// frees the backend handle. The contract this buys: a `Quill` instance's
|
|
30
|
-
// contents never change after construction — mutate by replacing the
|
|
31
|
-
// instance.
|
|
8
|
+
// A `Quill` instance's contents never change after construction — mutate by
|
|
9
|
+
// replacing the instance — which is what makes the cached backend clone below
|
|
10
|
+
// safe to hand out for the instance's whole life.
|
|
32
11
|
|
|
33
12
|
// Local bindings, so this module can augment them: `quill.writer(doc)` is
|
|
34
13
|
// patched onto the prototype below, and `instanceof` reads them directly. The
|
|
@@ -41,34 +20,13 @@ import initCore, { Quill, Document } from '../core/wasm.js';
|
|
|
41
20
|
// Resolution-time, so `node:fs` never enters a browser graph.
|
|
42
21
|
import { toModuleSource } from '#quillmark-env';
|
|
43
22
|
import { importMarkdown, exportMarkdown, rebase, mapPos, mapMarks } from '../core/wasm.js';
|
|
44
|
-
import { parseDocPath, formatDocPath } from '../core/wasm.js';
|
|
23
|
+
import { parseDocPath, formatDocPath, formatDiagnostic } from '../core/wasm.js';
|
|
45
24
|
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
// THE GATE IS THE ONLY DOOR: `init` resolves to the core surface and nothing
|
|
53
|
-
// here exports it statically, so a handle is unobtainable without having
|
|
54
|
-
// awaited, and package.json's `exports` map carries exactly one entry. The
|
|
55
|
-
// guarded surface cannot be async (`Quill.fromTree` and `quill.seedDocument`
|
|
56
|
-
// return synchronously), so there is nowhere to hide an await except in front.
|
|
57
|
-
//
|
|
58
|
-
// WHAT STAYS A STATIC EXPORT is what needs no instance: `MAIN_CARD_ADDR`, the
|
|
59
|
-
// open-set guards, and `isQuillmarkError` are pure JS over plain objects.
|
|
60
|
-
// `Engine`, `LiveSession` and the four writer/reader classes stay static too,
|
|
61
|
-
// gated by their ARGUMENTS instead — every verb takes a `Quill` or both
|
|
62
|
-
// handles, so a caller who has not awaited cannot produce an argument, and the
|
|
63
|
-
// two constructors taking no handle reach no wasm. None carries a static
|
|
64
|
-
// method, the one member shape an argument cannot gate. `gate.test.js` drives
|
|
65
|
-
// the whole static surface before `init`. Holding them out of the gate also
|
|
66
|
-
// keeps them tree-shakable.
|
|
67
|
-
//
|
|
68
|
-
// FAILURE DELIVERY follows the FUNCTION kind, not the failure kind: a sync verb
|
|
69
|
-
// throws, a promise-returning verb rejects, and nothing does both. `init` is the
|
|
70
|
-
// one promise-returning export not declared `async`, because the memo is
|
|
71
|
-
// returned by identity; its conflict guard rejects explicitly to hold the rule.
|
|
25
|
+
// A `--target web` build exports its classes synchronously but carries no wasm
|
|
26
|
+
// instance until something instantiates it. This module owns that for core,
|
|
27
|
+
// behind one awaited gate; `Engine` owns it for the backends, inside their lazy
|
|
28
|
+
// load. Which values that gate holds and which stay static exports:
|
|
29
|
+
// `prose/canon/BINDINGS.md`.
|
|
72
30
|
|
|
73
31
|
/**
|
|
74
32
|
* The gated surface: the core build's values, which are exactly the ones its
|
|
@@ -92,7 +50,8 @@ const CORE_SURFACE = Object.freeze({
|
|
|
92
50
|
mapPos,
|
|
93
51
|
mapMarks,
|
|
94
52
|
parseDocPath,
|
|
95
|
-
formatDocPath
|
|
53
|
+
formatDocPath,
|
|
54
|
+
formatDiagnostic
|
|
96
55
|
});
|
|
97
56
|
|
|
98
57
|
/** The in-flight or settled core instantiation, resolving to `CORE_SURFACE`.
|
|
@@ -104,38 +63,8 @@ let coreInit;
|
|
|
104
63
|
let coreInitSource;
|
|
105
64
|
|
|
106
65
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* ```js
|
|
110
|
-
* import { init } from '@quillmark/wasm';
|
|
111
|
-
* const { Quill, Document } = await init();
|
|
112
|
-
* ```
|
|
113
|
-
*
|
|
114
|
-
* The classes and the free functions come from here and nowhere else, so the
|
|
115
|
-
* pre-init mistake is not expressible. Destructure at each entry point (route
|
|
116
|
-
* loader, hydration path, worker) rather than threading one result around: the
|
|
117
|
-
* gate is memoized, so every await after the first is free.
|
|
118
|
-
*
|
|
119
|
-
* Identical in every environment: in a browser the binary is fetched and
|
|
120
|
-
* streamed, under Node it is read off disk, and the call site is the same line.
|
|
121
|
-
*
|
|
122
|
-
* Idempotent and concurrency-safe: every non-conflicting call returns the same
|
|
123
|
-
* promise, so several entry points cost one instantiation. A failed init clears
|
|
124
|
-
* the memo, so a retry is possible.
|
|
125
|
-
*
|
|
126
|
-
* Both failures reject (§ "Initialization", FAILURE DELIVERY): one `catch`
|
|
127
|
-
* around `await init(...)` covers `runtime::init_conflict` and
|
|
128
|
-
* `runtime::init_failed` alike.
|
|
129
|
-
*
|
|
130
|
-
* @param {import('../core/wasm.js').InitInput} [source] override the binary's
|
|
131
|
-
* source (bytes, a `Response`, a `WebAssembly.Module`, a URL) for hosts that
|
|
132
|
-
* route assets themselves or embed the binary. Pass it on the FIRST call; a
|
|
133
|
-
* later call passing a *different* source rejects with
|
|
134
|
-
* `runtime::init_conflict` rather than silently ignoring it. Passing the same
|
|
135
|
-
* value again is fine, so several entry points may each `await init(BYTES)`
|
|
136
|
-
* against one constant.
|
|
137
|
-
* @returns {Promise<import('./runtime.js').CoreSurface>} the core surface, once
|
|
138
|
-
* its instance is live
|
|
66
|
+
* @param {import('../core/wasm.js').InitInput} [source]
|
|
67
|
+
* @returns {Promise<import('./runtime.js').CoreSurface>}
|
|
139
68
|
*/
|
|
140
69
|
export function init(source) {
|
|
141
70
|
if (coreInit) {
|
|
@@ -195,46 +124,17 @@ async function instantiateCore(source) {
|
|
|
195
124
|
}
|
|
196
125
|
}
|
|
197
126
|
|
|
198
|
-
// ── The main-card address ───────────────────────────────────────────────────
|
|
199
127
|
/**
|
|
200
|
-
* The main card's address: the default target of the card-scoped verbs
|
|
201
|
-
* (`storeFields` / `storeExt` / `commitFields` / …). A named, `CardAddr`-typed
|
|
202
|
-
* alias for the empty address `{}`, so a main-card write names its target:
|
|
203
|
-
* `doc.storeFields(MAIN_CARD_ADDR, fields)`. It IS `{}` (frozen), a pure alias:
|
|
204
|
-
* `{}` and `undefined` stay equally valid. Card axis only: a card selector,
|
|
205
|
-
* never a field address.
|
|
206
128
|
* @type {import('../core/wasm.js').CardAddr}
|
|
207
129
|
*/
|
|
208
130
|
export const MAIN_CARD_ADDR = Object.freeze({});
|
|
209
131
|
|
|
210
|
-
// ── The variant discriminant key ────────────────────────────────────────────
|
|
211
132
|
/**
|
|
212
|
-
* The key carrying the discriminant inside a variant-bearing enum's value.
|
|
213
|
-
*
|
|
214
|
-
* A field declaring `variants:` rests as a container, `{value: <member>, …that
|
|
215
|
-
* member's fields}`, so reading or writing one means naming this key:
|
|
216
|
-
* `doc.storeFields(MAIN_CARD_ADDR, { classification: { [VARIANT_DISCRIMINANT_KEY]: 'CUI' } })`.
|
|
217
|
-
* It crosses the boundary inside untyped container data, with no type to read
|
|
218
|
-
* it off.
|
|
219
|
-
*
|
|
220
|
-
* Reserved: no variant may declare a field under it
|
|
221
|
-
* (`quill::variant_reserved_field_name`), and `QuillFieldSchema.variants`,
|
|
222
|
-
* keyed by member, never contains it.
|
|
223
133
|
* @type {'value'}
|
|
224
134
|
*/
|
|
225
135
|
export const VARIANT_DISCRIMINANT_KEY = 'value';
|
|
226
136
|
|
|
227
137
|
/**
|
|
228
|
-
* Narrow an unknown caught value to a `QuillmarkError`, the error every
|
|
229
|
-
* fallible method in this package throws: a real `Error` with a non-empty
|
|
230
|
-
* `diagnostics` array attached (same entry shape as `RenderResult.warnings`).
|
|
231
|
-
*
|
|
232
|
-
* Structural by necessity AND by design: the WASM layer constructs a plain
|
|
233
|
-
* `Error` and attaches the property (there is no error class to `instanceof`),
|
|
234
|
-
* and a structural check narrows errors from any build or WASM instance in the
|
|
235
|
-
* page. The deliberate exception to § "Handles from another copy": an error is
|
|
236
|
-
* data, not a handle, so nothing is gained by refusing one that crossed.
|
|
237
|
-
*
|
|
238
138
|
* @param {unknown} e
|
|
239
139
|
* @returns {e is Error & { diagnostics: import('../core/wasm.js').Diagnostic[] }}
|
|
240
140
|
*/
|
|
@@ -258,62 +158,35 @@ function quillmarkError(code, message, hint) {
|
|
|
258
158
|
return err;
|
|
259
159
|
}
|
|
260
160
|
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
// `toTree` serialize either way) and is not done: it leaves a package where some
|
|
269
|
-
// verbs work and some throw, and it hides a cliff, since a crossed read is a
|
|
270
|
-
// whole-document `toJson` + `fromJson` and a form reading fifty fields pays
|
|
271
|
-
// fifty round trips.
|
|
272
|
-
//
|
|
273
|
-
// What the checks deliver is the ERROR, not the rejection. wasm-bindgen's glue
|
|
274
|
-
// already rejects a foreign class wherever a method declares a reference
|
|
275
|
-
// parameter, but its `_assertClass` throws a bare `Error` reading
|
|
276
|
-
// `expected instance of Document` at a value that IS a `Document`, so
|
|
277
|
-
// `isQuillmarkError` returns false and the failure leaves this package's error
|
|
278
|
-
// contract. The checks front-run it with a `QuillmarkError` naming both cause
|
|
279
|
-
// and cure, and cover the seams with no `_assertClass` to front-run: `Engine`
|
|
280
|
-
// and `LiveSession.update` cross into backend memory as data, where a foreign
|
|
281
|
-
// handle would silently work at the price of that round trip and a per-copy
|
|
282
|
-
// split of the quill clone cache.
|
|
161
|
+
// These checks deliver the ERROR, not the rejection, at the seams that cross
|
|
162
|
+
// into backend memory as DATA — `Engine` and `LiveSession.update` — where a
|
|
163
|
+
// foreign handle would otherwise work silently at the price of a whole-document
|
|
164
|
+
// round trip and a per-copy split of the quill clone cache, and at the four
|
|
165
|
+
// writer/reader binds, whose constructors hold both handles. A method declaring
|
|
166
|
+
// a reference parameter needs none of this: wasm-bindgen's own `_assertClass`
|
|
167
|
+
// refuses a foreign class there.
|
|
283
168
|
|
|
284
|
-
/** Per class: the
|
|
169
|
+
/** Per class: the diagnostic for a value that is not one of THIS copy's handles. */
|
|
285
170
|
const HANDLE_KINDS = {
|
|
286
171
|
Quill: {
|
|
287
172
|
code: 'runtime::not_a_quill',
|
|
288
|
-
|
|
289
|
-
hint: 'Pass a Quill built by Quill.fromTree.'
|
|
173
|
+
hint: 'Pass a Quill built by Quill.fromTree. A Quill from another copy of @quillmark/wasm is refused too — each copy is its own WASM linear memory and its own class — so run `npm ls @quillmark/wasm` and dedupe to one.'
|
|
290
174
|
},
|
|
291
175
|
Document: {
|
|
292
176
|
code: 'runtime::not_a_document',
|
|
293
|
-
|
|
294
|
-
hint: 'Pass a Document built by Document.fromMarkdown / fromJson or quill.seedDocument.'
|
|
177
|
+
hint: 'Pass a Document built by Document.fromMarkdown / fromStored or quill.seedDocument. A Document from another copy of @quillmark/wasm is refused too — each copy is its own WASM linear memory and its own class — so run `npm ls @quillmark/wasm` and dedupe to one.'
|
|
295
178
|
}
|
|
296
179
|
};
|
|
297
180
|
|
|
298
181
|
/**
|
|
299
|
-
* The rejection for a value that is not one of this copy's handles.
|
|
300
|
-
* so two diagnostics: a value carrying the class's serializer is that class
|
|
301
|
-
* from ANOTHER copy (dedupe the install), anything else is the wrong argument
|
|
302
|
-
* (fix the call).
|
|
182
|
+
* The rejection for a value that is not one of this copy's handles.
|
|
303
183
|
* @param {unknown} value
|
|
304
184
|
* @param {string} method
|
|
305
185
|
* @param {'Quill' | 'Document'} className
|
|
306
186
|
* @returns {Error & { diagnostics: import('../core/wasm.js').Diagnostic[] }}
|
|
307
187
|
*/
|
|
308
188
|
function notLocal(value, method, className) {
|
|
309
|
-
const { code,
|
|
310
|
-
if (value && typeof (/** @type {any} */ (value)[probe]) === 'function') {
|
|
311
|
-
return quillmarkError(
|
|
312
|
-
'runtime::foreign_handle',
|
|
313
|
-
`${method}: the ${className} belongs to a different copy of @quillmark/wasm. Handles never cross between copies: each copy is its own WASM linear memory and its own ${className} class.`,
|
|
314
|
-
'Two copies of @quillmark/wasm are installed. Run `npm ls @quillmark/wasm` and dedupe to one.'
|
|
315
|
-
);
|
|
316
|
-
}
|
|
189
|
+
const { code, hint } = HANDLE_KINDS[className];
|
|
317
190
|
return quillmarkError(
|
|
318
191
|
code,
|
|
319
192
|
`${method}: expected a ${className}, got ${value === null ? 'null' : typeof value}.`,
|
|
@@ -343,213 +216,26 @@ function requireLocalQuill(quill, method) {
|
|
|
343
216
|
throw notLocal(quill, method, 'Quill');
|
|
344
217
|
}
|
|
345
218
|
|
|
346
|
-
// Marker for the patches below. `Symbol.for`, not a module-local `Symbol()`:
|
|
347
|
-
// re-evaluating THIS module (Vite HMR, a Vitest worker sharing a module graph)
|
|
348
|
-
// against an already-patched (because cached) core build must see the existing
|
|
349
|
-
// marker, or each pass wraps the previous wrapper.
|
|
350
|
-
const HANDLE_CHECKED = Symbol.for('@quillmark/wasm:handle-checked');
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Replace `proto[name]` with `wrap(original)`, once.
|
|
354
|
-
* @param {object} proto
|
|
355
|
-
* @param {string} name
|
|
356
|
-
* @param {(original: Function) => Function} wrap
|
|
357
|
-
*/
|
|
358
|
-
function patchHandleChecked(proto, name, wrap) {
|
|
359
|
-
const original = /** @type {any} */ (proto)[name];
|
|
360
|
-
if (typeof original !== 'function' || original[HANDLE_CHECKED]) return;
|
|
361
|
-
const patched = wrap(original);
|
|
362
|
-
/** @type {any} */ (patched)[HANDLE_CHECKED] = true;
|
|
363
|
-
// Keep the method's name so a stack trace still reads `Quill.validate`.
|
|
364
|
-
Object.defineProperty(patched, 'name', { value: name, configurable: true });
|
|
365
|
-
/** @type {any} */ (proto)[name] = patched;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// The core methods declaring a `&Document` parameter. Each already refuses a
|
|
369
|
-
// foreign handle inside `_assertClass`; the patch makes the refusal legible.
|
|
370
|
-
patchHandleChecked(Document.prototype, 'equals', (original) =>
|
|
371
|
-
function equals(/** @type {any} */ other) {
|
|
372
|
-
requireLocalDoc(other, 'Document.equals');
|
|
373
|
-
return original.call(this, other);
|
|
374
|
-
}
|
|
375
|
-
);
|
|
376
|
-
for (const name of /** @type {const} */ (['validate', 'resolve', 'conform'])) {
|
|
377
|
-
// Named once per patch, not per call: `validate` runs per keystroke.
|
|
378
|
-
const method = `Quill.${name}`;
|
|
379
|
-
patchHandleChecked(Quill.prototype, name, (original) =>
|
|
380
|
-
function (/** @type {any} */ doc) {
|
|
381
|
-
requireLocalDoc(doc, method);
|
|
382
|
-
return original.call(this, doc);
|
|
383
|
-
}
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// The typed writer/reader primitives (`Document._commitField` and friends) take
|
|
388
|
-
// the QUILL by reference, so they hit the same `_assertClass` from the other
|
|
389
|
-
// direction. Checked at the four writer/reader classes below, not patched onto
|
|
390
|
-
// `Document`: a foreign document carries its OWN prototype, so patching this
|
|
391
|
-
// copy's would never run.
|
|
392
|
-
|
|
393
|
-
// ── Open-set discriminant guards ────────────────────────────────────────────
|
|
394
|
-
// `ContentIsland.type`, `ContentMark.type`, `ContentLine.kind`, and
|
|
395
|
-
// `ContentContainer.container` are OPEN sets: each union carries a residual
|
|
396
|
-
// `{ …: string; … }` arm, so a bare `x.type === 'table'` check never narrows the
|
|
397
|
-
// payload, TS keeps the residual arm live (a `string` can be `'table'`),
|
|
398
|
-
// leaving `props` / the mark payload / `level` opaque at every consumer. These
|
|
399
|
-
// are the checked narrowing path: on the true branch the payload's pinned shape
|
|
400
|
-
// is asserted. Only the payload-carrying arms get a guard: an island always
|
|
401
|
-
// carries `props`, and a `link`/`anchor` mark, a `heading`/`code` line and a
|
|
402
|
-
// `list_item` container each carry their payload in `attrs`; the payload-free
|
|
403
|
-
// arms (`strong`/`emph`/`underline`/`strike`/`code` marks, `para`/`island`/
|
|
404
|
-
// `rule` lines, `quote`) omit `attrs` and narrow to nothing. An unrecognized
|
|
405
|
-
// discriminant fails every guard and carries the same `attrs` a known one
|
|
406
|
-
// would.
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* @param {import('../core/wasm.js').ContentIsland} island
|
|
410
|
-
* @returns {island is import('../core/wasm.js').ContentIsland & { type: 'table'; props: import('../core/wasm.js').TableProps }}
|
|
411
|
-
*/
|
|
412
|
-
export function isTableIsland(island) {
|
|
413
|
-
return island.type === 'table';
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* @param {import('../core/wasm.js').ContentIsland} island
|
|
418
|
-
* @returns {island is import('../core/wasm.js').ContentIsland & { type: 'image'; props: import('../core/wasm.js').ImageProps }}
|
|
419
|
-
*/
|
|
420
|
-
export function isImageIsland(island) {
|
|
421
|
-
return island.type === 'image';
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* @param {import('../core/wasm.js').ContentMark} mark
|
|
426
|
-
* @returns {mark is import('../core/wasm.js').ContentMark & { type: 'link'; attrs: { url: string } }}
|
|
427
|
-
*/
|
|
428
|
-
export function isLinkMark(mark) {
|
|
429
|
-
return mark.type === 'link';
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* @param {import('../core/wasm.js').ContentMark} mark
|
|
434
|
-
* @returns {mark is import('../core/wasm.js').ContentMark & { type: 'anchor'; attrs: { id: string } }}
|
|
435
|
-
*/
|
|
436
|
-
export function isAnchorMark(mark) {
|
|
437
|
-
return mark.type === 'anchor';
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* @param {import('../core/wasm.js').ContentLine} line
|
|
442
|
-
* @returns {line is import('../core/wasm.js').ContentLine & { kind: 'heading'; attrs: { level: number } }}
|
|
443
|
-
*/
|
|
444
|
-
export function isHeadingLine(line) {
|
|
445
|
-
return line.kind === 'heading';
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* @param {import('../core/wasm.js').ContentLine} line
|
|
450
|
-
* @returns {line is import('../core/wasm.js').ContentLine & { kind: 'code'; attrs?: { lang?: string } }}
|
|
451
|
-
*/
|
|
452
|
-
export function isCodeLine(line) {
|
|
453
|
-
return line.kind === 'code';
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* @param {import('../core/wasm.js').ContentContainer} container
|
|
458
|
-
* @returns {container is import('../core/wasm.js').ContentContainer & { container: 'list_item'; attrs: { ordered: boolean; start: number; ordinal: number }; instance?: number }}
|
|
459
|
-
*/
|
|
460
|
-
export function isListItemContainer(container) {
|
|
461
|
-
return container.container === 'list_item';
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// ── Open-set membership guards ──────────────────────────────────────────────
|
|
465
|
-
// The guards above each answer "is this arm X". These four answer "is this a
|
|
466
|
-
// value this build knows?", the question any read-modify-write consumer must
|
|
467
|
-
// ask, since lowering an edit restates every line's kind and containers. A
|
|
468
|
-
// predicate rather than an exported name list, because the tables below are
|
|
469
|
-
// upstream's business: they are pinned against the Rust source by
|
|
470
|
-
// `tests/known_names_drift.rs`, so adding a built-in means editing there, here,
|
|
471
|
-
// and the TS unions in `src/engine.rs` in one commit.
|
|
472
|
-
//
|
|
473
|
-
// These classify unknown *tags*, not unknown *payloads on known tags*. A future
|
|
474
|
-
// `kind: "footnote"` with a sibling `ref` loses `ref` at a consumer that predates
|
|
475
|
-
// it either way.
|
|
476
|
-
|
|
477
|
-
const KNOWN_LINE_KINDS = new Set(['para', 'heading', 'code', 'island', 'rule']);
|
|
478
|
-
const KNOWN_CONTAINERS = new Set(['list_item', 'quote']);
|
|
479
|
-
const KNOWN_MARK_TYPES = new Set(['strong', 'emph', 'underline', 'strike', 'code', 'link', 'anchor']);
|
|
480
|
-
const KNOWN_ISLAND_TYPES = new Set(['table', 'image']);
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* @param {import('../core/wasm.js').ContentLine} line
|
|
484
|
-
* @returns {line is import('../core/wasm.js').ContentLine & { kind: string; attrs: unknown }}
|
|
485
|
-
*/
|
|
486
|
-
export function isUnknownLine(line) {
|
|
487
|
-
return typeof line?.kind === 'string' && !KNOWN_LINE_KINDS.has(line.kind);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* @param {import('../core/wasm.js').ContentContainer} container
|
|
492
|
-
* @returns {container is import('../core/wasm.js').ContentContainer & { container: string; attrs: unknown }}
|
|
493
|
-
*/
|
|
494
|
-
export function isUnknownContainer(container) {
|
|
495
|
-
return typeof container?.container === 'string' && !KNOWN_CONTAINERS.has(container.container);
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* @param {import('../core/wasm.js').ContentMark} mark
|
|
500
|
-
* @returns {mark is import('../core/wasm.js').ContentMark & { type: string; attrs: unknown }}
|
|
501
|
-
*/
|
|
502
|
-
export function isUnknownMark(mark) {
|
|
503
|
-
return typeof mark?.type === 'string' && !KNOWN_MARK_TYPES.has(mark.type);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* @param {import('../core/wasm.js').ContentIsland} island
|
|
508
|
-
* @returns {island is import('../core/wasm.js').ContentIsland & { type: string; props: unknown }}
|
|
509
|
-
*/
|
|
510
|
-
export function isUnknownIsland(island) {
|
|
511
|
-
return typeof island?.type === 'string' && !KNOWN_ISLAND_TYPES.has(island.type);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// ── Container run boundaries ────────────────────────────────────────────────
|
|
515
|
-
// `ContentContainer.instance` is what keeps two adjacent runs of one shape
|
|
516
|
-
// apart, and only a writer knows where a boundary is: the flat `containers`
|
|
517
|
-
// form cannot tell a list ending beside another from one list of two items, so
|
|
518
|
-
// an omitted discriminator welds them and nothing reports it.
|
|
519
|
-
//
|
|
520
219
|
// WELD_KEYS is the rule `Container::same_weld` owns upstream: which `attrs`
|
|
521
220
|
// entries two adjacent runs must share for the markdown projection to read them
|
|
522
|
-
// as one
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
// `tests/known_names_drift.rs` can pin it against the Rust predicate.
|
|
221
|
+
// as one. `start` is not among them, since CommonMark reads only a list's first
|
|
222
|
+
// number — a subset, which is why every container needs its own entry. A table
|
|
223
|
+
// rather than a switch, so `tests/known_names_drift.rs` can pin it against the
|
|
224
|
+
// Rust predicate.
|
|
527
225
|
|
|
528
226
|
const WELD_KEYS = { list_item: ['ordered'], quote: [] };
|
|
529
227
|
|
|
530
|
-
function sameJson(a, b) {
|
|
531
|
-
if (a === b) return true;
|
|
532
|
-
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;
|
|
533
|
-
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
534
|
-
const ka = Object.keys(a);
|
|
535
|
-
return (
|
|
536
|
-
ka.length === Object.keys(b).length &&
|
|
537
|
-
ka.every((k) => Object.hasOwn(b, k) && sameJson(a[k], b[k]))
|
|
538
|
-
);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
228
|
/**
|
|
542
229
|
* @param {import('../core/wasm.js').ContentContainer} a
|
|
543
230
|
* @param {import('../core/wasm.js').ContentContainer} b
|
|
544
231
|
* @returns {boolean}
|
|
545
232
|
*/
|
|
546
233
|
function weldsWith(a, b) {
|
|
547
|
-
// A malformed value welds with nothing
|
|
548
|
-
// answer rather than throw.
|
|
234
|
+
// A malformed value welds with nothing: answer rather than throw.
|
|
549
235
|
if (typeof a?.container !== 'string' || a.container !== b?.container) return false;
|
|
550
|
-
// `hasOwn`, so a tag colliding with an `Object.prototype` member
|
|
551
|
-
//
|
|
552
|
-
if (!Object.hasOwn(WELD_KEYS, a.container)) return
|
|
236
|
+
// `hasOwn`, so a tag colliding with an `Object.prototype` member answers
|
|
237
|
+
// `false` rather than reaching a function.
|
|
238
|
+
if (!Object.hasOwn(WELD_KEYS, a.container)) return false;
|
|
553
239
|
return WELD_KEYS[a.container].every((k) => a.attrs?.[k] === b.attrs?.[k]);
|
|
554
240
|
}
|
|
555
241
|
|
|
@@ -579,7 +265,7 @@ export function assignInstances(runs) {
|
|
|
579
265
|
* generated entry's own `wasm !== undefined` guard only catches a call arriving
|
|
580
266
|
* after one finished, not one already in flight.
|
|
581
267
|
*
|
|
582
|
-
* @param {string} id
|
|
268
|
+
* @param {string} id the build's name, for the failure message
|
|
583
269
|
* @param {() => Promise<any>} importThunk the dynamic `import()`
|
|
584
270
|
* @param {() => URL} wasmUrl the build's binary, resolved at call time
|
|
585
271
|
* @returns {() => Promise<any>} resolves to a ready-to-use module
|
|
@@ -598,108 +284,89 @@ function backendLoad(id, importThunk, wasmUrl) {
|
|
|
598
284
|
throw Object.assign(
|
|
599
285
|
quillmarkError(
|
|
600
286
|
'runtime::backend_load_failed',
|
|
601
|
-
`Engine: could not load the '${id}'
|
|
287
|
+
`Engine: could not load the '${id}' build: ${
|
|
602
288
|
/** @type {any} */ (cause)?.message ?? cause
|
|
603
289
|
}`,
|
|
604
|
-
'The
|
|
290
|
+
'The build ships beside the package files; check the network tab for a 404 or an HTML response.'
|
|
605
291
|
),
|
|
606
292
|
{ cause }
|
|
607
293
|
);
|
|
608
294
|
}));
|
|
609
295
|
}
|
|
610
296
|
|
|
611
|
-
//
|
|
612
|
-
// binary into the eager graph and defeat lazy loading.
|
|
613
|
-
//
|
|
614
|
-
//
|
|
615
|
-
// `
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
297
|
+
// The render build is NEVER statically imported here: that would pull a
|
|
298
|
+
// multi-MB binary into the eager graph and defeat lazy loading. Both built-in
|
|
299
|
+
// backends ship in it, so they share one `load`; each `formats` manifest
|
|
300
|
+
// mirrors that backend's Rust `SUPPORTED_FORMATS`, pinned by a
|
|
301
|
+
// `runtime.test.js` drift guard that renders once and compares.
|
|
302
|
+
const RENDER_BUILD = backendLoad(
|
|
303
|
+
'render',
|
|
304
|
+
() => import('../render/wasm.js'),
|
|
305
|
+
() => new URL('../render/wasm_bg.wasm', import.meta.url)
|
|
306
|
+
);
|
|
620
307
|
const DEFAULT_BACKENDS = {
|
|
621
308
|
typst: {
|
|
622
|
-
load:
|
|
623
|
-
|
|
624
|
-
() => import('../backends/typst/wasm.js'),
|
|
625
|
-
() => new URL('../backends/typst/wasm_bg.wasm', import.meta.url)
|
|
626
|
-
),
|
|
627
|
-
formats: ['pdf', 'svg', 'png'], // crates/backends/typst/src/lib.rs SUPPORTED_FORMATS
|
|
628
|
-
canvas: true // has svg/png → formats_support_canvas == true
|
|
309
|
+
load: RENDER_BUILD,
|
|
310
|
+
formats: ['pdf', 'svg', 'png'] // crates/backends/typst/src/lib.rs SUPPORTED_FORMATS
|
|
629
311
|
},
|
|
630
|
-
|
|
631
|
-
load:
|
|
632
|
-
|
|
633
|
-
() => import('../backends/pdfform/wasm.js'),
|
|
634
|
-
() => new URL('../backends/pdfform/wasm_bg.wasm', import.meta.url)
|
|
635
|
-
),
|
|
636
|
-
// crates/backends/pdfform/src/lib.rs SUPPORTED_FORMATS == [Pdf, Svg, Png]
|
|
637
|
-
formats: ['pdf', 'svg', 'png'],
|
|
638
|
-
canvas: true // has svg/png → formats_support_canvas == true
|
|
312
|
+
acroform: {
|
|
313
|
+
load: RENDER_BUILD,
|
|
314
|
+
formats: ['pdf'] // crates/backends/acroform/src/lib.rs SUPPORTED_FORMATS
|
|
639
315
|
}
|
|
640
316
|
};
|
|
641
317
|
|
|
642
318
|
/**
|
|
643
319
|
* Validate a backend registry descriptor, naming the backend id on any
|
|
644
320
|
* malformed entry. Failing at construction rather than deep inside a render is
|
|
645
|
-
* what lets
|
|
321
|
+
* what lets `supportedFormats` answer from the manifest unconditionally.
|
|
646
322
|
* @param {string} id
|
|
647
323
|
* @param {unknown} entry
|
|
648
|
-
* @returns {{ load: () => Promise<unknown>, formats: string[]
|
|
324
|
+
* @returns {{ load: () => Promise<unknown>, formats: string[] }}
|
|
649
325
|
*/
|
|
650
326
|
function validateBackend(id, entry) {
|
|
651
327
|
if (!entry || typeof entry !== 'object') {
|
|
652
|
-
throw new Error(
|
|
653
|
-
`Engine: backend '${id}' must be a descriptor { load, formats, canvas }.`
|
|
654
|
-
);
|
|
328
|
+
throw new Error(`Engine: backend '${id}' must be a descriptor { load, formats }.`);
|
|
655
329
|
}
|
|
656
|
-
const { load, formats
|
|
330
|
+
const { load, formats } = /** @type {any} */ (entry);
|
|
657
331
|
if (typeof load !== 'function') {
|
|
658
332
|
throw new Error(`Engine: backend '${id}' descriptor needs a callable 'load'.`);
|
|
659
333
|
}
|
|
660
334
|
if (!Array.isArray(formats)) {
|
|
661
335
|
throw new Error(`Engine: backend '${id}' descriptor needs a 'formats' array.`);
|
|
662
336
|
}
|
|
663
|
-
|
|
664
|
-
throw new Error(`Engine: backend '${id}' descriptor needs a boolean 'canvas'.`);
|
|
665
|
-
}
|
|
666
|
-
return { load, formats, canvas };
|
|
337
|
+
return { load, formats };
|
|
667
338
|
}
|
|
668
339
|
|
|
669
|
-
/**
|
|
670
|
-
* Render dispatcher over the canonical `Quill`/`Document`. One `Engine`
|
|
671
|
-
* instance can drive every backend; it resolves the right backend build from
|
|
672
|
-
* each quill's declared `backendId` and loads it lazily on first use.
|
|
673
|
-
*/
|
|
674
340
|
export class Engine {
|
|
675
|
-
/**
|
|
341
|
+
/**
|
|
342
|
+
* The three caches below key on a descriptor's `load` thunk, the build's
|
|
343
|
+
* identity: two backend ids sharing one build (the built-ins) share one
|
|
344
|
+
* module, one engine and one clone cache, and a consumer's override build
|
|
345
|
+
* gets its own.
|
|
346
|
+
*/
|
|
347
|
+
/** load → Promise<build module>, memoized so each build loads once. */
|
|
676
348
|
#modules = new Map();
|
|
677
|
-
/**
|
|
349
|
+
/** load → that build's engine instance (the WASM backend registry). */
|
|
678
350
|
#engines = new Map();
|
|
679
|
-
/** backendId → descriptor `{ load, formats
|
|
351
|
+
/** backendId → descriptor `{ load, formats }`. */
|
|
680
352
|
#loaders;
|
|
681
353
|
/**
|
|
682
|
-
*
|
|
354
|
+
* load → WeakMap<canonical Quill, build-memory clone>, caching the
|
|
683
355
|
* expensive materialization. WeakMap so dropping the canonical quill makes
|
|
684
356
|
* its clone collectable, and wasm-bindgen weak-refs then free the handle.
|
|
685
|
-
* @type {Map<
|
|
357
|
+
* @type {Map<() => Promise<unknown>, WeakMap<object, any>>}
|
|
686
358
|
*/
|
|
687
359
|
#quillClones = new Map();
|
|
688
360
|
|
|
689
361
|
/**
|
|
690
|
-
*
|
|
691
|
-
*
|
|
692
|
-
*
|
|
693
|
-
*
|
|
694
|
-
* throw here, at construction.
|
|
695
|
-
*
|
|
696
|
-
* `load` resolves to a READY module: a registrant shipping its own
|
|
697
|
-
* `--target web` build instantiates inside the thunk. More than one
|
|
698
|
-
* `Engine` may call it, so memoize (the built-ins do, at module scope).
|
|
362
|
+
* `load` must resolve to a READY module: a registrant shipping its own
|
|
363
|
+
* `--target web` build instantiates inside the thunk, and more than one
|
|
364
|
+
* `Engine` may call it, so memoize (the built-ins do, at module scope).
|
|
365
|
+
* @param {{ backends?: Record<string, { load: () => Promise<unknown>, formats: string[] }> }} [options]
|
|
699
366
|
*/
|
|
700
367
|
constructor(options) {
|
|
701
368
|
const merged = { ...DEFAULT_BACKENDS, ...(options?.backends ?? {}) };
|
|
702
|
-
/** @type {Record<string, { load: () => Promise<unknown>, formats: string[]
|
|
369
|
+
/** @type {Record<string, { load: () => Promise<unknown>, formats: string[] }>} */
|
|
703
370
|
const loaders = {};
|
|
704
371
|
for (const [id, entry] of Object.entries(merged)) {
|
|
705
372
|
loaders[id] = validateBackend(id, entry);
|
|
@@ -708,17 +375,19 @@ export class Engine {
|
|
|
708
375
|
}
|
|
709
376
|
|
|
710
377
|
/**
|
|
711
|
-
* The registered descriptor for `backendId`, or the
|
|
712
|
-
* throw
|
|
378
|
+
* The registered descriptor for `backendId`, or the
|
|
379
|
+
* `engine::backend_not_found` throw core raises for the same condition.
|
|
380
|
+
* Touches no binary.
|
|
713
381
|
* @param {string} backendId
|
|
714
|
-
* @returns {{ load: () => Promise<unknown>, formats: string[]
|
|
382
|
+
* @returns {{ load: () => Promise<unknown>, formats: string[] }}
|
|
715
383
|
*/
|
|
716
384
|
#descriptorFor(backendId) {
|
|
717
385
|
const descriptor = this.#loaders[backendId];
|
|
718
386
|
if (!descriptor) {
|
|
719
|
-
throw
|
|
720
|
-
|
|
721
|
-
|
|
387
|
+
throw quillmarkError(
|
|
388
|
+
'engine::backend_not_found',
|
|
389
|
+
`Engine: backend '${backendId}' not registered or not enabled.`,
|
|
390
|
+
`Available backends: ${Object.keys(this.#loaders).join(', ') || '(none)'}`
|
|
722
391
|
);
|
|
723
392
|
}
|
|
724
393
|
return descriptor;
|
|
@@ -738,51 +407,52 @@ export class Engine {
|
|
|
738
407
|
}
|
|
739
408
|
|
|
740
409
|
/**
|
|
741
|
-
* Resolve (and lazily load) the
|
|
410
|
+
* Resolve (and lazily load) the build module + its engine for `backendId`.
|
|
742
411
|
* @param {string} backendId
|
|
743
412
|
* @returns {Promise<{ mod: any, engine: any }>}
|
|
744
413
|
*/
|
|
745
414
|
async #resolveBackend(backendId) {
|
|
746
|
-
const
|
|
415
|
+
const { load } = this.#descriptorFor(backendId);
|
|
747
416
|
|
|
748
|
-
let modPromise = this.#modules.get(
|
|
417
|
+
let modPromise = this.#modules.get(load);
|
|
749
418
|
if (!modPromise) {
|
|
750
419
|
// Set the promise synchronously (before any await) so concurrent first
|
|
751
420
|
// renders share ONE import. Self-heal on failure so a transient load
|
|
752
421
|
// error doesn't poison every later attempt.
|
|
753
422
|
modPromise = Promise.resolve()
|
|
754
|
-
.then(
|
|
423
|
+
.then(load)
|
|
755
424
|
.catch((err) => {
|
|
756
|
-
this.#modules.delete(
|
|
425
|
+
this.#modules.delete(load);
|
|
757
426
|
throw err;
|
|
758
427
|
});
|
|
759
|
-
this.#modules.set(
|
|
428
|
+
this.#modules.set(load, modPromise);
|
|
760
429
|
}
|
|
761
430
|
const mod = await modPromise;
|
|
762
431
|
|
|
763
|
-
let engine = this.#engines.get(
|
|
432
|
+
let engine = this.#engines.get(load);
|
|
764
433
|
if (!engine) {
|
|
765
434
|
engine = new mod.Quillmark();
|
|
766
|
-
this.#engines.set(
|
|
435
|
+
this.#engines.set(load, engine);
|
|
767
436
|
}
|
|
768
437
|
return { mod, engine };
|
|
769
438
|
}
|
|
770
439
|
|
|
771
440
|
/**
|
|
772
|
-
* Get (or materialize-and-cache) the
|
|
773
|
-
* under `backendId
|
|
774
|
-
* pre-await snapshot, since the canonical handle may be freed by now.
|
|
775
|
-
* @param {any} mod the
|
|
441
|
+
* Get (or materialize-and-cache) the build-memory `Quill` clone for `quill`
|
|
442
|
+
* under `backendId`'s build. On a miss the clone is built from `tree`, the
|
|
443
|
+
* caller's pre-await snapshot, since the canonical handle may be freed by now.
|
|
444
|
+
* @param {any} mod the build module
|
|
776
445
|
* @param {string} backendId
|
|
777
446
|
* @param {object} quill the canonical instance (cache key only)
|
|
778
447
|
* @param {Map<string, Uint8Array> | null} tree pre-await snapshot; `null` on a cache hit
|
|
779
|
-
* @returns {any} the
|
|
448
|
+
* @returns {any} the build-memory quill clone
|
|
780
449
|
*/
|
|
781
450
|
#cachedQuillClone(mod, backendId, quill, tree) {
|
|
782
|
-
|
|
451
|
+
const { load } = this.#descriptorFor(backendId);
|
|
452
|
+
let perQuill = this.#quillClones.get(load);
|
|
783
453
|
if (!perQuill) {
|
|
784
454
|
perQuill = new WeakMap();
|
|
785
|
-
this.#quillClones.set(
|
|
455
|
+
this.#quillClones.set(load, perQuill);
|
|
786
456
|
}
|
|
787
457
|
let backendQuill = perQuill.get(quill);
|
|
788
458
|
if (!backendQuill) {
|
|
@@ -797,11 +467,15 @@ export class Engine {
|
|
|
797
467
|
* memory and run `fn` against the backend engine. Only `render`/`open` call
|
|
798
468
|
* this, so `doc` is always present.
|
|
799
469
|
*
|
|
800
|
-
* OWNERSHIP WINDOW: both caller handles are snapshotted (`doc.
|
|
801
|
-
* `quill.toTree()` on a clone-cache miss) BEFORE the first
|
|
802
|
-
* load below is a real suspension point, so reading the
|
|
803
|
-
* would race a caller that `free()`s them as soon as this
|
|
804
|
-
* promise ("null pointer passed to rust").
|
|
470
|
+
* OWNERSHIP WINDOW: both caller handles are snapshotted (`doc.toStored()` and
|
|
471
|
+
* `doc.warnings`, and `quill.toTree()` on a clone-cache miss) BEFORE the first
|
|
472
|
+
* await. The backend load below is a real suspension point, so reading the
|
|
473
|
+
* handles after it would race a caller that `free()`s them as soon as this
|
|
474
|
+
* call returns its promise ("null pointer passed to rust").
|
|
475
|
+
*
|
|
476
|
+
* `docWarnings` rides the context because the storage DTO does not carry
|
|
477
|
+
* them: `fromStored` clears the load's warnings, so the backend clone knows
|
|
478
|
+
* nothing of them and `render` splices the snapshot back in.
|
|
805
479
|
*
|
|
806
480
|
* Clone lifetimes differ by design: the `doc` clone is TRANSIENT, freed in
|
|
807
481
|
* the `finally` of every call, while the `quill` clone is CACHED and is not
|
|
@@ -811,13 +485,16 @@ export class Engine {
|
|
|
811
485
|
* @param {string} method the caller's name, for the rejection message
|
|
812
486
|
* @param {Quill} quill
|
|
813
487
|
* @param {Document} doc
|
|
814
|
-
* @param {(ctx: { mod: any, engine: any, quill: any, doc: any }) => any} fn
|
|
488
|
+
* @param {(ctx: { mod: any, engine: any, quill: any, doc: any, docWarnings: any[] }) => any} fn
|
|
815
489
|
*/
|
|
816
490
|
async #withClones(method, quill, doc, fn) {
|
|
817
491
|
const backendId = this.#backendOf(quill, method);
|
|
818
492
|
requireLocalDoc(doc, method);
|
|
819
|
-
const docJson = doc.
|
|
820
|
-
const
|
|
493
|
+
const docJson = doc.toStored();
|
|
494
|
+
const docWarnings = doc.warnings;
|
|
495
|
+
const quillTree = this.#quillClones.get(this.#descriptorFor(backendId).load)?.has(quill)
|
|
496
|
+
? null
|
|
497
|
+
: quill.toTree();
|
|
821
498
|
const { mod, engine } = await this.#resolveBackend(backendId);
|
|
822
499
|
// The doc clone and `fn` share one try so the clone is freed even if a
|
|
823
500
|
// later step throws; the cached quill clone is intentionally not freed.
|
|
@@ -825,31 +502,33 @@ export class Engine {
|
|
|
825
502
|
const backendQuill = this.#cachedQuillClone(mod, backendId, quill, quillTree);
|
|
826
503
|
let backendDoc = null;
|
|
827
504
|
try {
|
|
828
|
-
backendDoc = mod.Document.
|
|
829
|
-
return fn({ mod, engine, quill: backendQuill, doc: backendDoc });
|
|
505
|
+
backendDoc = mod.Document.fromStored(docJson);
|
|
506
|
+
return fn({ mod, engine, quill: backendQuill, doc: backendDoc, docWarnings });
|
|
830
507
|
} finally {
|
|
831
508
|
backendDoc?.free();
|
|
832
509
|
}
|
|
833
510
|
}
|
|
834
511
|
|
|
835
512
|
/**
|
|
836
|
-
* Render `doc` against `quill` in one shot. Both handles are read
|
|
837
|
-
* synchronously before the first await.
|
|
838
513
|
* @param {Quill} quill
|
|
839
514
|
* @param {Document} doc
|
|
840
|
-
* @param {object} [options] render options (`{ format, ppi, pages,
|
|
515
|
+
* @param {object} [options] render options (`{ format, ppi, pages, regions }`)
|
|
841
516
|
* @returns {Promise<import('./runtime.js').RenderResult>}
|
|
842
517
|
*/
|
|
843
518
|
async render(quill, doc, options) {
|
|
844
|
-
return this.#withClones(
|
|
845
|
-
engine.render(
|
|
519
|
+
return this.#withClones(
|
|
520
|
+
'engine.render(quill, doc)',
|
|
521
|
+
quill,
|
|
522
|
+
doc,
|
|
523
|
+
({ engine, quill: q, doc: d, docWarnings }) => {
|
|
524
|
+
const result = engine.render(q, d, options ?? undefined);
|
|
525
|
+
result.warnings = docWarnings.concat(result.warnings);
|
|
526
|
+
return result;
|
|
527
|
+
}
|
|
846
528
|
);
|
|
847
529
|
}
|
|
848
530
|
|
|
849
531
|
/**
|
|
850
|
-
* Open a live render session. It retains what `update` needs, so the
|
|
851
|
-
* transient clones are freed before this returns; the caller owns the session
|
|
852
|
-
* and must `.free()` it.
|
|
853
532
|
* @param {Quill} quill
|
|
854
533
|
* @param {Document} doc
|
|
855
534
|
* @returns {Promise<LiveSession>}
|
|
@@ -864,8 +543,6 @@ export class Engine {
|
|
|
864
543
|
}
|
|
865
544
|
|
|
866
545
|
/**
|
|
867
|
-
* The output formats `quill`'s backend can emit: an always-free probe over
|
|
868
|
-
* the descriptor's manifest. `async` for API stability; it awaits nothing.
|
|
869
546
|
* @param {Quill} quill
|
|
870
547
|
* @returns {Promise<import('./runtime.js').OutputFormat[]>}
|
|
871
548
|
*/
|
|
@@ -874,29 +551,17 @@ export class Engine {
|
|
|
874
551
|
// Defensive copy so callers can't mutate the shared manifest.
|
|
875
552
|
return descriptor.formats.slice();
|
|
876
553
|
}
|
|
877
|
-
|
|
878
|
-
/**
|
|
879
|
-
* Whether `quill`'s backend can paint to a canvas: a pre-session estimate over
|
|
880
|
-
* the descriptor's manifest, so it can answer `true` where the resulting
|
|
881
|
-
* `LiveSession.supportsCanvas` answers `false`.
|
|
882
|
-
* @param {Quill} quill
|
|
883
|
-
* @returns {Promise<boolean>}
|
|
884
|
-
*/
|
|
885
|
-
async supportsCanvas(quill) {
|
|
886
|
-
const descriptor = this.#descriptorFor(this.#backendOf(quill, 'engine.supportsCanvas(quill)'));
|
|
887
|
-
return descriptor.canvas;
|
|
888
|
-
}
|
|
889
554
|
}
|
|
890
555
|
|
|
891
556
|
/**
|
|
892
|
-
* Thin wrapper over a backend's live render session
|
|
893
|
-
*
|
|
894
|
-
*
|
|
557
|
+
* Thin wrapper over a backend's live render session. The quill/document clones
|
|
558
|
+
* it was opened from have already been freed: the session retains what `update`
|
|
559
|
+
* needs.
|
|
895
560
|
*/
|
|
896
561
|
export class LiveSession {
|
|
897
562
|
/**
|
|
898
|
-
* @param {
|
|
899
|
-
* @param {{ Document: {
|
|
563
|
+
* @param {object} inner the render build's LiveSession, whose members the delegations below name
|
|
564
|
+
* @param {{ Document: { fromStored(json: string): any } }} mod the session's backend build, used to materialize `update` documents in its linear memory
|
|
900
565
|
*/
|
|
901
566
|
constructor(inner, mod) {
|
|
902
567
|
this.#inner = inner;
|
|
@@ -913,7 +578,7 @@ export class LiveSession {
|
|
|
913
578
|
requireLocalDoc(doc, 'session.update(doc)');
|
|
914
579
|
let backendDoc = null;
|
|
915
580
|
try {
|
|
916
|
-
backendDoc = this.#mod.Document.
|
|
581
|
+
backendDoc = this.#mod.Document.fromStored(doc.toStored());
|
|
917
582
|
return this.#inner.update(backendDoc);
|
|
918
583
|
} finally {
|
|
919
584
|
backendDoc?.free();
|
|
@@ -926,15 +591,6 @@ export class LiveSession {
|
|
|
926
591
|
get backendId() {
|
|
927
592
|
return this.#inner.backendId;
|
|
928
593
|
}
|
|
929
|
-
/**
|
|
930
|
-
* `true` iff `paint`/`pageSize` will succeed for THIS compile: the
|
|
931
|
-
* authoritative answer, which can be `false` where `Engine.supportsCanvas`
|
|
932
|
-
* answered `true` for the same quill.
|
|
933
|
-
* @returns {boolean}
|
|
934
|
-
*/
|
|
935
|
-
get supportsCanvas() {
|
|
936
|
-
return this.#inner.supportsCanvas;
|
|
937
|
-
}
|
|
938
594
|
get warnings() {
|
|
939
595
|
return this.#inner.warnings;
|
|
940
596
|
}
|
|
@@ -1009,28 +665,11 @@ export class LiveSession {
|
|
|
1009
665
|
}
|
|
1010
666
|
}
|
|
1011
667
|
|
|
1012
|
-
//
|
|
1013
|
-
//
|
|
1014
|
-
//
|
|
1015
|
-
//
|
|
1016
|
-
// schema, so each `commit*` method takes the `quill` handle as its first
|
|
1017
|
-
// argument. These pure-JS classes restore the Rust ergonomics: bind `quill` +
|
|
1018
|
-
// `doc` once, then issue `set` / `setAll` / `card(i).set`.
|
|
1019
|
-
//
|
|
1020
|
-
// They hold JS references to the caller's EXISTING handles (no WASM object of
|
|
1021
|
-
// their own, no `free()` burden, no second owner of either handle) and every
|
|
1022
|
-
// write delegates straight to the underlying `commit*` verb: a schema field is
|
|
1023
|
-
// typed-committed (coerced to canonical form, mismatch throws now), and a name
|
|
1024
|
-
// the schema does not declare throws `UnknownField` rather than falling to the
|
|
1025
|
-
// opaque store, on the typed path an undeclared name is a typo. Opaque storage
|
|
1026
|
-
// stays available through the raw addressed `Document.storeField` verb.
|
|
668
|
+
// These pure-JS classes bind a `quill` + `doc` pair once and delegate to the
|
|
669
|
+
// `_commit*` / `_reader*` verbs, which take the quill per call. They hold JS
|
|
670
|
+
// references to the caller's existing handles, so there is no WASM object of
|
|
671
|
+
// their own, no `free()` burden, and no second owner of either handle.
|
|
1027
672
|
|
|
1028
|
-
/**
|
|
1029
|
-
* A {@link Document} bound to its {@link Quill} for typed writes: the JS twin
|
|
1030
|
-
* of Rust's `quill.writer(&mut doc)`. Writes target the main card; use
|
|
1031
|
-
* {@link card} for a composable card. Holds both handles by reference and owns
|
|
1032
|
-
* neither, so there is nothing to `free()`.
|
|
1033
|
-
*/
|
|
1034
673
|
export class DocumentWriter {
|
|
1035
674
|
#quill;
|
|
1036
675
|
#doc;
|
|
@@ -1044,13 +683,10 @@ export class DocumentWriter {
|
|
|
1044
683
|
this.#quill = quill;
|
|
1045
684
|
this.#doc = doc;
|
|
1046
685
|
}
|
|
1047
|
-
/** The bound document: the same instance passed in, mutated in place. */
|
|
1048
686
|
get document() {
|
|
1049
687
|
return this.#doc;
|
|
1050
688
|
}
|
|
1051
689
|
/**
|
|
1052
|
-
* Typed-commit one main-card field (strict coerce, mismatch throws now).
|
|
1053
|
-
* Throws `UnknownField` for a name the schema does not declare.
|
|
1054
690
|
* @param {string} name
|
|
1055
691
|
* @param {unknown} value
|
|
1056
692
|
* @returns {void}
|
|
@@ -1059,9 +695,6 @@ export class DocumentWriter {
|
|
|
1059
695
|
return this.#doc._commitField(this.#quill, name, value);
|
|
1060
696
|
}
|
|
1061
697
|
/**
|
|
1062
|
-
* Typed-commit several main-card fields atomically: nothing is applied on
|
|
1063
|
-
* error (throws a per-field diagnostic bundle, including an `UnknownField`
|
|
1064
|
-
* for each undeclared name).
|
|
1065
698
|
* @param {Record<string, unknown>} fields
|
|
1066
699
|
* @returns {void}
|
|
1067
700
|
*/
|
|
@@ -1069,8 +702,6 @@ export class DocumentWriter {
|
|
|
1069
702
|
return this.#doc._commitFields(this.#quill, MAIN_CARD_ADDR, fields);
|
|
1070
703
|
}
|
|
1071
704
|
/**
|
|
1072
|
-
* Revise the main body from markdown; anchors rebase. A body carries no field
|
|
1073
|
-
* schema, so this is the content lane's `revise` reached through the writer.
|
|
1074
705
|
* @param {string} markdown
|
|
1075
706
|
* @returns {import('../core/wasm.js').Delta}
|
|
1076
707
|
*/
|
|
@@ -1078,10 +709,6 @@ export class DocumentWriter {
|
|
|
1078
709
|
return this.#doc.revise({}, markdown);
|
|
1079
710
|
}
|
|
1080
711
|
/**
|
|
1081
|
-
* Revise the content main-card field `name` from authored text: typed *and*
|
|
1082
|
-
* anchor-preserving. Anchors rebase, then the diffed result is
|
|
1083
|
-
* schema-conformed. The codec comes from the declared type: `richtext` diffs
|
|
1084
|
-
* markdown, `plaintext` the literal text.
|
|
1085
712
|
* @param {string} name
|
|
1086
713
|
* @param {string} text
|
|
1087
714
|
* @returns {import('../core/wasm.js').Delta}
|
|
@@ -1090,10 +717,6 @@ export class DocumentWriter {
|
|
|
1090
717
|
return this.#doc._reviseField(this.#quill, name, text);
|
|
1091
718
|
}
|
|
1092
719
|
/**
|
|
1093
|
-
* Build a composable card of `kind`, typed-commit `fields` onto it, set its
|
|
1094
|
-
* body from optional markdown, and place it. Transactional: the card is
|
|
1095
|
-
* committed in full before it joins the document, so a rejected field, kind,
|
|
1096
|
-
* body, or position leaves the document untouched.
|
|
1097
720
|
* @param {string} kind
|
|
1098
721
|
* @param {Record<string, unknown>} [fields]
|
|
1099
722
|
* @param {string} [body]
|
|
@@ -1111,9 +734,6 @@ export class DocumentWriter {
|
|
|
1111
734
|
return this.#doc.removeCard(index);
|
|
1112
735
|
}
|
|
1113
736
|
/**
|
|
1114
|
-
* A {@link CardWriter} bound to the composable card at `index`, checked
|
|
1115
|
-
* lazily at the write. It holds `index`, not the card, so a
|
|
1116
|
-
* `removeCard`/`addCard` between binding and writing silently retargets it.
|
|
1117
737
|
* @param {number} index
|
|
1118
738
|
* @returns {CardWriter}
|
|
1119
739
|
*/
|
|
@@ -1122,11 +742,6 @@ export class DocumentWriter {
|
|
|
1122
742
|
}
|
|
1123
743
|
}
|
|
1124
744
|
|
|
1125
|
-
/**
|
|
1126
|
-
* A single composable card bound to its {@link Quill} for typed writes, from
|
|
1127
|
-
* {@link DocumentWriter.card}. Same `set` / `setAll` verbs as
|
|
1128
|
-
* {@link DocumentWriter}, targeting the card at its bound index.
|
|
1129
|
-
*/
|
|
1130
745
|
export class CardWriter {
|
|
1131
746
|
#quill;
|
|
1132
747
|
#doc;
|
|
@@ -1143,22 +758,16 @@ export class CardWriter {
|
|
|
1143
758
|
this.#doc = doc;
|
|
1144
759
|
this.#index = index;
|
|
1145
760
|
}
|
|
1146
|
-
/** The bound card index. */
|
|
1147
761
|
get index() {
|
|
1148
762
|
return this.#index;
|
|
1149
763
|
}
|
|
1150
764
|
/**
|
|
1151
|
-
* The bound card's `$kind`, empty string when it carries none. Throws
|
|
1152
|
-
* `IndexOutOfRange` for a bad bound index.
|
|
1153
765
|
* @returns {string}
|
|
1154
766
|
*/
|
|
1155
767
|
get kind() {
|
|
1156
768
|
return this.#doc.card(this.#index).kind;
|
|
1157
769
|
}
|
|
1158
770
|
/**
|
|
1159
|
-
* Typed-commit one field on this card, addressed at `{ card, field }`. Throws
|
|
1160
|
-
* `UnknownField` for an undeclared name and `IndexOutOfRange` if the bound
|
|
1161
|
-
* index is out of range.
|
|
1162
771
|
* @param {string} name
|
|
1163
772
|
* @param {unknown} value
|
|
1164
773
|
* @returns {void}
|
|
@@ -1167,9 +776,6 @@ export class CardWriter {
|
|
|
1167
776
|
return this.#doc._commitField(this.#quill, { card: this.#index, field: name }, value);
|
|
1168
777
|
}
|
|
1169
778
|
/**
|
|
1170
|
-
* Typed-commit several fields on this card atomically, addressed at
|
|
1171
|
-
* `{ card }`. Throws a per-field diagnostic bundle on error and
|
|
1172
|
-
* `IndexOutOfRange` if the bound index is out of range.
|
|
1173
779
|
* @param {Record<string, unknown>} fields
|
|
1174
780
|
* @returns {void}
|
|
1175
781
|
*/
|
|
@@ -1177,7 +783,6 @@ export class CardWriter {
|
|
|
1177
783
|
return this.#doc._commitFields(this.#quill, { card: this.#index }, fields);
|
|
1178
784
|
}
|
|
1179
785
|
/**
|
|
1180
|
-
* The card twin of {@link DocumentWriter.reviseBody}.
|
|
1181
786
|
* @param {string} markdown
|
|
1182
787
|
* @returns {import('../core/wasm.js').Delta}
|
|
1183
788
|
*/
|
|
@@ -1185,8 +790,6 @@ export class CardWriter {
|
|
|
1185
790
|
return this.#doc.revise({ card: this.#index }, markdown);
|
|
1186
791
|
}
|
|
1187
792
|
/**
|
|
1188
|
-
* The card twin of {@link DocumentWriter.reviseField}. Throws `UnknownField`
|
|
1189
|
-
* for an undeclared name and `IndexOutOfRange` for a bad bound index.
|
|
1190
793
|
* @param {string} name
|
|
1191
794
|
* @param {string} text
|
|
1192
795
|
* @returns {import('../core/wasm.js').Delta}
|
|
@@ -1196,13 +799,10 @@ export class CardWriter {
|
|
|
1196
799
|
}
|
|
1197
800
|
}
|
|
1198
801
|
|
|
1199
|
-
// ── `quill.writer(doc)`, the typed front door ──────────────────────────────
|
|
1200
802
|
// Patched onto the re-exported `Quill` prototype rather than wrapped, so
|
|
1201
803
|
// `Quill === CoreQuill` stays true: this only adds a method constructing the
|
|
1202
804
|
// pure-JS writer, which owns no WASM handle.
|
|
1203
805
|
/**
|
|
1204
|
-
* A {@link DocumentWriter} binding this quill's schema to `doc`. It holds both
|
|
1205
|
-
* handles by reference and owns neither: bind, write, discard.
|
|
1206
806
|
* @this {Quill}
|
|
1207
807
|
* @param {Document} doc the document to mutate, held by reference (not owned)
|
|
1208
808
|
* @returns {DocumentWriter}
|
|
@@ -1211,17 +811,10 @@ Quill.prototype.writer = function writer(doc) {
|
|
|
1211
811
|
return new DocumentWriter(this, doc);
|
|
1212
812
|
};
|
|
1213
813
|
|
|
1214
|
-
// ── Typed-reader sugar: the schema-plane read surface ─────────────────────────
|
|
1215
814
|
// The transport `Document.getStored` is schema-free, so an unknown field name
|
|
1216
815
|
// reads back `undefined` rather than as the typo it is. Binding the quill's
|
|
1217
|
-
// schema lets one `get` interpret by declared type and throw `UnknownField
|
|
1218
|
-
// name the schema does not declare.
|
|
816
|
+
// schema lets one `get` interpret by declared type and throw `UnknownField`.
|
|
1219
817
|
|
|
1220
|
-
/**
|
|
1221
|
-
* A {@link Document} bound to its {@link Quill} for typed reads, the read
|
|
1222
|
-
* counterpart of {@link DocumentWriter}. Reads target the main card; use
|
|
1223
|
-
* {@link card} for a composable one. Owns neither handle.
|
|
1224
|
-
*/
|
|
1225
818
|
export class DocumentReader {
|
|
1226
819
|
#quill;
|
|
1227
820
|
#doc;
|
|
@@ -1235,17 +828,10 @@ export class DocumentReader {
|
|
|
1235
828
|
this.#quill = quill;
|
|
1236
829
|
this.#doc = doc;
|
|
1237
830
|
}
|
|
1238
|
-
/** The bound document: the same instance passed in. */
|
|
1239
831
|
get document() {
|
|
1240
832
|
return this.#doc;
|
|
1241
833
|
}
|
|
1242
834
|
/**
|
|
1243
|
-
* Read the value at `addr`, interpreted by its declared type: a richtext field
|
|
1244
|
-
* to markdown, every other type verbatim. A bare string is `Addr` shorthand
|
|
1245
|
-
* for `{ field }`; an absent `addr.field` reads the body markdown. `undefined`
|
|
1246
|
-
* for an absent field; throws `UnknownField` for a name the schema does not
|
|
1247
|
-
* declare, `FieldDecode` for a richtext field holding an undecodable
|
|
1248
|
-
* value, and `IndexOutOfRange` for a bad `addr.card`.
|
|
1249
835
|
* @param {import('../core/wasm.js').Addr | string} addr
|
|
1250
836
|
* @returns {unknown}
|
|
1251
837
|
*/
|
|
@@ -1253,11 +839,6 @@ export class DocumentReader {
|
|
|
1253
839
|
return this.#doc._readerGet(this.#quill, addr);
|
|
1254
840
|
}
|
|
1255
841
|
/**
|
|
1256
|
-
* Read the content field at `addr` as canonical `Content`: the twin of
|
|
1257
|
-
* {@link get}, which projects. An absent `addr.field` reads the body
|
|
1258
|
-
* `Content`. `undefined` for an absent field; throws `UnknownField`,
|
|
1259
|
-
* `FieldNotContent` for a type that is not a content leaf, `FieldDecode` for
|
|
1260
|
-
* an undecodable value, and `IndexOutOfRange` for a bad `addr.card`.
|
|
1261
842
|
* @param {import('../core/wasm.js').Addr | string} addr
|
|
1262
843
|
* @returns {import('../core/wasm.js').Content | undefined}
|
|
1263
844
|
*/
|
|
@@ -1265,13 +846,6 @@ export class DocumentReader {
|
|
|
1265
846
|
return this.#doc._readerGetContent(this.#quill, addr);
|
|
1266
847
|
}
|
|
1267
848
|
/**
|
|
1268
|
-
* Read the `Content` nested inside the composite field at `addr`, at `path`:
|
|
1269
|
-
* `[0]` an `array<richtext>` element, `["motto"]` an object's content property,
|
|
1270
|
-
* `[1, "notes"]` a leaf under both, `["controlled_by"]` a variant's cell. The
|
|
1271
|
-
* codec is the leaf's declared type's.
|
|
1272
|
-
* `undefined` for an absent field and for a path that names nothing stored;
|
|
1273
|
-
* throws `UnknownField`, `FieldNotContent` when `path` resolves to no content
|
|
1274
|
-
* leaf, `FieldDecode` anchored at the addressed path, and `IndexOutOfRange`.
|
|
1275
849
|
* @param {import('../core/wasm.js').Addr | string} addr
|
|
1276
850
|
* @param {import('../core/wasm.js').PathStep[]} path
|
|
1277
851
|
* @returns {import('../core/wasm.js').Content | undefined}
|
|
@@ -1280,16 +854,18 @@ export class DocumentReader {
|
|
|
1280
854
|
return this.#doc._readerGetContentAt(this.#quill, addr, path);
|
|
1281
855
|
}
|
|
1282
856
|
/**
|
|
1283
|
-
* The main body's markdown: the quill-free body read. Equals `get({})`.
|
|
1284
857
|
* @returns {string}
|
|
1285
858
|
*/
|
|
1286
859
|
bodyMarkdown() {
|
|
1287
860
|
return this.#doc._readerGet(this.#quill, {});
|
|
1288
861
|
}
|
|
1289
862
|
/**
|
|
1290
|
-
*
|
|
1291
|
-
|
|
1292
|
-
|
|
863
|
+
* @returns {Resolved}
|
|
864
|
+
*/
|
|
865
|
+
resolve() {
|
|
866
|
+
return this.#quill._resolve(this.#doc);
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
1293
869
|
* @param {number} index
|
|
1294
870
|
* @returns {CardReader}
|
|
1295
871
|
*/
|
|
@@ -1298,11 +874,6 @@ export class DocumentReader {
|
|
|
1298
874
|
}
|
|
1299
875
|
}
|
|
1300
876
|
|
|
1301
|
-
/**
|
|
1302
|
-
* A single composable card bound to its {@link Quill} for typed reads, from
|
|
1303
|
-
* {@link DocumentReader.card}. Same `get` / `bodyMarkdown` verbs as
|
|
1304
|
-
* {@link DocumentReader}, reading the card at its bound index.
|
|
1305
|
-
*/
|
|
1306
877
|
export class CardReader {
|
|
1307
878
|
#quill;
|
|
1308
879
|
#doc;
|
|
@@ -1319,22 +890,16 @@ export class CardReader {
|
|
|
1319
890
|
this.#doc = doc;
|
|
1320
891
|
this.#index = index;
|
|
1321
892
|
}
|
|
1322
|
-
/** The bound card index. */
|
|
1323
893
|
get index() {
|
|
1324
894
|
return this.#index;
|
|
1325
895
|
}
|
|
1326
896
|
/**
|
|
1327
|
-
* The bound card's `$kind` (empty string when it carries none), read through
|
|
1328
|
-
* the document. Throws `IndexOutOfRange` if the bound index is out of range.
|
|
1329
897
|
* @returns {string}
|
|
1330
898
|
*/
|
|
1331
899
|
get kind() {
|
|
1332
900
|
return this.#doc.card(this.#index).kind;
|
|
1333
901
|
}
|
|
1334
902
|
/**
|
|
1335
|
-
* Read the field `name` on this card, interpreted by its declared type,
|
|
1336
|
-
* addressed at `{ card, field }`. `undefined` when absent; throws
|
|
1337
|
-
* `UnknownField` for an undeclared name and `IndexOutOfRange` for a bad index.
|
|
1338
903
|
* @param {string} name
|
|
1339
904
|
* @returns {unknown}
|
|
1340
905
|
*/
|
|
@@ -1342,7 +907,6 @@ export class CardReader {
|
|
|
1342
907
|
return this.#doc._readerGet(this.#quill, { card: this.#index, field: name });
|
|
1343
908
|
}
|
|
1344
909
|
/**
|
|
1345
|
-
* The card twin of {@link DocumentReader.getContent}.
|
|
1346
910
|
* @param {string} name
|
|
1347
911
|
* @returns {import('../core/wasm.js').Content | undefined}
|
|
1348
912
|
*/
|
|
@@ -1350,7 +914,6 @@ export class CardReader {
|
|
|
1350
914
|
return this.#doc._readerGetContent(this.#quill, { card: this.#index, field: name });
|
|
1351
915
|
}
|
|
1352
916
|
/**
|
|
1353
|
-
* The card twin of {@link DocumentReader.getContentAt}.
|
|
1354
917
|
* @param {string} name
|
|
1355
918
|
* @param {import('../core/wasm.js').PathStep[]} path
|
|
1356
919
|
* @returns {import('../core/wasm.js').Content | undefined}
|
|
@@ -1359,7 +922,6 @@ export class CardReader {
|
|
|
1359
922
|
return this.#doc._readerGetContentAt(this.#quill, { card: this.#index, field: name }, path);
|
|
1360
923
|
}
|
|
1361
924
|
/**
|
|
1362
|
-
* The card twin of {@link DocumentReader.bodyMarkdown}.
|
|
1363
925
|
* @returns {string}
|
|
1364
926
|
*/
|
|
1365
927
|
bodyMarkdown() {
|
|
@@ -1367,11 +929,8 @@ export class CardReader {
|
|
|
1367
929
|
}
|
|
1368
930
|
}
|
|
1369
931
|
|
|
1370
|
-
// ── `quill.reader(doc)`: the schema-plane read front door ─────────────────────
|
|
1371
932
|
// Patched onto the same re-exported `Quill` prototype as `writer`.
|
|
1372
933
|
/**
|
|
1373
|
-
* A {@link DocumentReader} binding this quill's schema to `doc`. It holds both
|
|
1374
|
-
* handles by reference and owns neither: bind, read, discard.
|
|
1375
934
|
* @this {Quill}
|
|
1376
935
|
* @param {Document} doc the document to read, held by reference (not owned)
|
|
1377
936
|
* @returns {DocumentReader}
|