@quillmark/wasm 0.111.0 → 0.113.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 +1742 -0
- package/README.md +93 -68
- package/core/wasm.d.ts +126 -186
- package/core/wasm.js +245 -391
- package/core/wasm_bg.wasm +0 -0
- package/core/wasm_bg.wasm.d.ts +5 -11
- package/package.json +2 -2
- package/{backends/pdfform → render}/wasm.d.ts +196 -277
- package/{backends/pdfform → render}/wasm.js +343 -455
- package/{backends/typst → render}/wasm_bg.wasm +0 -0
- package/{backends/typst → render}/wasm_bg.wasm.d.ts +8 -16
- package/runtime/runtime.d.ts +98 -182
- package/runtime/runtime.js +148 -587
- 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 -1496
- package/backends/typst/wasm.js +0 -2996
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
|
|
@@ -43,32 +22,11 @@ import { toModuleSource } from '#quillmark-env';
|
|
|
43
22
|
import { importMarkdown, exportMarkdown, rebase, mapPos, mapMarks } from '../core/wasm.js';
|
|
44
23
|
import { parseDocPath, formatDocPath } 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
|
|
@@ -104,38 +62,8 @@ let coreInit;
|
|
|
104
62
|
let coreInitSource;
|
|
105
63
|
|
|
106
64
|
/**
|
|
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
|
|
65
|
+
* @param {import('../core/wasm.js').InitInput} [source]
|
|
66
|
+
* @returns {Promise<import('./runtime.js').CoreSurface>}
|
|
139
67
|
*/
|
|
140
68
|
export function init(source) {
|
|
141
69
|
if (coreInit) {
|
|
@@ -195,46 +123,17 @@ async function instantiateCore(source) {
|
|
|
195
123
|
}
|
|
196
124
|
}
|
|
197
125
|
|
|
198
|
-
// ── The main-card address ───────────────────────────────────────────────────
|
|
199
126
|
/**
|
|
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
127
|
* @type {import('../core/wasm.js').CardAddr}
|
|
207
128
|
*/
|
|
208
129
|
export const MAIN_CARD_ADDR = Object.freeze({});
|
|
209
130
|
|
|
210
|
-
// ── The variant discriminant key ────────────────────────────────────────────
|
|
211
131
|
/**
|
|
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
132
|
* @type {'value'}
|
|
224
133
|
*/
|
|
225
134
|
export const VARIANT_DISCRIMINANT_KEY = 'value';
|
|
226
135
|
|
|
227
136
|
/**
|
|
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
137
|
* @param {unknown} e
|
|
239
138
|
* @returns {e is Error & { diagnostics: import('../core/wasm.js').Diagnostic[] }}
|
|
240
139
|
*/
|
|
@@ -258,62 +157,35 @@ function quillmarkError(code, message, hint) {
|
|
|
258
157
|
return err;
|
|
259
158
|
}
|
|
260
159
|
|
|
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.
|
|
160
|
+
// These checks deliver the ERROR, not the rejection, at the seams that cross
|
|
161
|
+
// into backend memory as DATA — `Engine` and `LiveSession.update` — where a
|
|
162
|
+
// foreign handle would otherwise work silently at the price of a whole-document
|
|
163
|
+
// round trip and a per-copy split of the quill clone cache, and at the four
|
|
164
|
+
// writer/reader binds, whose constructors hold both handles. A method declaring
|
|
165
|
+
// a reference parameter needs none of this: wasm-bindgen's own `_assertClass`
|
|
166
|
+
// refuses a foreign class there.
|
|
283
167
|
|
|
284
|
-
/** Per class: the
|
|
168
|
+
/** Per class: the diagnostic for a value that is not one of THIS copy's handles. */
|
|
285
169
|
const HANDLE_KINDS = {
|
|
286
170
|
Quill: {
|
|
287
171
|
code: 'runtime::not_a_quill',
|
|
288
|
-
|
|
289
|
-
hint: 'Pass a Quill built by Quill.fromTree.'
|
|
172
|
+
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
173
|
},
|
|
291
174
|
Document: {
|
|
292
175
|
code: 'runtime::not_a_document',
|
|
293
|
-
|
|
294
|
-
hint: 'Pass a Document built by Document.fromMarkdown / fromJson or quill.seedDocument.'
|
|
176
|
+
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
177
|
}
|
|
296
178
|
};
|
|
297
179
|
|
|
298
180
|
/**
|
|
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).
|
|
181
|
+
* The rejection for a value that is not one of this copy's handles.
|
|
303
182
|
* @param {unknown} value
|
|
304
183
|
* @param {string} method
|
|
305
184
|
* @param {'Quill' | 'Document'} className
|
|
306
185
|
* @returns {Error & { diagnostics: import('../core/wasm.js').Diagnostic[] }}
|
|
307
186
|
*/
|
|
308
187
|
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
|
-
}
|
|
188
|
+
const { code, hint } = HANDLE_KINDS[className];
|
|
317
189
|
return quillmarkError(
|
|
318
190
|
code,
|
|
319
191
|
`${method}: expected a ${className}, got ${value === null ? 'null' : typeof value}.`,
|
|
@@ -343,213 +215,27 @@ function requireLocalQuill(quill, method) {
|
|
|
343
215
|
throw notLocal(quill, method, 'Quill');
|
|
344
216
|
}
|
|
345
217
|
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
//
|
|
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`, a `link` mark carries `url`, an `anchor` mark carries `id`, a
|
|
402
|
-
// `heading` line carries `level` and a `code` line `lang`, a `list_item`
|
|
403
|
-
// container its shape; the payload-free arms (`strong`/`emph`/`underline`/
|
|
404
|
-
// `strike`/`code` marks, `para`/`island`/`rule` lines, `quote`) narrow to
|
|
405
|
-
// nothing. An unrecognized discriminant fails every guard and keeps its opaque
|
|
406
|
-
// `attrs`/`props`.
|
|
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'; 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'; 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'; 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'; 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'; 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
|
-
// WELD_KEYS is the rule `Container::same_weld` owns upstream: which fields two
|
|
521
|
-
// adjacent runs must share for the markdown projection to read them as one, and
|
|
522
|
-
// therefore for the canonical form to have to spend a discriminator. `start` is
|
|
523
|
-
// not among them, since CommonMark reads only a list's first number. A table
|
|
218
|
+
// WELD_KEYS is the rule `Container::same_weld` owns upstream: which `attrs`
|
|
219
|
+
// entries two adjacent runs must share for the markdown projection to read them
|
|
220
|
+
// as one. `start` is not among them, since CommonMark reads only a list's first
|
|
221
|
+
// number — a subset, which is why every container needs its own entry. A table
|
|
524
222
|
// rather than a switch, so `tests/known_names_drift.rs` can pin it against the
|
|
525
223
|
// Rust predicate.
|
|
526
224
|
|
|
527
225
|
const WELD_KEYS = { list_item: ['ordered'], quote: [] };
|
|
528
226
|
|
|
529
|
-
function sameJson(a, b) {
|
|
530
|
-
if (a === b) return true;
|
|
531
|
-
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;
|
|
532
|
-
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
533
|
-
const ka = Object.keys(a);
|
|
534
|
-
return (
|
|
535
|
-
ka.length === Object.keys(b).length &&
|
|
536
|
-
ka.every((k) => Object.hasOwn(b, k) && sameJson(a[k], b[k]))
|
|
537
|
-
);
|
|
538
|
-
}
|
|
539
|
-
|
|
540
227
|
/**
|
|
541
228
|
* @param {import('../core/wasm.js').ContentContainer} a
|
|
542
229
|
* @param {import('../core/wasm.js').ContentContainer} b
|
|
543
230
|
* @returns {boolean}
|
|
544
231
|
*/
|
|
545
232
|
function weldsWith(a, b) {
|
|
546
|
-
// A malformed value welds with nothing
|
|
547
|
-
// answer rather than throw.
|
|
233
|
+
// A malformed value welds with nothing: answer rather than throw.
|
|
548
234
|
if (typeof a?.container !== 'string' || a.container !== b?.container) return false;
|
|
549
|
-
// `hasOwn`, so a tag colliding with an `Object.prototype` member
|
|
550
|
-
//
|
|
551
|
-
if (!Object.hasOwn(WELD_KEYS, a.container)) return
|
|
552
|
-
return WELD_KEYS[a.container].every((k) => a[k] === b[k]);
|
|
235
|
+
// `hasOwn`, so a tag colliding with an `Object.prototype` member answers
|
|
236
|
+
// `false` rather than reaching a function.
|
|
237
|
+
if (!Object.hasOwn(WELD_KEYS, a.container)) return false;
|
|
238
|
+
return WELD_KEYS[a.container].every((k) => a.attrs?.[k] === b.attrs?.[k]);
|
|
553
239
|
}
|
|
554
240
|
|
|
555
241
|
/**
|
|
@@ -578,7 +264,7 @@ export function assignInstances(runs) {
|
|
|
578
264
|
* generated entry's own `wasm !== undefined` guard only catches a call arriving
|
|
579
265
|
* after one finished, not one already in flight.
|
|
580
266
|
*
|
|
581
|
-
* @param {string} id
|
|
267
|
+
* @param {string} id the build's name, for the failure message
|
|
582
268
|
* @param {() => Promise<any>} importThunk the dynamic `import()`
|
|
583
269
|
* @param {() => URL} wasmUrl the build's binary, resolved at call time
|
|
584
270
|
* @returns {() => Promise<any>} resolves to a ready-to-use module
|
|
@@ -597,108 +283,89 @@ function backendLoad(id, importThunk, wasmUrl) {
|
|
|
597
283
|
throw Object.assign(
|
|
598
284
|
quillmarkError(
|
|
599
285
|
'runtime::backend_load_failed',
|
|
600
|
-
`Engine: could not load the '${id}'
|
|
286
|
+
`Engine: could not load the '${id}' build: ${
|
|
601
287
|
/** @type {any} */ (cause)?.message ?? cause
|
|
602
288
|
}`,
|
|
603
|
-
'The
|
|
289
|
+
'The build ships beside the package files; check the network tab for a 404 or an HTML response.'
|
|
604
290
|
),
|
|
605
291
|
{ cause }
|
|
606
292
|
);
|
|
607
293
|
}));
|
|
608
294
|
}
|
|
609
295
|
|
|
610
|
-
//
|
|
611
|
-
// binary into the eager graph and defeat lazy loading.
|
|
612
|
-
//
|
|
613
|
-
//
|
|
614
|
-
// `
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
296
|
+
// The render build is NEVER statically imported here: that would pull a
|
|
297
|
+
// multi-MB binary into the eager graph and defeat lazy loading. Both built-in
|
|
298
|
+
// backends ship in it, so they share one `load`; each `formats` manifest
|
|
299
|
+
// mirrors that backend's Rust `SUPPORTED_FORMATS`, pinned by a
|
|
300
|
+
// `runtime.test.js` drift guard that renders once and compares.
|
|
301
|
+
const RENDER_BUILD = backendLoad(
|
|
302
|
+
'render',
|
|
303
|
+
() => import('../render/wasm.js'),
|
|
304
|
+
() => new URL('../render/wasm_bg.wasm', import.meta.url)
|
|
305
|
+
);
|
|
619
306
|
const DEFAULT_BACKENDS = {
|
|
620
307
|
typst: {
|
|
621
|
-
load:
|
|
622
|
-
|
|
623
|
-
() => import('../backends/typst/wasm.js'),
|
|
624
|
-
() => new URL('../backends/typst/wasm_bg.wasm', import.meta.url)
|
|
625
|
-
),
|
|
626
|
-
formats: ['pdf', 'svg', 'png'], // crates/backends/typst/src/lib.rs SUPPORTED_FORMATS
|
|
627
|
-
canvas: true // has svg/png → formats_support_canvas == true
|
|
308
|
+
load: RENDER_BUILD,
|
|
309
|
+
formats: ['pdf', 'svg', 'png'] // crates/backends/typst/src/lib.rs SUPPORTED_FORMATS
|
|
628
310
|
},
|
|
629
|
-
|
|
630
|
-
load:
|
|
631
|
-
|
|
632
|
-
() => import('../backends/pdfform/wasm.js'),
|
|
633
|
-
() => new URL('../backends/pdfform/wasm_bg.wasm', import.meta.url)
|
|
634
|
-
),
|
|
635
|
-
// crates/backends/pdfform/src/lib.rs SUPPORTED_FORMATS == [Pdf, Svg, Png]
|
|
636
|
-
formats: ['pdf', 'svg', 'png'],
|
|
637
|
-
canvas: true // has svg/png → formats_support_canvas == true
|
|
311
|
+
acroform: {
|
|
312
|
+
load: RENDER_BUILD,
|
|
313
|
+
formats: ['pdf'] // crates/backends/acroform/src/lib.rs SUPPORTED_FORMATS
|
|
638
314
|
}
|
|
639
315
|
};
|
|
640
316
|
|
|
641
317
|
/**
|
|
642
318
|
* Validate a backend registry descriptor, naming the backend id on any
|
|
643
319
|
* malformed entry. Failing at construction rather than deep inside a render is
|
|
644
|
-
* what lets
|
|
320
|
+
* what lets `supportedFormats` answer from the manifest unconditionally.
|
|
645
321
|
* @param {string} id
|
|
646
322
|
* @param {unknown} entry
|
|
647
|
-
* @returns {{ load: () => Promise<unknown>, formats: string[]
|
|
323
|
+
* @returns {{ load: () => Promise<unknown>, formats: string[] }}
|
|
648
324
|
*/
|
|
649
325
|
function validateBackend(id, entry) {
|
|
650
326
|
if (!entry || typeof entry !== 'object') {
|
|
651
|
-
throw new Error(
|
|
652
|
-
`Engine: backend '${id}' must be a descriptor { load, formats, canvas }.`
|
|
653
|
-
);
|
|
327
|
+
throw new Error(`Engine: backend '${id}' must be a descriptor { load, formats }.`);
|
|
654
328
|
}
|
|
655
|
-
const { load, formats
|
|
329
|
+
const { load, formats } = /** @type {any} */ (entry);
|
|
656
330
|
if (typeof load !== 'function') {
|
|
657
331
|
throw new Error(`Engine: backend '${id}' descriptor needs a callable 'load'.`);
|
|
658
332
|
}
|
|
659
333
|
if (!Array.isArray(formats)) {
|
|
660
334
|
throw new Error(`Engine: backend '${id}' descriptor needs a 'formats' array.`);
|
|
661
335
|
}
|
|
662
|
-
|
|
663
|
-
throw new Error(`Engine: backend '${id}' descriptor needs a boolean 'canvas'.`);
|
|
664
|
-
}
|
|
665
|
-
return { load, formats, canvas };
|
|
336
|
+
return { load, formats };
|
|
666
337
|
}
|
|
667
338
|
|
|
668
|
-
/**
|
|
669
|
-
* Render dispatcher over the canonical `Quill`/`Document`. One `Engine`
|
|
670
|
-
* instance can drive every backend; it resolves the right backend build from
|
|
671
|
-
* each quill's declared `backendId` and loads it lazily on first use.
|
|
672
|
-
*/
|
|
673
339
|
export class Engine {
|
|
674
|
-
/**
|
|
340
|
+
/**
|
|
341
|
+
* The three caches below key on a descriptor's `load` thunk, the build's
|
|
342
|
+
* identity: two backend ids sharing one build (the built-ins) share one
|
|
343
|
+
* module, one engine and one clone cache, and a consumer's override build
|
|
344
|
+
* gets its own.
|
|
345
|
+
*/
|
|
346
|
+
/** load → Promise<build module>, memoized so each build loads once. */
|
|
675
347
|
#modules = new Map();
|
|
676
|
-
/**
|
|
348
|
+
/** load → that build's engine instance (the WASM backend registry). */
|
|
677
349
|
#engines = new Map();
|
|
678
|
-
/** backendId → descriptor `{ load, formats
|
|
350
|
+
/** backendId → descriptor `{ load, formats }`. */
|
|
679
351
|
#loaders;
|
|
680
352
|
/**
|
|
681
|
-
*
|
|
353
|
+
* load → WeakMap<canonical Quill, build-memory clone>, caching the
|
|
682
354
|
* expensive materialization. WeakMap so dropping the canonical quill makes
|
|
683
355
|
* its clone collectable, and wasm-bindgen weak-refs then free the handle.
|
|
684
|
-
* @type {Map<
|
|
356
|
+
* @type {Map<() => Promise<unknown>, WeakMap<object, any>>}
|
|
685
357
|
*/
|
|
686
358
|
#quillClones = new Map();
|
|
687
359
|
|
|
688
360
|
/**
|
|
689
|
-
*
|
|
690
|
-
*
|
|
691
|
-
*
|
|
692
|
-
*
|
|
693
|
-
* throw here, at construction.
|
|
694
|
-
*
|
|
695
|
-
* `load` resolves to a READY module: a registrant shipping its own
|
|
696
|
-
* `--target web` build instantiates inside the thunk. More than one
|
|
697
|
-
* `Engine` may call it, so memoize (the built-ins do, at module scope).
|
|
361
|
+
* `load` must resolve to a READY module: a registrant shipping its own
|
|
362
|
+
* `--target web` build instantiates inside the thunk, and more than one
|
|
363
|
+
* `Engine` may call it, so memoize (the built-ins do, at module scope).
|
|
364
|
+
* @param {{ backends?: Record<string, { load: () => Promise<unknown>, formats: string[] }> }} [options]
|
|
698
365
|
*/
|
|
699
366
|
constructor(options) {
|
|
700
367
|
const merged = { ...DEFAULT_BACKENDS, ...(options?.backends ?? {}) };
|
|
701
|
-
/** @type {Record<string, { load: () => Promise<unknown>, formats: string[]
|
|
368
|
+
/** @type {Record<string, { load: () => Promise<unknown>, formats: string[] }>} */
|
|
702
369
|
const loaders = {};
|
|
703
370
|
for (const [id, entry] of Object.entries(merged)) {
|
|
704
371
|
loaders[id] = validateBackend(id, entry);
|
|
@@ -707,17 +374,19 @@ export class Engine {
|
|
|
707
374
|
}
|
|
708
375
|
|
|
709
376
|
/**
|
|
710
|
-
* The registered descriptor for `backendId`, or the
|
|
711
|
-
* throw
|
|
377
|
+
* The registered descriptor for `backendId`, or the
|
|
378
|
+
* `engine::backend_not_found` throw core raises for the same condition.
|
|
379
|
+
* Touches no binary.
|
|
712
380
|
* @param {string} backendId
|
|
713
|
-
* @returns {{ load: () => Promise<unknown>, formats: string[]
|
|
381
|
+
* @returns {{ load: () => Promise<unknown>, formats: string[] }}
|
|
714
382
|
*/
|
|
715
383
|
#descriptorFor(backendId) {
|
|
716
384
|
const descriptor = this.#loaders[backendId];
|
|
717
385
|
if (!descriptor) {
|
|
718
|
-
throw
|
|
719
|
-
|
|
720
|
-
|
|
386
|
+
throw quillmarkError(
|
|
387
|
+
'engine::backend_not_found',
|
|
388
|
+
`Engine: backend '${backendId}' not registered or not enabled.`,
|
|
389
|
+
`Available backends: ${Object.keys(this.#loaders).join(', ') || '(none)'}`
|
|
721
390
|
);
|
|
722
391
|
}
|
|
723
392
|
return descriptor;
|
|
@@ -737,51 +406,52 @@ export class Engine {
|
|
|
737
406
|
}
|
|
738
407
|
|
|
739
408
|
/**
|
|
740
|
-
* Resolve (and lazily load) the
|
|
409
|
+
* Resolve (and lazily load) the build module + its engine for `backendId`.
|
|
741
410
|
* @param {string} backendId
|
|
742
411
|
* @returns {Promise<{ mod: any, engine: any }>}
|
|
743
412
|
*/
|
|
744
413
|
async #resolveBackend(backendId) {
|
|
745
|
-
const
|
|
414
|
+
const { load } = this.#descriptorFor(backendId);
|
|
746
415
|
|
|
747
|
-
let modPromise = this.#modules.get(
|
|
416
|
+
let modPromise = this.#modules.get(load);
|
|
748
417
|
if (!modPromise) {
|
|
749
418
|
// Set the promise synchronously (before any await) so concurrent first
|
|
750
419
|
// renders share ONE import. Self-heal on failure so a transient load
|
|
751
420
|
// error doesn't poison every later attempt.
|
|
752
421
|
modPromise = Promise.resolve()
|
|
753
|
-
.then(
|
|
422
|
+
.then(load)
|
|
754
423
|
.catch((err) => {
|
|
755
|
-
this.#modules.delete(
|
|
424
|
+
this.#modules.delete(load);
|
|
756
425
|
throw err;
|
|
757
426
|
});
|
|
758
|
-
this.#modules.set(
|
|
427
|
+
this.#modules.set(load, modPromise);
|
|
759
428
|
}
|
|
760
429
|
const mod = await modPromise;
|
|
761
430
|
|
|
762
|
-
let engine = this.#engines.get(
|
|
431
|
+
let engine = this.#engines.get(load);
|
|
763
432
|
if (!engine) {
|
|
764
433
|
engine = new mod.Quillmark();
|
|
765
|
-
this.#engines.set(
|
|
434
|
+
this.#engines.set(load, engine);
|
|
766
435
|
}
|
|
767
436
|
return { mod, engine };
|
|
768
437
|
}
|
|
769
438
|
|
|
770
439
|
/**
|
|
771
|
-
* Get (or materialize-and-cache) the
|
|
772
|
-
* under `backendId
|
|
773
|
-
* pre-await snapshot, since the canonical handle may be freed by now.
|
|
774
|
-
* @param {any} mod the
|
|
440
|
+
* Get (or materialize-and-cache) the build-memory `Quill` clone for `quill`
|
|
441
|
+
* under `backendId`'s build. On a miss the clone is built from `tree`, the
|
|
442
|
+
* caller's pre-await snapshot, since the canonical handle may be freed by now.
|
|
443
|
+
* @param {any} mod the build module
|
|
775
444
|
* @param {string} backendId
|
|
776
445
|
* @param {object} quill the canonical instance (cache key only)
|
|
777
446
|
* @param {Map<string, Uint8Array> | null} tree pre-await snapshot; `null` on a cache hit
|
|
778
|
-
* @returns {any} the
|
|
447
|
+
* @returns {any} the build-memory quill clone
|
|
779
448
|
*/
|
|
780
449
|
#cachedQuillClone(mod, backendId, quill, tree) {
|
|
781
|
-
|
|
450
|
+
const { load } = this.#descriptorFor(backendId);
|
|
451
|
+
let perQuill = this.#quillClones.get(load);
|
|
782
452
|
if (!perQuill) {
|
|
783
453
|
perQuill = new WeakMap();
|
|
784
|
-
this.#quillClones.set(
|
|
454
|
+
this.#quillClones.set(load, perQuill);
|
|
785
455
|
}
|
|
786
456
|
let backendQuill = perQuill.get(quill);
|
|
787
457
|
if (!backendQuill) {
|
|
@@ -796,11 +466,15 @@ export class Engine {
|
|
|
796
466
|
* memory and run `fn` against the backend engine. Only `render`/`open` call
|
|
797
467
|
* this, so `doc` is always present.
|
|
798
468
|
*
|
|
799
|
-
* OWNERSHIP WINDOW: both caller handles are snapshotted (`doc.
|
|
800
|
-
* `quill.toTree()` on a clone-cache miss) BEFORE the first
|
|
801
|
-
* load below is a real suspension point, so reading the
|
|
802
|
-
* would race a caller that `free()`s them as soon as this
|
|
803
|
-
* promise ("null pointer passed to rust").
|
|
469
|
+
* OWNERSHIP WINDOW: both caller handles are snapshotted (`doc.toStored()` and
|
|
470
|
+
* `doc.warnings`, and `quill.toTree()` on a clone-cache miss) BEFORE the first
|
|
471
|
+
* await. The backend load below is a real suspension point, so reading the
|
|
472
|
+
* handles after it would race a caller that `free()`s them as soon as this
|
|
473
|
+
* call returns its promise ("null pointer passed to rust").
|
|
474
|
+
*
|
|
475
|
+
* `docWarnings` rides the context because the storage DTO does not carry
|
|
476
|
+
* them: `fromStored` clears the load's warnings, so the backend clone knows
|
|
477
|
+
* nothing of them and `render` splices the snapshot back in.
|
|
804
478
|
*
|
|
805
479
|
* Clone lifetimes differ by design: the `doc` clone is TRANSIENT, freed in
|
|
806
480
|
* the `finally` of every call, while the `quill` clone is CACHED and is not
|
|
@@ -810,13 +484,16 @@ export class Engine {
|
|
|
810
484
|
* @param {string} method the caller's name, for the rejection message
|
|
811
485
|
* @param {Quill} quill
|
|
812
486
|
* @param {Document} doc
|
|
813
|
-
* @param {(ctx: { mod: any, engine: any, quill: any, doc: any }) => any} fn
|
|
487
|
+
* @param {(ctx: { mod: any, engine: any, quill: any, doc: any, docWarnings: any[] }) => any} fn
|
|
814
488
|
*/
|
|
815
489
|
async #withClones(method, quill, doc, fn) {
|
|
816
490
|
const backendId = this.#backendOf(quill, method);
|
|
817
491
|
requireLocalDoc(doc, method);
|
|
818
|
-
const docJson = doc.
|
|
819
|
-
const
|
|
492
|
+
const docJson = doc.toStored();
|
|
493
|
+
const docWarnings = doc.warnings;
|
|
494
|
+
const quillTree = this.#quillClones.get(this.#descriptorFor(backendId).load)?.has(quill)
|
|
495
|
+
? null
|
|
496
|
+
: quill.toTree();
|
|
820
497
|
const { mod, engine } = await this.#resolveBackend(backendId);
|
|
821
498
|
// The doc clone and `fn` share one try so the clone is freed even if a
|
|
822
499
|
// later step throws; the cached quill clone is intentionally not freed.
|
|
@@ -824,31 +501,33 @@ export class Engine {
|
|
|
824
501
|
const backendQuill = this.#cachedQuillClone(mod, backendId, quill, quillTree);
|
|
825
502
|
let backendDoc = null;
|
|
826
503
|
try {
|
|
827
|
-
backendDoc = mod.Document.
|
|
828
|
-
return fn({ mod, engine, quill: backendQuill, doc: backendDoc });
|
|
504
|
+
backendDoc = mod.Document.fromStored(docJson);
|
|
505
|
+
return fn({ mod, engine, quill: backendQuill, doc: backendDoc, docWarnings });
|
|
829
506
|
} finally {
|
|
830
507
|
backendDoc?.free();
|
|
831
508
|
}
|
|
832
509
|
}
|
|
833
510
|
|
|
834
511
|
/**
|
|
835
|
-
* Render `doc` against `quill` in one shot. Both handles are read
|
|
836
|
-
* synchronously before the first await.
|
|
837
512
|
* @param {Quill} quill
|
|
838
513
|
* @param {Document} doc
|
|
839
|
-
* @param {object} [options] render options (`{ format, ppi, pages,
|
|
514
|
+
* @param {object} [options] render options (`{ format, ppi, pages, regions }`)
|
|
840
515
|
* @returns {Promise<import('./runtime.js').RenderResult>}
|
|
841
516
|
*/
|
|
842
517
|
async render(quill, doc, options) {
|
|
843
|
-
return this.#withClones(
|
|
844
|
-
engine.render(
|
|
518
|
+
return this.#withClones(
|
|
519
|
+
'engine.render(quill, doc)',
|
|
520
|
+
quill,
|
|
521
|
+
doc,
|
|
522
|
+
({ engine, quill: q, doc: d, docWarnings }) => {
|
|
523
|
+
const result = engine.render(q, d, options ?? undefined);
|
|
524
|
+
result.warnings = docWarnings.concat(result.warnings);
|
|
525
|
+
return result;
|
|
526
|
+
}
|
|
845
527
|
);
|
|
846
528
|
}
|
|
847
529
|
|
|
848
530
|
/**
|
|
849
|
-
* Open a live render session. It retains what `update` needs, so the
|
|
850
|
-
* transient clones are freed before this returns; the caller owns the session
|
|
851
|
-
* and must `.free()` it.
|
|
852
531
|
* @param {Quill} quill
|
|
853
532
|
* @param {Document} doc
|
|
854
533
|
* @returns {Promise<LiveSession>}
|
|
@@ -863,8 +542,6 @@ export class Engine {
|
|
|
863
542
|
}
|
|
864
543
|
|
|
865
544
|
/**
|
|
866
|
-
* The output formats `quill`'s backend can emit: an always-free probe over
|
|
867
|
-
* the descriptor's manifest. `async` for API stability; it awaits nothing.
|
|
868
545
|
* @param {Quill} quill
|
|
869
546
|
* @returns {Promise<import('./runtime.js').OutputFormat[]>}
|
|
870
547
|
*/
|
|
@@ -873,29 +550,17 @@ export class Engine {
|
|
|
873
550
|
// Defensive copy so callers can't mutate the shared manifest.
|
|
874
551
|
return descriptor.formats.slice();
|
|
875
552
|
}
|
|
876
|
-
|
|
877
|
-
/**
|
|
878
|
-
* Whether `quill`'s backend can paint to a canvas: a pre-session estimate over
|
|
879
|
-
* the descriptor's manifest, so it can answer `true` where the resulting
|
|
880
|
-
* `LiveSession.supportsCanvas` answers `false`.
|
|
881
|
-
* @param {Quill} quill
|
|
882
|
-
* @returns {Promise<boolean>}
|
|
883
|
-
*/
|
|
884
|
-
async supportsCanvas(quill) {
|
|
885
|
-
const descriptor = this.#descriptorFor(this.#backendOf(quill, 'engine.supportsCanvas(quill)'));
|
|
886
|
-
return descriptor.canvas;
|
|
887
|
-
}
|
|
888
553
|
}
|
|
889
554
|
|
|
890
555
|
/**
|
|
891
|
-
* Thin wrapper over a backend's live render session
|
|
892
|
-
*
|
|
893
|
-
*
|
|
556
|
+
* Thin wrapper over a backend's live render session. The quill/document clones
|
|
557
|
+
* it was opened from have already been freed: the session retains what `update`
|
|
558
|
+
* needs.
|
|
894
559
|
*/
|
|
895
560
|
export class LiveSession {
|
|
896
561
|
/**
|
|
897
|
-
* @param {
|
|
898
|
-
* @param {{ Document: {
|
|
562
|
+
* @param {object} inner the render build's LiveSession, whose members the delegations below name
|
|
563
|
+
* @param {{ Document: { fromStored(json: string): any } }} mod the session's backend build, used to materialize `update` documents in its linear memory
|
|
899
564
|
*/
|
|
900
565
|
constructor(inner, mod) {
|
|
901
566
|
this.#inner = inner;
|
|
@@ -912,7 +577,7 @@ export class LiveSession {
|
|
|
912
577
|
requireLocalDoc(doc, 'session.update(doc)');
|
|
913
578
|
let backendDoc = null;
|
|
914
579
|
try {
|
|
915
|
-
backendDoc = this.#mod.Document.
|
|
580
|
+
backendDoc = this.#mod.Document.fromStored(doc.toStored());
|
|
916
581
|
return this.#inner.update(backendDoc);
|
|
917
582
|
} finally {
|
|
918
583
|
backendDoc?.free();
|
|
@@ -925,15 +590,6 @@ export class LiveSession {
|
|
|
925
590
|
get backendId() {
|
|
926
591
|
return this.#inner.backendId;
|
|
927
592
|
}
|
|
928
|
-
/**
|
|
929
|
-
* `true` iff `paint`/`pageSize` will succeed for THIS compile: the
|
|
930
|
-
* authoritative answer, which can be `false` where `Engine.supportsCanvas`
|
|
931
|
-
* answered `true` for the same quill.
|
|
932
|
-
* @returns {boolean}
|
|
933
|
-
*/
|
|
934
|
-
get supportsCanvas() {
|
|
935
|
-
return this.#inner.supportsCanvas;
|
|
936
|
-
}
|
|
937
593
|
get warnings() {
|
|
938
594
|
return this.#inner.warnings;
|
|
939
595
|
}
|
|
@@ -962,20 +618,22 @@ export class LiveSession {
|
|
|
962
618
|
* @param {number} page
|
|
963
619
|
* @param {number} x
|
|
964
620
|
* @param {number} y
|
|
621
|
+
* @param {number} [tolPt]
|
|
965
622
|
* @returns {string | undefined}
|
|
966
623
|
*/
|
|
967
|
-
fieldAt(page, x, y) {
|
|
968
|
-
return this.#inner.fieldAt(page, x, y);
|
|
624
|
+
fieldAt(page, x, y, tolPt) {
|
|
625
|
+
return this.#inner.fieldAt(page, x, y, tolPt);
|
|
969
626
|
}
|
|
970
627
|
|
|
971
628
|
/**
|
|
972
629
|
* @param {number} page
|
|
973
630
|
* @param {number} x
|
|
974
631
|
* @param {number} y
|
|
632
|
+
* @param {number} [tolPt]
|
|
975
633
|
* @returns {import('./runtime.d.ts').ContentHit | undefined}
|
|
976
634
|
*/
|
|
977
|
-
positionAt(page, x, y) {
|
|
978
|
-
return this.#inner.positionAt(page, x, y);
|
|
635
|
+
positionAt(page, x, y, tolPt) {
|
|
636
|
+
return this.#inner.positionAt(page, x, y, tolPt);
|
|
979
637
|
}
|
|
980
638
|
|
|
981
639
|
/**
|
|
@@ -1006,28 +664,11 @@ export class LiveSession {
|
|
|
1006
664
|
}
|
|
1007
665
|
}
|
|
1008
666
|
|
|
1009
|
-
//
|
|
1010
|
-
//
|
|
1011
|
-
//
|
|
1012
|
-
//
|
|
1013
|
-
// schema, so each `commit*` method takes the `quill` handle as its first
|
|
1014
|
-
// argument. These pure-JS classes restore the Rust ergonomics: bind `quill` +
|
|
1015
|
-
// `doc` once, then issue `set` / `setAll` / `card(i).set`.
|
|
1016
|
-
//
|
|
1017
|
-
// They hold JS references to the caller's EXISTING handles (no WASM object of
|
|
1018
|
-
// their own, no `free()` burden, no second owner of either handle) and every
|
|
1019
|
-
// write delegates straight to the underlying `commit*` verb: a schema field is
|
|
1020
|
-
// typed-committed (coerced to canonical form, mismatch throws now), and a name
|
|
1021
|
-
// the schema does not declare throws `UnknownField` rather than falling to the
|
|
1022
|
-
// opaque store, on the typed path an undeclared name is a typo. Opaque storage
|
|
1023
|
-
// stays available through the raw addressed `Document.storeField` verb.
|
|
667
|
+
// These pure-JS classes bind a `quill` + `doc` pair once and delegate to the
|
|
668
|
+
// `_commit*` / `_reader*` verbs, which take the quill per call. They hold JS
|
|
669
|
+
// references to the caller's existing handles, so there is no WASM object of
|
|
670
|
+
// their own, no `free()` burden, and no second owner of either handle.
|
|
1024
671
|
|
|
1025
|
-
/**
|
|
1026
|
-
* A {@link Document} bound to its {@link Quill} for typed writes: the JS twin
|
|
1027
|
-
* of Rust's `quill.writer(&mut doc)`. Writes target the main card; use
|
|
1028
|
-
* {@link card} for a composable card. Holds both handles by reference and owns
|
|
1029
|
-
* neither, so there is nothing to `free()`.
|
|
1030
|
-
*/
|
|
1031
672
|
export class DocumentWriter {
|
|
1032
673
|
#quill;
|
|
1033
674
|
#doc;
|
|
@@ -1041,13 +682,10 @@ export class DocumentWriter {
|
|
|
1041
682
|
this.#quill = quill;
|
|
1042
683
|
this.#doc = doc;
|
|
1043
684
|
}
|
|
1044
|
-
/** The bound document: the same instance passed in, mutated in place. */
|
|
1045
685
|
get document() {
|
|
1046
686
|
return this.#doc;
|
|
1047
687
|
}
|
|
1048
688
|
/**
|
|
1049
|
-
* Typed-commit one main-card field (strict coerce, mismatch throws now).
|
|
1050
|
-
* Throws `UnknownField` for a name the schema does not declare.
|
|
1051
689
|
* @param {string} name
|
|
1052
690
|
* @param {unknown} value
|
|
1053
691
|
* @returns {void}
|
|
@@ -1056,9 +694,6 @@ export class DocumentWriter {
|
|
|
1056
694
|
return this.#doc._commitField(this.#quill, name, value);
|
|
1057
695
|
}
|
|
1058
696
|
/**
|
|
1059
|
-
* Typed-commit several main-card fields atomically: nothing is applied on
|
|
1060
|
-
* error (throws a per-field diagnostic bundle, including an `UnknownField`
|
|
1061
|
-
* for each undeclared name).
|
|
1062
697
|
* @param {Record<string, unknown>} fields
|
|
1063
698
|
* @returns {void}
|
|
1064
699
|
*/
|
|
@@ -1066,8 +701,6 @@ export class DocumentWriter {
|
|
|
1066
701
|
return this.#doc._commitFields(this.#quill, MAIN_CARD_ADDR, fields);
|
|
1067
702
|
}
|
|
1068
703
|
/**
|
|
1069
|
-
* Revise the main body from markdown; anchors rebase. A body carries no field
|
|
1070
|
-
* schema, so this is the content lane's `revise` reached through the writer.
|
|
1071
704
|
* @param {string} markdown
|
|
1072
705
|
* @returns {import('../core/wasm.js').Delta}
|
|
1073
706
|
*/
|
|
@@ -1075,10 +708,6 @@ export class DocumentWriter {
|
|
|
1075
708
|
return this.#doc.revise({}, markdown);
|
|
1076
709
|
}
|
|
1077
710
|
/**
|
|
1078
|
-
* Revise the content main-card field `name` from authored text: typed *and*
|
|
1079
|
-
* anchor-preserving. Anchors rebase, then the diffed result is
|
|
1080
|
-
* schema-conformed. The codec comes from the declared type: `richtext` diffs
|
|
1081
|
-
* markdown, `plaintext` the literal text.
|
|
1082
711
|
* @param {string} name
|
|
1083
712
|
* @param {string} text
|
|
1084
713
|
* @returns {import('../core/wasm.js').Delta}
|
|
@@ -1087,10 +716,6 @@ export class DocumentWriter {
|
|
|
1087
716
|
return this.#doc._reviseField(this.#quill, name, text);
|
|
1088
717
|
}
|
|
1089
718
|
/**
|
|
1090
|
-
* Build a composable card of `kind`, typed-commit `fields` onto it, set its
|
|
1091
|
-
* body from optional markdown, and place it. Transactional: the card is
|
|
1092
|
-
* committed in full before it joins the document, so a rejected field, kind,
|
|
1093
|
-
* body, or position leaves the document untouched.
|
|
1094
719
|
* @param {string} kind
|
|
1095
720
|
* @param {Record<string, unknown>} [fields]
|
|
1096
721
|
* @param {string} [body]
|
|
@@ -1108,9 +733,6 @@ export class DocumentWriter {
|
|
|
1108
733
|
return this.#doc.removeCard(index);
|
|
1109
734
|
}
|
|
1110
735
|
/**
|
|
1111
|
-
* A {@link CardWriter} bound to the composable card at `index`, checked
|
|
1112
|
-
* lazily at the write. It holds `index`, not the card, so a
|
|
1113
|
-
* `removeCard`/`addCard` between binding and writing silently retargets it.
|
|
1114
736
|
* @param {number} index
|
|
1115
737
|
* @returns {CardWriter}
|
|
1116
738
|
*/
|
|
@@ -1119,11 +741,6 @@ export class DocumentWriter {
|
|
|
1119
741
|
}
|
|
1120
742
|
}
|
|
1121
743
|
|
|
1122
|
-
/**
|
|
1123
|
-
* A single composable card bound to its {@link Quill} for typed writes, from
|
|
1124
|
-
* {@link DocumentWriter.card}. Same `set` / `setAll` verbs as
|
|
1125
|
-
* {@link DocumentWriter}, targeting the card at its bound index.
|
|
1126
|
-
*/
|
|
1127
744
|
export class CardWriter {
|
|
1128
745
|
#quill;
|
|
1129
746
|
#doc;
|
|
@@ -1140,22 +757,16 @@ export class CardWriter {
|
|
|
1140
757
|
this.#doc = doc;
|
|
1141
758
|
this.#index = index;
|
|
1142
759
|
}
|
|
1143
|
-
/** The bound card index. */
|
|
1144
760
|
get index() {
|
|
1145
761
|
return this.#index;
|
|
1146
762
|
}
|
|
1147
763
|
/**
|
|
1148
|
-
* The bound card's `$kind`, empty string when it carries none. Throws
|
|
1149
|
-
* `IndexOutOfRange` for a bad bound index.
|
|
1150
764
|
* @returns {string}
|
|
1151
765
|
*/
|
|
1152
766
|
get kind() {
|
|
1153
767
|
return this.#doc.card(this.#index).kind;
|
|
1154
768
|
}
|
|
1155
769
|
/**
|
|
1156
|
-
* Typed-commit one field on this card, addressed at `{ card, field }`. Throws
|
|
1157
|
-
* `UnknownField` for an undeclared name and `IndexOutOfRange` if the bound
|
|
1158
|
-
* index is out of range.
|
|
1159
770
|
* @param {string} name
|
|
1160
771
|
* @param {unknown} value
|
|
1161
772
|
* @returns {void}
|
|
@@ -1164,9 +775,6 @@ export class CardWriter {
|
|
|
1164
775
|
return this.#doc._commitField(this.#quill, { card: this.#index, field: name }, value);
|
|
1165
776
|
}
|
|
1166
777
|
/**
|
|
1167
|
-
* Typed-commit several fields on this card atomically, addressed at
|
|
1168
|
-
* `{ card }`. Throws a per-field diagnostic bundle on error and
|
|
1169
|
-
* `IndexOutOfRange` if the bound index is out of range.
|
|
1170
778
|
* @param {Record<string, unknown>} fields
|
|
1171
779
|
* @returns {void}
|
|
1172
780
|
*/
|
|
@@ -1174,7 +782,6 @@ export class CardWriter {
|
|
|
1174
782
|
return this.#doc._commitFields(this.#quill, { card: this.#index }, fields);
|
|
1175
783
|
}
|
|
1176
784
|
/**
|
|
1177
|
-
* The card twin of {@link DocumentWriter.reviseBody}.
|
|
1178
785
|
* @param {string} markdown
|
|
1179
786
|
* @returns {import('../core/wasm.js').Delta}
|
|
1180
787
|
*/
|
|
@@ -1182,8 +789,6 @@ export class CardWriter {
|
|
|
1182
789
|
return this.#doc.revise({ card: this.#index }, markdown);
|
|
1183
790
|
}
|
|
1184
791
|
/**
|
|
1185
|
-
* The card twin of {@link DocumentWriter.reviseField}. Throws `UnknownField`
|
|
1186
|
-
* for an undeclared name and `IndexOutOfRange` for a bad bound index.
|
|
1187
792
|
* @param {string} name
|
|
1188
793
|
* @param {string} text
|
|
1189
794
|
* @returns {import('../core/wasm.js').Delta}
|
|
@@ -1193,13 +798,10 @@ export class CardWriter {
|
|
|
1193
798
|
}
|
|
1194
799
|
}
|
|
1195
800
|
|
|
1196
|
-
// ── `quill.writer(doc)`, the typed front door ──────────────────────────────
|
|
1197
801
|
// Patched onto the re-exported `Quill` prototype rather than wrapped, so
|
|
1198
802
|
// `Quill === CoreQuill` stays true: this only adds a method constructing the
|
|
1199
803
|
// pure-JS writer, which owns no WASM handle.
|
|
1200
804
|
/**
|
|
1201
|
-
* A {@link DocumentWriter} binding this quill's schema to `doc`. It holds both
|
|
1202
|
-
* handles by reference and owns neither: bind, write, discard.
|
|
1203
805
|
* @this {Quill}
|
|
1204
806
|
* @param {Document} doc the document to mutate, held by reference (not owned)
|
|
1205
807
|
* @returns {DocumentWriter}
|
|
@@ -1208,17 +810,10 @@ Quill.prototype.writer = function writer(doc) {
|
|
|
1208
810
|
return new DocumentWriter(this, doc);
|
|
1209
811
|
};
|
|
1210
812
|
|
|
1211
|
-
// ── Typed-reader sugar: the schema-plane read surface ─────────────────────────
|
|
1212
813
|
// The transport `Document.getStored` is schema-free, so an unknown field name
|
|
1213
814
|
// reads back `undefined` rather than as the typo it is. Binding the quill's
|
|
1214
|
-
// schema lets one `get` interpret by declared type and throw `UnknownField
|
|
1215
|
-
// name the schema does not declare.
|
|
815
|
+
// schema lets one `get` interpret by declared type and throw `UnknownField`.
|
|
1216
816
|
|
|
1217
|
-
/**
|
|
1218
|
-
* A {@link Document} bound to its {@link Quill} for typed reads, the read
|
|
1219
|
-
* counterpart of {@link DocumentWriter}. Reads target the main card; use
|
|
1220
|
-
* {@link card} for a composable one. Owns neither handle.
|
|
1221
|
-
*/
|
|
1222
817
|
export class DocumentReader {
|
|
1223
818
|
#quill;
|
|
1224
819
|
#doc;
|
|
@@ -1232,17 +827,10 @@ export class DocumentReader {
|
|
|
1232
827
|
this.#quill = quill;
|
|
1233
828
|
this.#doc = doc;
|
|
1234
829
|
}
|
|
1235
|
-
/** The bound document: the same instance passed in. */
|
|
1236
830
|
get document() {
|
|
1237
831
|
return this.#doc;
|
|
1238
832
|
}
|
|
1239
833
|
/**
|
|
1240
|
-
* Read the value at `addr`, interpreted by its declared type: a richtext field
|
|
1241
|
-
* to markdown, every other type verbatim. A bare string is `Addr` shorthand
|
|
1242
|
-
* for `{ field }`; an absent `addr.field` reads the body markdown. `undefined`
|
|
1243
|
-
* for an absent field; throws `UnknownField` for a name the schema does not
|
|
1244
|
-
* declare, `FieldDecode` for a richtext field holding an undecodable
|
|
1245
|
-
* value, and `IndexOutOfRange` for a bad `addr.card`.
|
|
1246
834
|
* @param {import('../core/wasm.js').Addr | string} addr
|
|
1247
835
|
* @returns {unknown}
|
|
1248
836
|
*/
|
|
@@ -1250,11 +838,6 @@ export class DocumentReader {
|
|
|
1250
838
|
return this.#doc._readerGet(this.#quill, addr);
|
|
1251
839
|
}
|
|
1252
840
|
/**
|
|
1253
|
-
* Read the content field at `addr` as canonical `Content`: the twin of
|
|
1254
|
-
* {@link get}, which projects. An absent `addr.field` reads the body
|
|
1255
|
-
* `Content`. `undefined` for an absent field; throws `UnknownField`,
|
|
1256
|
-
* `FieldNotContent` for a type that is not a content leaf, `FieldDecode` for
|
|
1257
|
-
* an undecodable value, and `IndexOutOfRange` for a bad `addr.card`.
|
|
1258
841
|
* @param {import('../core/wasm.js').Addr | string} addr
|
|
1259
842
|
* @returns {import('../core/wasm.js').Content | undefined}
|
|
1260
843
|
*/
|
|
@@ -1262,13 +845,6 @@ export class DocumentReader {
|
|
|
1262
845
|
return this.#doc._readerGetContent(this.#quill, addr);
|
|
1263
846
|
}
|
|
1264
847
|
/**
|
|
1265
|
-
* Read the `Content` nested inside the composite field at `addr`, at `path`:
|
|
1266
|
-
* `[0]` an `array<richtext>` element, `["motto"]` an object's content property,
|
|
1267
|
-
* `[1, "notes"]` a leaf under both, `["controlled_by"]` a variant's cell. The
|
|
1268
|
-
* codec is the leaf's declared type's.
|
|
1269
|
-
* `undefined` for an absent field and for a path that names nothing stored;
|
|
1270
|
-
* throws `UnknownField`, `FieldNotContent` when `path` resolves to no content
|
|
1271
|
-
* leaf, `FieldDecode` anchored at the addressed path, and `IndexOutOfRange`.
|
|
1272
848
|
* @param {import('../core/wasm.js').Addr | string} addr
|
|
1273
849
|
* @param {import('../core/wasm.js').PathStep[]} path
|
|
1274
850
|
* @returns {import('../core/wasm.js').Content | undefined}
|
|
@@ -1277,16 +853,18 @@ export class DocumentReader {
|
|
|
1277
853
|
return this.#doc._readerGetContentAt(this.#quill, addr, path);
|
|
1278
854
|
}
|
|
1279
855
|
/**
|
|
1280
|
-
* The main body's markdown: the quill-free body read. Equals `get({})`.
|
|
1281
856
|
* @returns {string}
|
|
1282
857
|
*/
|
|
1283
858
|
bodyMarkdown() {
|
|
1284
859
|
return this.#doc._readerGet(this.#quill, {});
|
|
1285
860
|
}
|
|
1286
861
|
/**
|
|
1287
|
-
*
|
|
1288
|
-
|
|
1289
|
-
|
|
862
|
+
* @returns {Resolved}
|
|
863
|
+
*/
|
|
864
|
+
resolve() {
|
|
865
|
+
return this.#quill._resolve(this.#doc);
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
1290
868
|
* @param {number} index
|
|
1291
869
|
* @returns {CardReader}
|
|
1292
870
|
*/
|
|
@@ -1295,11 +873,6 @@ export class DocumentReader {
|
|
|
1295
873
|
}
|
|
1296
874
|
}
|
|
1297
875
|
|
|
1298
|
-
/**
|
|
1299
|
-
* A single composable card bound to its {@link Quill} for typed reads, from
|
|
1300
|
-
* {@link DocumentReader.card}. Same `get` / `bodyMarkdown` verbs as
|
|
1301
|
-
* {@link DocumentReader}, reading the card at its bound index.
|
|
1302
|
-
*/
|
|
1303
876
|
export class CardReader {
|
|
1304
877
|
#quill;
|
|
1305
878
|
#doc;
|
|
@@ -1316,22 +889,16 @@ export class CardReader {
|
|
|
1316
889
|
this.#doc = doc;
|
|
1317
890
|
this.#index = index;
|
|
1318
891
|
}
|
|
1319
|
-
/** The bound card index. */
|
|
1320
892
|
get index() {
|
|
1321
893
|
return this.#index;
|
|
1322
894
|
}
|
|
1323
895
|
/**
|
|
1324
|
-
* The bound card's `$kind` (empty string when it carries none), read through
|
|
1325
|
-
* the document. Throws `IndexOutOfRange` if the bound index is out of range.
|
|
1326
896
|
* @returns {string}
|
|
1327
897
|
*/
|
|
1328
898
|
get kind() {
|
|
1329
899
|
return this.#doc.card(this.#index).kind;
|
|
1330
900
|
}
|
|
1331
901
|
/**
|
|
1332
|
-
* Read the field `name` on this card, interpreted by its declared type,
|
|
1333
|
-
* addressed at `{ card, field }`. `undefined` when absent; throws
|
|
1334
|
-
* `UnknownField` for an undeclared name and `IndexOutOfRange` for a bad index.
|
|
1335
902
|
* @param {string} name
|
|
1336
903
|
* @returns {unknown}
|
|
1337
904
|
*/
|
|
@@ -1339,7 +906,6 @@ export class CardReader {
|
|
|
1339
906
|
return this.#doc._readerGet(this.#quill, { card: this.#index, field: name });
|
|
1340
907
|
}
|
|
1341
908
|
/**
|
|
1342
|
-
* The card twin of {@link DocumentReader.getContent}.
|
|
1343
909
|
* @param {string} name
|
|
1344
910
|
* @returns {import('../core/wasm.js').Content | undefined}
|
|
1345
911
|
*/
|
|
@@ -1347,7 +913,6 @@ export class CardReader {
|
|
|
1347
913
|
return this.#doc._readerGetContent(this.#quill, { card: this.#index, field: name });
|
|
1348
914
|
}
|
|
1349
915
|
/**
|
|
1350
|
-
* The card twin of {@link DocumentReader.getContentAt}.
|
|
1351
916
|
* @param {string} name
|
|
1352
917
|
* @param {import('../core/wasm.js').PathStep[]} path
|
|
1353
918
|
* @returns {import('../core/wasm.js').Content | undefined}
|
|
@@ -1356,7 +921,6 @@ export class CardReader {
|
|
|
1356
921
|
return this.#doc._readerGetContentAt(this.#quill, { card: this.#index, field: name }, path);
|
|
1357
922
|
}
|
|
1358
923
|
/**
|
|
1359
|
-
* The card twin of {@link DocumentReader.bodyMarkdown}.
|
|
1360
924
|
* @returns {string}
|
|
1361
925
|
*/
|
|
1362
926
|
bodyMarkdown() {
|
|
@@ -1364,11 +928,8 @@ export class CardReader {
|
|
|
1364
928
|
}
|
|
1365
929
|
}
|
|
1366
930
|
|
|
1367
|
-
// ── `quill.reader(doc)`: the schema-plane read front door ─────────────────────
|
|
1368
931
|
// Patched onto the same re-exported `Quill` prototype as `writer`.
|
|
1369
932
|
/**
|
|
1370
|
-
* A {@link DocumentReader} binding this quill's schema to `doc`. It holds both
|
|
1371
|
-
* handles by reference and owns neither: bind, read, discard.
|
|
1372
933
|
* @this {Quill}
|
|
1373
934
|
* @param {Document} doc the document to read, held by reference (not owned)
|
|
1374
935
|
* @returns {DocumentReader}
|