@mdzip/editor 1.4.5 → 1.4.7
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/AGENTS.md +48 -46
- package/README.md +554 -547
- package/dist/library-info.d.ts +1 -1
- package/dist/library-info.js +1 -1
- package/dist/mermaid.d.ts.map +1 -1
- package/dist/mermaid.js +28 -2
- package/dist/mermaid.js.map +1 -1
- package/dist/view.d.ts +24 -0
- package/dist/view.d.ts.map +1 -1
- package/dist/view.js +13 -1
- package/dist/view.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,547 +1,554 @@
|
|
|
1
|
-
[![MDZip logo][mdzip-logo]][mdzip-url]
|
|
2
|
-
|
|
3
|
-
[mdzip-logo]: https://raw.githubusercontent.com/mdzip-project/mdzip-editor/main/resources/mdzip-mark.svg
|
|
4
|
-
[mdzip-url]: https://mdzip.org
|
|
5
|
-
|
|
6
|
-
# @mdzip/editor
|
|
7
|
-
|
|
8
|
-
[](https://www.npmjs.com/package/@mdzip/editor)
|
|
9
|
-
[](https://github.com/mdzip-project/mdzip-editor/blob/main/LICENSE)
|
|
10
|
-
|
|
11
|
-
Framework-independent MDZip workspace engine and browser view.
|
|
12
|
-
|
|
13
|
-
`@mdzip/editor` provides reusable helpers for opening `.mdz` archives, rendering Markdown previews, editing archive contents, comparing archive inventories, and embedding a configurable MDZip workspace UI.
|
|
14
|
-
|
|
15
|
-
## Install
|
|
16
|
-
|
|
17
|
-
```sh
|
|
18
|
-
npm install @mdzip/editor
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Basic Usage
|
|
22
|
-
|
|
23
|
-
```ts
|
|
24
|
-
import { MdzipWorkspaceView } from '@mdzip/editor';
|
|
25
|
-
|
|
26
|
-
const view = new MdzipWorkspaceView(container, {
|
|
27
|
-
controls: 'viewer',
|
|
28
|
-
initialLayout: 'preview',
|
|
29
|
-
onFailed(error) {
|
|
30
|
-
console.error(error);
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
await view.open(bytes, {
|
|
35
|
-
mode: 'read-only',
|
|
36
|
-
fileName: 'document.mdz'
|
|
37
|
-
});
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Regular Markdown is also supported:
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
await view.open(markdownBytes, {
|
|
44
|
-
mode: 'editable',
|
|
45
|
-
fileName: 'notes.md'
|
|
46
|
-
});
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
CodeMirror is not initialized until a source or split layout is used. Consumers
|
|
50
|
-
that only need archive, rendering, and preview helpers can import from
|
|
51
|
-
`@mdzip/editor/preview`; that entry point excludes `MdzipWorkspaceView` and its
|
|
52
|
-
CodeMirror dependencies from the module graph.
|
|
53
|
-
|
|
54
|
-
The filename normally selects the source format. Pass `sourceFormat:
|
|
55
|
-
'markdown'` or `sourceFormat: 'mdz'` to override detection.
|
|
56
|
-
|
|
57
|
-
Normalized `@mdzip/core-js` workspaces can be opened without an initial archive
|
|
58
|
-
rebuild:
|
|
59
|
-
|
|
60
|
-
```ts
|
|
61
|
-
await view.openWorkspace(workspace, {
|
|
62
|
-
mode: 'editable',
|
|
63
|
-
fileName: 'document.mdz',
|
|
64
|
-
assetSourceId: 'document-etag-or-content-id'
|
|
65
|
-
});
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Images are resolved only when referenced by the active document or selected in
|
|
69
|
-
the navigator. To reuse resolved bytes across sessions, inject the optional
|
|
70
|
-
bounded IndexedDB cache:
|
|
71
|
-
|
|
72
|
-
```ts
|
|
73
|
-
import { MdzipIndexedDbAssetCache, MdzipWorkspaceView } from '@mdzip/editor';
|
|
74
|
-
|
|
75
|
-
const view = new MdzipWorkspaceView(container, {
|
|
76
|
-
assetCache: new MdzipIndexedDbAssetCache({ maxBytes: 64 * 1024 * 1024 })
|
|
77
|
-
});
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
For `openWorkspace()`, provide a stable `assetSourceId` (or `archiveBytes`) so
|
|
81
|
-
cache hits can bypass lazy ZIP readers. Cache and storage failures automatically
|
|
82
|
-
fall back to archive reads.
|
|
83
|
-
|
|
84
|
-
## Editor Mode
|
|
85
|
-
|
|
86
|
-
```ts
|
|
87
|
-
const view = new MdzipWorkspaceView(container, {
|
|
88
|
-
controls: 'standalone-editor',
|
|
89
|
-
async onSaved(bytes) {
|
|
90
|
-
await persist(bytes);
|
|
91
|
-
view.markPersisted();
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
await view.open(bytes, {
|
|
96
|
-
mode: 'editable',
|
|
97
|
-
fileName: 'document.mdz'
|
|
98
|
-
});
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
When `onSaved` is omitted, the built-in Save button downloads the current file
|
|
102
|
-
in the browser. When `onSaved` is provided, the host owns persistence and must
|
|
103
|
-
call `markPersisted()` after a successful write. Failed writes should leave the
|
|
104
|
-
workspace dirty.
|
|
105
|
-
|
|
106
|
-
## Control Presets
|
|
107
|
-
|
|
108
|
-
- `preview`: clean document preview with no toolbar or package navigation.
|
|
109
|
-
- `viewer`: read-only viewer controls, including navigation, layout switching, and zoom.
|
|
110
|
-
- `standalone-editor`: full editor controls, including save.
|
|
111
|
-
- `hosted-editor`: editor controls without an embedded save button, for hosts such as VS Code that own persistence.
|
|
112
|
-
|
|
113
|
-
Call `setControls(nextControls)` to update the policy after construction
|
|
114
|
-
without rebuilding the workspace view. `lineNumbers` changes are applied to the
|
|
115
|
-
existing CodeMirror editor, preserving the current document and selection.
|
|
116
|
-
|
|
117
|
-
## Density and Spacing
|
|
118
|
-
|
|
119
|
-
Hosts can opt into smaller built-in UI without targeting private classes:
|
|
120
|
-
|
|
121
|
-
```ts
|
|
122
|
-
const view = new MdzipWorkspaceView(container, {
|
|
123
|
-
controls: 'hosted-editor',
|
|
124
|
-
toolbarDensity: 'compact', // 'comfortable' | 'compact' | 'dense'
|
|
125
|
-
contentDensity: 'compact' // 'comfortable' | 'compact'
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
view.setDensityOptions({
|
|
129
|
-
toolbarDensity: 'dense',
|
|
130
|
-
contentDensity: 'compact'
|
|
131
|
-
});
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
For exact sizing, set stable CSS custom properties on an ancestor of the
|
|
135
|
-
workspace:
|
|
136
|
-
|
|
137
|
-
```css
|
|
138
|
-
.studio-editor {
|
|
139
|
-
--mdzip-toolbar-button-size: 28px;
|
|
140
|
-
--mdzip-toolbar-compact-button-size: 26px;
|
|
141
|
-
--mdzip-toolbar-icon-size: 14px;
|
|
142
|
-
--mdzip-format-button-size: 26px;
|
|
143
|
-
--mdzip-format-icon-size: 14px;
|
|
144
|
-
--mdzip-toolbar-padding: 2px 8px;
|
|
145
|
-
--mdzip-toolbar-gap: 4px;
|
|
146
|
-
--mdzip-editor-content-padding: 16px 20px;
|
|
147
|
-
--mdzip-preview-content-padding: 16px 20px 24px;
|
|
148
|
-
--mdzip-preview-content-max-width: 720px;
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
## Preview Width
|
|
153
|
-
|
|
154
|
-
`previewMaxWidth` is a developer-facing option, not toolbar UI — it sets the
|
|
155
|
-
preview's reading-column width (`--mdzip-preview-content-max-width` under the
|
|
156
|
-
hood). A number is an exact pixel value; `'narrow'` / `'default'` / `'wide'`
|
|
157
|
-
are convenience aliases for 650 / 900 / 1200px:
|
|
158
|
-
|
|
159
|
-
```ts
|
|
160
|
-
const view = new MdzipWorkspaceView(container, {
|
|
161
|
-
controls: 'hosted-editor',
|
|
162
|
-
previewMaxWidth: 'wide' // or an exact pixel number, e.g. 760
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
view.setPreviewMaxWidth(720);
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
Leaving it unset (the default) keeps the built-in CSS default in effect,
|
|
169
|
-
which scales with the zoom control; an explicit value set here (or via the
|
|
170
|
-
`--mdzip-preview-content-max-width` CSS variable directly) does not scale
|
|
171
|
-
with zoom — it's used exactly as given.
|
|
172
|
-
|
|
173
|
-
## Host Persistence
|
|
174
|
-
|
|
175
|
-
Desktop hosts can flush pending editor content, persist the returned bytes, and
|
|
176
|
-
only then acknowledge a successful write:
|
|
177
|
-
|
|
178
|
-
```ts
|
|
179
|
-
const snapshot = await view.flush();
|
|
180
|
-
if (snapshot) {
|
|
181
|
-
await nativeSave(snapshot.bytes);
|
|
182
|
-
view.markPersisted();
|
|
183
|
-
}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
`flush()` deliberately leaves `dirty` set until `markPersisted()` is called.
|
|
187
|
-
`serialize()` and `getCurrentSnapshot()` provide non-acknowledging alternatives.
|
|
188
|
-
|
|
189
|
-
Structured callbacks are available for workspace, document, asset, manifest,
|
|
190
|
-
selection, dirty, validation, and snapshot changes. Asset hosts can also call
|
|
191
|
-
`addAsset()`, `replaceAsset()`, `removeAsset()`, and `listAssets()`.
|
|
192
|
-
|
|
193
|
-
## File Management (navigation pane)
|
|
194
|
-
|
|
195
|
-
With `controls: 'standalone-editor'` or `'hosted-editor'` (or `fileActions: true`
|
|
196
|
-
in a custom policy), the navigation pane offers a right-click context menu: new
|
|
197
|
-
`.md` file, new folder, rename/move (edit the full archive path), duplicate,
|
|
198
|
-
replace, download, copy markdown link / image embed, set entry point (shown bold
|
|
199
|
-
in the tree), set/remove cover image, and delete with a confirmation prompt
|
|
200
|
-
(orphaned assets delete immediately). The entry-point document and
|
|
201
|
-
`manifest.json` cannot be deleted. Copy and Download remain available in
|
|
202
|
-
read-only contexts.
|
|
203
|
-
|
|
204
|
-
Drag and drop is supported in all directions: move files between folders, drop
|
|
205
|
-
OS files onto the pane to add them as assets, drag a tree file onto the editor
|
|
206
|
-
to insert a markdown link or image embed at the pointer, and drop an OS image
|
|
207
|
-
onto the editor to embed it like a paste.
|
|
208
|
-
|
|
209
|
-
The same operations are available programmatically: `removeFile()`,
|
|
210
|
-
`renameFile()` (rewrites markdown references, including a moved document's own
|
|
211
|
-
relative links), `setEntryPoint()`, and `setCoverImage()`.
|
|
212
|
-
|
|
213
|
-
## Conversion Hook
|
|
214
|
-
|
|
215
|
-
For plain-markdown sources, hosts can take over the markdown→MDZ conversion
|
|
216
|
-
flow (triggered by the nav button, Insert Image, or an image paste/drop):
|
|
217
|
-
|
|
218
|
-
```ts
|
|
219
|
-
const view = new MdzipWorkspaceView(container, {
|
|
220
|
-
async onConversionRequested(action, context) {
|
|
221
|
-
// action.kind: 'navigation' | 'image-picker' | 'image-file' (with action.file)
|
|
222
|
-
const relativePath = await hostHandlesImage(action);
|
|
223
|
-
return relativePath
|
|
224
|
-
? context.insertMarkdown(``)
|
|
225
|
-
: false;
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
Returning or resolving `false` (or omitting the callback) keeps the built-in
|
|
231
|
-
conversion dialog. Errors thrown by the hook are reported via `onFailed` and
|
|
232
|
-
fall back to the built-in dialog. The context preserves the triggering
|
|
233
|
-
selection while a host dialog is open; it returns `false` if that document has
|
|
234
|
-
changed. `context.convertToMdz()` runs the built-in conversion and image action
|
|
235
|
-
against the same captured selection.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
`
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
`
|
|
247
|
-
`
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
`
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
archive
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
`
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
`
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1
|
+
[![MDZip logo][mdzip-logo]][mdzip-url]
|
|
2
|
+
|
|
3
|
+
[mdzip-logo]: https://raw.githubusercontent.com/mdzip-project/mdzip-editor/main/resources/mdzip-mark.svg
|
|
4
|
+
[mdzip-url]: https://mdzip.org
|
|
5
|
+
|
|
6
|
+
# @mdzip/editor
|
|
7
|
+
|
|
8
|
+
[](https://www.npmjs.com/package/@mdzip/editor)
|
|
9
|
+
[](https://github.com/mdzip-project/mdzip-editor/blob/main/LICENSE)
|
|
10
|
+
|
|
11
|
+
Framework-independent MDZip workspace engine and browser view.
|
|
12
|
+
|
|
13
|
+
`@mdzip/editor` provides reusable helpers for opening `.mdz` archives, rendering Markdown previews, editing archive contents, comparing archive inventories, and embedding a configurable MDZip workspace UI.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install @mdzip/editor
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Basic Usage
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { MdzipWorkspaceView } from '@mdzip/editor';
|
|
25
|
+
|
|
26
|
+
const view = new MdzipWorkspaceView(container, {
|
|
27
|
+
controls: 'viewer',
|
|
28
|
+
initialLayout: 'preview',
|
|
29
|
+
onFailed(error) {
|
|
30
|
+
console.error(error);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await view.open(bytes, {
|
|
35
|
+
mode: 'read-only',
|
|
36
|
+
fileName: 'document.mdz'
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Regular Markdown is also supported:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
await view.open(markdownBytes, {
|
|
44
|
+
mode: 'editable',
|
|
45
|
+
fileName: 'notes.md'
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
CodeMirror is not initialized until a source or split layout is used. Consumers
|
|
50
|
+
that only need archive, rendering, and preview helpers can import from
|
|
51
|
+
`@mdzip/editor/preview`; that entry point excludes `MdzipWorkspaceView` and its
|
|
52
|
+
CodeMirror dependencies from the module graph.
|
|
53
|
+
|
|
54
|
+
The filename normally selects the source format. Pass `sourceFormat:
|
|
55
|
+
'markdown'` or `sourceFormat: 'mdz'` to override detection.
|
|
56
|
+
|
|
57
|
+
Normalized `@mdzip/core-js` workspaces can be opened without an initial archive
|
|
58
|
+
rebuild:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
await view.openWorkspace(workspace, {
|
|
62
|
+
mode: 'editable',
|
|
63
|
+
fileName: 'document.mdz',
|
|
64
|
+
assetSourceId: 'document-etag-or-content-id'
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Images are resolved only when referenced by the active document or selected in
|
|
69
|
+
the navigator. To reuse resolved bytes across sessions, inject the optional
|
|
70
|
+
bounded IndexedDB cache:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { MdzipIndexedDbAssetCache, MdzipWorkspaceView } from '@mdzip/editor';
|
|
74
|
+
|
|
75
|
+
const view = new MdzipWorkspaceView(container, {
|
|
76
|
+
assetCache: new MdzipIndexedDbAssetCache({ maxBytes: 64 * 1024 * 1024 })
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
For `openWorkspace()`, provide a stable `assetSourceId` (or `archiveBytes`) so
|
|
81
|
+
cache hits can bypass lazy ZIP readers. Cache and storage failures automatically
|
|
82
|
+
fall back to archive reads.
|
|
83
|
+
|
|
84
|
+
## Editor Mode
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const view = new MdzipWorkspaceView(container, {
|
|
88
|
+
controls: 'standalone-editor',
|
|
89
|
+
async onSaved(bytes) {
|
|
90
|
+
await persist(bytes);
|
|
91
|
+
view.markPersisted();
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
await view.open(bytes, {
|
|
96
|
+
mode: 'editable',
|
|
97
|
+
fileName: 'document.mdz'
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
When `onSaved` is omitted, the built-in Save button downloads the current file
|
|
102
|
+
in the browser. When `onSaved` is provided, the host owns persistence and must
|
|
103
|
+
call `markPersisted()` after a successful write. Failed writes should leave the
|
|
104
|
+
workspace dirty.
|
|
105
|
+
|
|
106
|
+
## Control Presets
|
|
107
|
+
|
|
108
|
+
- `preview`: clean document preview with no toolbar or package navigation.
|
|
109
|
+
- `viewer`: read-only viewer controls, including navigation, layout switching, and zoom.
|
|
110
|
+
- `standalone-editor`: full editor controls, including save.
|
|
111
|
+
- `hosted-editor`: editor controls without an embedded save button, for hosts such as VS Code that own persistence.
|
|
112
|
+
|
|
113
|
+
Call `setControls(nextControls)` to update the policy after construction
|
|
114
|
+
without rebuilding the workspace view. `lineNumbers` changes are applied to the
|
|
115
|
+
existing CodeMirror editor, preserving the current document and selection.
|
|
116
|
+
|
|
117
|
+
## Density and Spacing
|
|
118
|
+
|
|
119
|
+
Hosts can opt into smaller built-in UI without targeting private classes:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
const view = new MdzipWorkspaceView(container, {
|
|
123
|
+
controls: 'hosted-editor',
|
|
124
|
+
toolbarDensity: 'compact', // 'comfortable' | 'compact' | 'dense'
|
|
125
|
+
contentDensity: 'compact' // 'comfortable' | 'compact'
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
view.setDensityOptions({
|
|
129
|
+
toolbarDensity: 'dense',
|
|
130
|
+
contentDensity: 'compact'
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
For exact sizing, set stable CSS custom properties on an ancestor of the
|
|
135
|
+
workspace:
|
|
136
|
+
|
|
137
|
+
```css
|
|
138
|
+
.studio-editor {
|
|
139
|
+
--mdzip-toolbar-button-size: 28px;
|
|
140
|
+
--mdzip-toolbar-compact-button-size: 26px;
|
|
141
|
+
--mdzip-toolbar-icon-size: 14px;
|
|
142
|
+
--mdzip-format-button-size: 26px;
|
|
143
|
+
--mdzip-format-icon-size: 14px;
|
|
144
|
+
--mdzip-toolbar-padding: 2px 8px;
|
|
145
|
+
--mdzip-toolbar-gap: 4px;
|
|
146
|
+
--mdzip-editor-content-padding: 16px 20px;
|
|
147
|
+
--mdzip-preview-content-padding: 16px 20px 24px;
|
|
148
|
+
--mdzip-preview-content-max-width: 720px;
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Preview Width
|
|
153
|
+
|
|
154
|
+
`previewMaxWidth` is a developer-facing option, not toolbar UI — it sets the
|
|
155
|
+
preview's reading-column width (`--mdzip-preview-content-max-width` under the
|
|
156
|
+
hood). A number is an exact pixel value; `'narrow'` / `'default'` / `'wide'`
|
|
157
|
+
are convenience aliases for 650 / 900 / 1200px:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
const view = new MdzipWorkspaceView(container, {
|
|
161
|
+
controls: 'hosted-editor',
|
|
162
|
+
previewMaxWidth: 'wide' // or an exact pixel number, e.g. 760
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
view.setPreviewMaxWidth(720);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Leaving it unset (the default) keeps the built-in CSS default in effect,
|
|
169
|
+
which scales with the zoom control; an explicit value set here (or via the
|
|
170
|
+
`--mdzip-preview-content-max-width` CSS variable directly) does not scale
|
|
171
|
+
with zoom — it's used exactly as given.
|
|
172
|
+
|
|
173
|
+
## Host Persistence
|
|
174
|
+
|
|
175
|
+
Desktop hosts can flush pending editor content, persist the returned bytes, and
|
|
176
|
+
only then acknowledge a successful write:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
const snapshot = await view.flush();
|
|
180
|
+
if (snapshot) {
|
|
181
|
+
await nativeSave(snapshot.bytes);
|
|
182
|
+
view.markPersisted();
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`flush()` deliberately leaves `dirty` set until `markPersisted()` is called.
|
|
187
|
+
`serialize()` and `getCurrentSnapshot()` provide non-acknowledging alternatives.
|
|
188
|
+
|
|
189
|
+
Structured callbacks are available for workspace, document, asset, manifest,
|
|
190
|
+
selection, dirty, validation, and snapshot changes. Asset hosts can also call
|
|
191
|
+
`addAsset()`, `replaceAsset()`, `removeAsset()`, and `listAssets()`.
|
|
192
|
+
|
|
193
|
+
## File Management (navigation pane)
|
|
194
|
+
|
|
195
|
+
With `controls: 'standalone-editor'` or `'hosted-editor'` (or `fileActions: true`
|
|
196
|
+
in a custom policy), the navigation pane offers a right-click context menu: new
|
|
197
|
+
`.md` file, new folder, rename/move (edit the full archive path), duplicate,
|
|
198
|
+
replace, download, copy markdown link / image embed, set entry point (shown bold
|
|
199
|
+
in the tree), set/remove cover image, and delete with a confirmation prompt
|
|
200
|
+
(orphaned assets delete immediately). The entry-point document and
|
|
201
|
+
`manifest.json` cannot be deleted. Copy and Download remain available in
|
|
202
|
+
read-only contexts.
|
|
203
|
+
|
|
204
|
+
Drag and drop is supported in all directions: move files between folders, drop
|
|
205
|
+
OS files onto the pane to add them as assets, drag a tree file onto the editor
|
|
206
|
+
to insert a markdown link or image embed at the pointer, and drop an OS image
|
|
207
|
+
onto the editor to embed it like a paste.
|
|
208
|
+
|
|
209
|
+
The same operations are available programmatically: `removeFile()`,
|
|
210
|
+
`renameFile()` (rewrites markdown references, including a moved document's own
|
|
211
|
+
relative links), `setEntryPoint()`, and `setCoverImage()`.
|
|
212
|
+
|
|
213
|
+
## Conversion Hook
|
|
214
|
+
|
|
215
|
+
For plain-markdown sources, hosts can take over the markdown→MDZ conversion
|
|
216
|
+
flow (triggered by the nav button, Insert Image, or an image paste/drop):
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
const view = new MdzipWorkspaceView(container, {
|
|
220
|
+
async onConversionRequested(action, context) {
|
|
221
|
+
// action.kind: 'navigation' | 'image-picker' | 'image-file' (with action.file)
|
|
222
|
+
const relativePath = await hostHandlesImage(action);
|
|
223
|
+
return relativePath
|
|
224
|
+
? context.insertMarkdown(``)
|
|
225
|
+
: false;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Returning or resolving `false` (or omitting the callback) keeps the built-in
|
|
231
|
+
conversion dialog. Errors thrown by the hook are reported via `onFailed` and
|
|
232
|
+
fall back to the built-in dialog. The context preserves the triggering
|
|
233
|
+
selection while a host dialog is open; it returns `false` if that document has
|
|
234
|
+
changed. `context.convertToMdz()` runs the built-in conversion and image action
|
|
235
|
+
against the same captured selection.
|
|
236
|
+
|
|
237
|
+
A host that writes the image file itself (a linked image next to the `.md`) can
|
|
238
|
+
still give the user the same Markdown/HTML, alt text, size and alignment choices
|
|
239
|
+
a `.mdz` paste gets: `await context.promptImageInsert({ bytes, fileName, altText })`
|
|
240
|
+
runs `imageInsertHandler` or the `'ask'` dialog and resolves `null` on cancel
|
|
241
|
+
(write nothing), then `context.formatImageInsert(src, decision)` returns the text
|
|
242
|
+
to pass to `context.insertMarkdown()`. `src` is used as given, so URL-encode it.
|
|
243
|
+
|
|
244
|
+
## Image Insert Hook
|
|
245
|
+
|
|
246
|
+
Set `imageInsertMode` to choose the built-in image markup flow:
|
|
247
|
+
`'markdown'` keeps the default `` insertion, `'html'` inserts a
|
|
248
|
+
default `<img>` element, and `'ask'` opens a small dialog for Markdown vs HTML,
|
|
249
|
+
alt text, proportional size, and alignment.
|
|
250
|
+
|
|
251
|
+
For host-owned UI, provide `imageInsertHandler`. It receives file metadata,
|
|
252
|
+
intrinsic image size when detected, and the source (`'paste'`, `'drop'`, or
|
|
253
|
+
`'picker'`). Return `{ mode: 'markdown', altText }` or
|
|
254
|
+
`{ mode: 'html', altText, width, height, position }`; return `null` to cancel.
|
|
255
|
+
The built-in dialog asks for width, height, or percent scaling and preserves
|
|
256
|
+
aspect ratio by emitting one dimension, or proportional dimensions for percent
|
|
257
|
+
scaling. Returning `undefined` falls back to `imageInsertMode`.
|
|
258
|
+
The built-in HTML path uses portable `align` attributes for positioning because
|
|
259
|
+
the default preview sanitizer strips inline `style` attributes.
|
|
260
|
+
|
|
261
|
+
`MdzipRenderingService` uses `defaultSafeMarkdownRenderer` when no renderer is
|
|
262
|
+
injected. The default renderer sanitizes generated HTML and unsafe URL schemes.
|
|
263
|
+
|
|
264
|
+
## Image Edit Hook
|
|
265
|
+
|
|
266
|
+
Editing an *existing* image (Markdown `![]()` or raw HTML `<img>`, including
|
|
267
|
+
one wrapped in `<p align="...">`) is fully opt-in — unlike image insertion,
|
|
268
|
+
there is no built-in fallback dialog and no `imageEditMode`. Set
|
|
269
|
+
`imageEditHandler` to enable it:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
const view = new MdzipWorkspaceView(container, {
|
|
273
|
+
async imageEditHandler(request) {
|
|
274
|
+
// request: { src, altText, width?, height?, position?, mode: 'markdown' | 'html' }
|
|
275
|
+
const decision = await hostEditDialog(request);
|
|
276
|
+
if (!decision) return null; // cancel — leaves the image untouched
|
|
277
|
+
return decision; // same shape as an image-insert decision
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
With no `imageEditHandler` set, clicking an existing image does nothing —
|
|
283
|
+
no edit affordance ever appears. With one set, clicking an image reference
|
|
284
|
+
in the source editor shows a small edit icon next to it; clicking that icon
|
|
285
|
+
calls the handler with the image's current alt/width/height/position parsed
|
|
286
|
+
from its existing Markdown or HTML, and rewrites that exact reference in
|
|
287
|
+
place with whatever decision is returned (same `{ mode, altText, width,
|
|
288
|
+
height, position }` shape `imageInsertHandler` returns). Reference-style
|
|
289
|
+
Markdown images (`![alt][label]`) aren't supported.
|
|
290
|
+
|
|
291
|
+
## Pack Files Hook
|
|
292
|
+
|
|
293
|
+
Hosts that let a user pick a folder (Electron dialog, VS Code workspace API,
|
|
294
|
+
etc.) can hand the collected files straight to the view instead of building
|
|
295
|
+
the `.mdz` themselves:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
const result = await view.packFilesAsWorkspace(
|
|
299
|
+
files, // Array<{ path: string; bytes: Uint8Array }>
|
|
300
|
+
{ title: 'My Project', fileName: 'project.mdz' }
|
|
301
|
+
);
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
With zero or one Markdown file among `files`, it packs Document mode
|
|
305
|
+
immediately and opens the result in memory — no prompt. With more than one,
|
|
306
|
+
it needs a Document-vs-Project mode and entry-point decision. Provide
|
|
307
|
+
`onPackRequested` to own that decision:
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
const view = new MdzipWorkspaceView(container, {
|
|
311
|
+
async onPackRequested(request, context) {
|
|
312
|
+
// request.markdownFiles, request.suggestedEntryPoint
|
|
313
|
+
const decision = await hostPicksModeAndEntryPoint(request);
|
|
314
|
+
if (!decision) return false; // fall back to the built-in dialog
|
|
315
|
+
await context.applyDecision(decision);
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Returning or resolving `false` (or omitting the callback) keeps the built-in
|
|
322
|
+
dialog — a mode toggle plus an entry-point picker listing the discovered
|
|
323
|
+
Markdown files. Errors thrown by the hook are reported via `onFailed` and
|
|
324
|
+
fall back to the built-in dialog. `context.applyDecision({ mode, entryPoint })`
|
|
325
|
+
performs the actual pack either way.
|
|
326
|
+
|
|
327
|
+
Document mode opens the packed archive in the view (unsaved); Project mode
|
|
328
|
+
returns the archive bytes without opening them, since only the host knows
|
|
329
|
+
where a project archive should be saved — save it, then `view.open(bytes, ...)`.
|
|
330
|
+
|
|
331
|
+
## Rendering Extensibility
|
|
332
|
+
|
|
333
|
+
The view accepts a custom markdown renderer, composable markdown pipeline
|
|
334
|
+
extensions, and entry renderers that replace the content area for selected
|
|
335
|
+
archive entries — all optional, with built-in behavior as the fallback:
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
import { mdzipPathMatcher, type MdzipEntryRenderer } from '@mdzip/editor';
|
|
339
|
+
|
|
340
|
+
const manifestRenderer: MdzipEntryRenderer = {
|
|
341
|
+
id: 'host-manifest-editor',
|
|
342
|
+
priority: 100,
|
|
343
|
+
matches: mdzipPathMatcher('manifest.json'),
|
|
344
|
+
mount: (container, context) => mountManifestEditor(container, {
|
|
345
|
+
manifest: context.manifest,
|
|
346
|
+
editable: context.mode === 'editable',
|
|
347
|
+
onChange: (manifest) => context.updateManifest(manifest)
|
|
348
|
+
})
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const view = new MdzipWorkspaceView(container, {
|
|
352
|
+
markdownRenderer, // optional full renderer replacement
|
|
353
|
+
markdownExtensions: [mermaidExt], // transformMarkdown/transformHtml/mount
|
|
354
|
+
entryRenderers: [manifestRenderer]
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Renderers may be asynchronous; stale results are dropped when the selection
|
|
359
|
+
moves on. Sanitization stays in the pipeline: string output from custom
|
|
360
|
+
renderers and transform hooks passes through DOMPurify before insertion. Entry
|
|
361
|
+
renderer handles are destroyed on selection change and `destroy()`. The same
|
|
362
|
+
options are available as inputs/props on the Angular, React, and Vue wrappers.
|
|
363
|
+
See the Developer Guide's Rendering Extensibility section for the contracts
|
|
364
|
+
and lifecycle rules.
|
|
365
|
+
|
|
366
|
+
An extension whose `transformHtml` emits markup the default policy would strip
|
|
367
|
+
(inline SVG, for example) declares the narrow relaxations it needs via a
|
|
368
|
+
`sanitize` contribution (`MdzipSanitizeContribution`), merged into the single
|
|
369
|
+
DOMPurify pass. Keep contributions minimal — they widen the policy for the
|
|
370
|
+
whole preview, since the extension output and surrounding markdown HTML are
|
|
371
|
+
sanitized together.
|
|
372
|
+
|
|
373
|
+
### Mermaid diagrams
|
|
374
|
+
|
|
375
|
+
Render fenced ` ```mermaid ` blocks to inline SVG with the optional extension
|
|
376
|
+
from the `@mdzip/editor/mermaid` entrypoint. It is shipped separately so the
|
|
377
|
+
~1MB mermaid library stays out of the core bundle — it is dynamically imported
|
|
378
|
+
the first time a document actually contains a mermaid block. Install `mermaid`
|
|
379
|
+
(an optional peer dependency) alongside `@mdzip/editor`:
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
import { mdzipMermaidExtension } from '@mdzip/editor/mermaid';
|
|
383
|
+
|
|
384
|
+
const view = new MdzipWorkspaceView(container, {
|
|
385
|
+
markdownExtensions: [mdzipMermaidExtension({ theme: 'auto' })]
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Diagrams render with mermaid's `strict` security level, each SVG is
|
|
390
|
+
re-sanitized before insertion, and an invalid diagram renders as an inline
|
|
391
|
+
error block instead of breaking the preview. `theme` defaults to `'auto'`,
|
|
392
|
+
following the preview color scheme. Enabling the extension widens the preview's
|
|
393
|
+
sanitize policy to allow SVG and inline styles for the whole render. For a
|
|
394
|
+
CSP-restricted host, mermaid bundles into the consumer's webview script (no new
|
|
395
|
+
`img-src` needs); if you lazy-load its chunk, serve it under the existing
|
|
396
|
+
`script-src` nonce.
|
|
397
|
+
|
|
398
|
+
### Front matter
|
|
399
|
+
|
|
400
|
+
A leading `---`-delimited YAML block is recognized automatically — no
|
|
401
|
+
`markdownExtensions` wiring needed — and no longer falls through to `marked`
|
|
402
|
+
as a stray `<hr>` plus paragraph/setext heading. Control how it renders with
|
|
403
|
+
`frontMatter`:
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
const view = new MdzipWorkspaceView(container, {
|
|
407
|
+
frontMatter: {
|
|
408
|
+
enabled: true, // default; false strips it with nothing rendered in its place
|
|
409
|
+
display: 'table', // default; 'raw' shows the YAML source as a syntax-highlighted code block
|
|
410
|
+
collapsible: true, // default; false renders a static, always-visible block (no collapse toggle)
|
|
411
|
+
label: 'Front matter' // default; a custom string, or the sentinel 'first-line' — see below
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
- `display: 'table'` (default) — a key/value table. `'raw'` — the block's YAML
|
|
417
|
+
source as a fenced, syntax-highlighted `yaml` code block. Either way the
|
|
418
|
+
panel is wrapped the same: same header, same expand/collapse behavior.
|
|
419
|
+
- `collapsible: false` turns off the collapsible `<details>` wrapper,
|
|
420
|
+
rendering a static block with the same header and body instead.
|
|
421
|
+
- `label` sets the header text — a fixed string (`'Front Matter'`, `'Metadata'`,
|
|
422
|
+
etc.), or the sentinel `'first-line'` to use the block's own first raw YAML
|
|
423
|
+
line instead (e.g. `title: My Document`), falling back to the default label
|
|
424
|
+
when that line is blank.
|
|
425
|
+
- `enabled: false` strips the block with nothing rendered in its place.
|
|
426
|
+
|
|
427
|
+
`setRenderingOptions({ frontMatter })` updates it live. A manifest-less
|
|
428
|
+
`.md` file's front matter `title:` also feeds the title-fallback chain
|
|
429
|
+
(`suggestedTitleFromMarkdown`), ahead of the first heading and filename. The
|
|
430
|
+
parser itself (`parseFrontMatter`, DOM-free) is exported from the package
|
|
431
|
+
root for hosts that want the raw data without the render extension.
|
|
432
|
+
|
|
433
|
+
## Developer Guide
|
|
434
|
+
|
|
435
|
+
See the [Developer Guide](https://github.com/mdzip-project/mdzip-editor/blob/main/docs/developer-guide.md)
|
|
436
|
+
for granular host controls, height requirements, lifecycle events, persistence,
|
|
437
|
+
theming, and Raw, Angular, React, and Vue examples.
|
|
438
|
+
|
|
439
|
+
See the [Theming Guide](https://github.com/mdzip-project/mdzip-editor/blob/main/docs/theming.md)
|
|
440
|
+
for custom theme examples and the complete CSS variable reference.
|
|
441
|
+
|
|
442
|
+
## Archive Helpers
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
import {
|
|
446
|
+
openMdzArchive,
|
|
447
|
+
readCanonicalMarkdown,
|
|
448
|
+
createArchiveInventory,
|
|
449
|
+
diffArchiveInventories
|
|
450
|
+
} from '@mdzip/editor';
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
These helpers are built on `@mdzip/core-js` and are suitable for framework wrappers, desktop hosts, browser apps, and extension integrations.
|
|
454
|
+
|
|
455
|
+
## Archive Diff View
|
|
456
|
+
|
|
457
|
+
The optional diff entry point keeps merge dependencies out of normal editor
|
|
458
|
+
and preview bundles:
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
import { MdzipDiffView } from '@mdzip/editor/diff-view';
|
|
462
|
+
|
|
463
|
+
const diff = new MdzipDiffView(container, {
|
|
464
|
+
before: { bytes: baseBytes, label: 'Git base' },
|
|
465
|
+
after: { bytes: workingBytes, label: 'Working tree' },
|
|
466
|
+
initialPath: 'index.md',
|
|
467
|
+
toolbarActions: [{
|
|
468
|
+
id: 'refresh',
|
|
469
|
+
label: 'Refresh comparison',
|
|
470
|
+
icon: 'refresh',
|
|
471
|
+
run: refreshComparison
|
|
472
|
+
}]
|
|
473
|
+
});
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Construction opens the initial comparison. Use `await diff.open(options)` to
|
|
477
|
+
replace both sides, `openPath(path)` to select an entry, and
|
|
478
|
+
`setShowUnchanged(false)` to focus the tree on changes. Toolbar actions may be
|
|
479
|
+
updated with `setToolbarActions()` without reopening either archive.
|
|
480
|
+
|
|
481
|
+
The library-owned toolbar provides a navigation toggle, Previous/Next change
|
|
482
|
+
buttons, and a Show-unchanged toggle. Drive change traversal in code with
|
|
483
|
+
`openPreviousChange()` / `openNextChange()` (they walk non-unchanged entries
|
|
484
|
+
and resolve `false` at the ends). Opt individual built-in controls out with
|
|
485
|
+
the `controls` option (`navigation`, `changeTraversal`, `showUnchanged`; all
|
|
486
|
+
default on). The navigation pane animates open/closed like the workspace nav
|
|
487
|
+
pane.
|
|
488
|
+
|
|
489
|
+
The read-only view shows the union of archive paths in a directory tree,
|
|
490
|
+
added/removed/changed status, side-by-side text diffs, explicit missing-side
|
|
491
|
+
states, image previews with metadata, and binary metadata. Entry content is
|
|
492
|
+
loaded only when selected. Call `destroy()` to release editors, listeners, and
|
|
493
|
+
image object URLs.
|
|
494
|
+
|
|
495
|
+
## Preview Lifecycle Signals
|
|
496
|
+
|
|
497
|
+
To reveal or animate read-only preview content without scraping internal DOM,
|
|
498
|
+
use the preview lifecycle signals on `MdzipWorkspaceView`:
|
|
499
|
+
|
|
500
|
+
```ts
|
|
501
|
+
const view = new MdzipWorkspaceView(container, {
|
|
502
|
+
controls: 'preview',
|
|
503
|
+
onPreviewRendered: (snapshot) => { /* preview HTML is mounted */ },
|
|
504
|
+
onAssetsHydrated: (snapshot) => { /* its images have finished loading */ }
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// Or await the fullest "ready" point (mounted + images hydrated):
|
|
508
|
+
await view.whenRendered();
|
|
509
|
+
revealContent();
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
`whenRendered()` resolves immediately when the latest preview is already
|
|
513
|
+
hydrated, and resolves (rather than hanging) if the view is destroyed.
|
|
514
|
+
|
|
515
|
+
The preview hydrates images progressively: the text mounts right away with each
|
|
516
|
+
archive image in a collapsed slot, so the reader gets a compact, readable text
|
|
517
|
+
block first. As each image resolves, its slot eases open to the height reserved
|
|
518
|
+
from dimensions sniffed out of the image header — a single slide to the exact
|
|
519
|
+
box, so the pixels arrive with no further reflow (and it snaps open instead
|
|
520
|
+
under `prefers-reduced-motion`). `onPreviewRendered` fires when the text is
|
|
521
|
+
mounted; `onAssetsHydrated` fires once every referenced image has resolved and
|
|
522
|
+
its final `src` is assigned.
|
|
523
|
+
|
|
524
|
+
In live-editing hosts where the preview re-renders frequently, pass
|
|
525
|
+
`imageHydrationAnimation: 'initial'` to keep the first-load reveal but snap
|
|
526
|
+
images open on same-document edits. Use `'off'` to disable the loading pulse
|
|
527
|
+
and slide-open animation entirely.
|
|
528
|
+
|
|
529
|
+
## Content Security Policy (restricted hosts)
|
|
530
|
+
|
|
531
|
+
Archive images resolve to `URL.createObjectURL()` **`blob:` object URLs** (the
|
|
532
|
+
view falls back to `data:` URLs only where `createObjectURL` is unavailable,
|
|
533
|
+
such as Node). When embedding the editor in a host with a restrictive
|
|
534
|
+
Content-Security-Policy — a VS Code webview, a sandboxed iframe, etc. — the
|
|
535
|
+
policy's `img-src` **must include `blob:`**, or every archive image renders as a
|
|
536
|
+
broken placeholder:
|
|
537
|
+
|
|
538
|
+
```
|
|
539
|
+
img-src 'self' <cspSource> blob: data:;
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
Including `data:` as well lets the view's built-in recovery path work: if a
|
|
543
|
+
`blob:` URL fails to load (for example because `img-src` omits `blob:`), the
|
|
544
|
+
view retries that image once with a `data:` URL and, if it still fails, reports
|
|
545
|
+
the error through the `onFailed` option instead of failing silently. Listen on
|
|
546
|
+
`onFailed` to surface image-load problems during development:
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
const view = new MdzipWorkspaceView(container, {
|
|
550
|
+
onFailed: (error) => console.warn('[mdzip]', error)
|
|
551
|
+
});
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
Call `destroy()` to revoke the object URLs the view created.
|