@mdzip/editor 1.3.21 → 1.4.4

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.
Files changed (74) hide show
  1. package/README.md +62 -0
  2. package/dist/archive-utils.d.ts.map +1 -1
  3. package/dist/archive-utils.js +16 -2
  4. package/dist/archive-utils.js.map +1 -1
  5. package/dist/asset-cache.d.ts +14 -0
  6. package/dist/asset-cache.d.ts.map +1 -1
  7. package/dist/asset-cache.js +57 -0
  8. package/dist/asset-cache.js.map +1 -1
  9. package/dist/front-matter-extension.d.ts +45 -0
  10. package/dist/front-matter-extension.d.ts.map +1 -0
  11. package/dist/front-matter-extension.js +114 -0
  12. package/dist/front-matter-extension.js.map +1 -0
  13. package/dist/front-matter.d.ts +18 -0
  14. package/dist/front-matter.d.ts.map +1 -0
  15. package/dist/front-matter.js +48 -0
  16. package/dist/front-matter.js.map +1 -0
  17. package/dist/highlight-core.d.ts +3 -0
  18. package/dist/highlight-core.d.ts.map +1 -0
  19. package/dist/highlight-core.js +53 -0
  20. package/dist/highlight-core.js.map +1 -0
  21. package/dist/image-edit.d.ts +26 -0
  22. package/dist/image-edit.d.ts.map +1 -0
  23. package/dist/image-edit.js +195 -0
  24. package/dist/image-edit.js.map +1 -0
  25. package/dist/index.d.ts +4 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +4 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/library-info.d.ts +2 -2
  30. package/dist/library-info.js +2 -2
  31. package/dist/library-info.js.map +1 -1
  32. package/dist/mdz-archive-worker-protocol.d.ts +36 -0
  33. package/dist/mdz-archive-worker-protocol.d.ts.map +1 -0
  34. package/dist/mdz-archive-worker-protocol.js +2 -0
  35. package/dist/mdz-archive-worker-protocol.js.map +1 -0
  36. package/dist/mdz-archive.worker-client.d.ts +30 -0
  37. package/dist/mdz-archive.worker-client.d.ts.map +1 -0
  38. package/dist/mdz-archive.worker-client.js +97 -0
  39. package/dist/mdz-archive.worker-client.js.map +1 -0
  40. package/dist/mdz-archive.worker.d.ts +2 -0
  41. package/dist/mdz-archive.worker.d.ts.map +1 -0
  42. package/dist/mdz-archive.worker.js +8870 -0
  43. package/dist/mdz-archive.worker.js.map +1 -0
  44. package/dist/mermaid.d.ts.map +1 -1
  45. package/dist/mermaid.js +60 -41
  46. package/dist/mermaid.js.map +1 -1
  47. package/dist/metadata.d.ts +1 -1
  48. package/dist/metadata.d.ts.map +1 -1
  49. package/dist/metadata.js +12 -2
  50. package/dist/metadata.js.map +1 -1
  51. package/dist/preview.d.ts +1 -0
  52. package/dist/preview.d.ts.map +1 -1
  53. package/dist/preview.js +1 -0
  54. package/dist/preview.js.map +1 -1
  55. package/dist/rendering.d.ts +114 -0
  56. package/dist/rendering.d.ts.map +1 -1
  57. package/dist/rendering.js +153 -1
  58. package/dist/rendering.js.map +1 -1
  59. package/dist/stats.d.ts +23 -0
  60. package/dist/stats.d.ts.map +1 -0
  61. package/dist/stats.js +31 -0
  62. package/dist/stats.js.map +1 -0
  63. package/dist/view-css.d.ts.map +1 -1
  64. package/dist/view-css.js +112 -0
  65. package/dist/view-css.js.map +1 -1
  66. package/dist/view.d.ts +421 -1
  67. package/dist/view.d.ts.map +1 -1
  68. package/dist/view.js +2219 -410
  69. package/dist/view.js.map +1 -1
  70. package/dist/workspace.d.ts +14 -0
  71. package/dist/workspace.d.ts.map +1 -1
  72. package/dist/workspace.js +53 -41
  73. package/dist/workspace.js.map +1 -1
  74. package/package.json +11 -5
package/dist/view.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { MdzWorkspace, MdzWorkspaceAsset } from '@mdzip/core-js';
2
2
  import { type MdzipAssetCache } from './asset-cache.js';
3
3
  import type { MdzipDocumentChangeEvent, MdzipEditorSnapshot, MdzipRemoveAssetOptions, MdzipWorkspaceOpenOptions } from './workspace.js';
4
4
  import { type MdzipEntryRenderer, type MdzipMarkdownRenderer, type MdzipMarkdownRenderExtension } from './rendering.js';
5
+ import { type MdzipFrontMatterOptions } from './front-matter-extension.js';
5
6
  import type { MdzipWorkspaceSnapshot } from './workspace.js';
6
7
  export declare const DEFAULT_CODE_BLOCK_LANGUAGES: readonly MdzipCodeBlockLanguage[];
7
8
  export type MdzipWorkspaceLayout = 'preview' | 'source' | 'split';
@@ -38,6 +39,28 @@ export interface MdzipImageInsertDecision {
38
39
  position?: MdzipImagePosition;
39
40
  }
40
41
  export type MdzipImageInsertHandler = (request: MdzipImageInsertRequest) => MdzipImageInsertDecision | null | undefined | Promise<MdzipImageInsertDecision | null | undefined>;
42
+ /**
43
+ * Parsed from the image reference (Markdown or raw HTML) currently under an
44
+ * edit-affordance click.
45
+ */
46
+ export interface MdzipImageEditRequest {
47
+ src: string;
48
+ altText: string;
49
+ width?: number;
50
+ height?: number;
51
+ position?: MdzipImagePosition;
52
+ /** Which form the image is currently written in. */
53
+ mode: MdzipImageInsertOutputMode;
54
+ }
55
+ /**
56
+ * Host-owned async UI for editing an existing image's alt/size/position.
57
+ * Invoked when the user clicks an existing image's edit affordance in the
58
+ * source editor; returning `null`/`undefined` cancels, leaving the image
59
+ * untouched. Reuses `MdzipImageInsertDecision` as its resolution shape.
60
+ * Fully opt-in: with no `imageEditHandler` set, no affordance ever appears
61
+ * and clicking an image does nothing — there is no built-in fallback dialog.
62
+ */
63
+ export type MdzipImageEditHandler = (request: MdzipImageEditRequest) => MdzipImageInsertDecision | null | undefined | Promise<MdzipImageInsertDecision | null | undefined>;
41
64
  export type MdzipEditorCommand = 'bold' | 'italic' | 'strikethrough' | 'highlight' | 'paragraph' | `heading-${MdzipHeadingLevel}` | 'bullet-list' | 'ordered-list' | 'inline-code' | 'code-block' | 'blockquote' | 'insert-line-break' | 'link' | 'insert-image';
42
65
  export type MdzipConversionAction = {
43
66
  kind: 'navigation';
@@ -228,6 +251,19 @@ export interface MdzipWorkspaceViewOptions {
228
251
  * not (see {@link MdzipWorkspaceView.setPreviewMaxWidth}).
229
252
  */
230
253
  previewMaxWidth?: MdzipPreviewMaxWidth;
254
+ /**
255
+ * Opt-in: render+mount the preview in chunks near the viewport instead of
256
+ * the whole document at once. For very large documents (e.g. a chat
257
+ * export with thousands of message rows) this keeps first-paint cost
258
+ * proportional to what's visible instead of the whole document's size —
259
+ * `marked`/DOMPurify otherwise run once, synchronously, over the entire
260
+ * rendered HTML before anything is mounted, which can block the main
261
+ * thread for seconds on real-world large files. Defaults to `false`.
262
+ * Only takes effect with the default marked-based renderer — a
263
+ * host-supplied custom {@link MdzipMarkdownRenderer} has no token
264
+ * structure to chunk by, so this option is silently ignored for it.
265
+ */
266
+ progressiveTextRendering?: boolean;
231
267
  /**
232
268
  * Controls the progressive preview image reveal animation. Use `'off'` in
233
269
  * live-editing hosts to prevent images from pulsing/sliding. Use `'initial'`
@@ -248,6 +284,14 @@ export interface MdzipWorkspaceViewOptions {
248
284
  * the insertion cleanly.
249
285
  */
250
286
  imageInsertHandler?: MdzipImageInsertHandler;
287
+ /**
288
+ * Optional host-owned async UI for editing an existing image (alt/size/
289
+ * position), opened by clicking that image's edit affordance in the
290
+ * source editor. Unset by default — with no handler, no affordance
291
+ * appears at all; there is no built-in fallback dialog like
292
+ * `imageInsertMode: 'ask'` provides for insertion.
293
+ */
294
+ imageEditHandler?: MdzipImageEditHandler;
251
295
  /**
252
296
  * Languages offered in the context menu's Code Block submenu. Each entry's
253
297
  * `id` becomes the fence info string (e.g. ```` ```ts ````); an empty `id`
@@ -337,6 +381,14 @@ export interface MdzipWorkspaceViewOptions {
337
381
  * the fallback. Handles are destroyed on selection change and destroy().
338
382
  */
339
383
  entryRenderers?: readonly MdzipEntryRenderer[];
384
+ /**
385
+ * Controls how a leading `---`-delimited YAML front matter block renders
386
+ * in the preview — display (table/raw), the collapsible expander, and its
387
+ * label. Unlike `markdownExtensions` (which is entirely opt-in), front
388
+ * matter handling is always registered; this only configures it. See
389
+ * {@link MdzipFrontMatterOptions}.
390
+ */
391
+ frontMatter?: MdzipFrontMatterOptions;
340
392
  }
341
393
  /** Options accepted by {@link MdzipWorkspaceView.setRenderingOptions}. */
342
394
  export interface MdzipRenderingOptions {
@@ -344,6 +396,7 @@ export interface MdzipRenderingOptions {
344
396
  markdownRenderer?: MdzipMarkdownRenderer | null;
345
397
  markdownExtensions?: readonly MdzipMarkdownRenderExtension[];
346
398
  entryRenderers?: readonly MdzipEntryRenderer[];
399
+ frontMatter?: MdzipFrontMatterOptions;
347
400
  }
348
401
  export declare function resolveMdzipControlPolicy(controls: MdzipControlPreset | MdzipControlPolicy | undefined): MdzipResolvedControlPolicy;
349
402
  export declare class MdzipWorkspaceView {
@@ -354,6 +407,7 @@ export declare class MdzipWorkspaceView {
354
407
  private controlPolicy;
355
408
  private readonly navigationMode;
356
409
  private imageHydrationAnimation;
410
+ private readonly progressiveTextRendering;
357
411
  private toolbarDensity;
358
412
  private contentDensity;
359
413
  private previewMaxWidth;
@@ -368,6 +422,11 @@ export declare class MdzipWorkspaceView {
368
422
  private conversionAction;
369
423
  private imageInsertDialogState;
370
424
  private packFilesDialogState;
425
+ private chunkedRenderState;
426
+ private chunkHtmlCache;
427
+ private copyRenderDialogState;
428
+ private copyReadyState;
429
+ private copyRenderDoneState;
371
430
  private navPaneWidth;
372
431
  private splitRatio;
373
432
  private resizing;
@@ -384,6 +443,9 @@ export declare class MdzipWorkspaceView {
384
443
  private tooltipState;
385
444
  private tooltipShowTimer;
386
445
  private tooltipHideTimer;
446
+ private copyToastHideTimer;
447
+ private clipboardWriteTimeoutMs;
448
+ private clipboardFallbackWriteTimeoutMs;
387
449
  private cmEditor;
388
450
  private readonly readOnlyCompartment;
389
451
  private readonly lineNumbersCompartment;
@@ -392,8 +454,17 @@ export declare class MdzipWorkspaceView {
392
454
  private syncing;
393
455
  private lastSyncedEditorScrollTop;
394
456
  private lastSyncedPreviewScrollTop;
457
+ private lastContentChangeTime;
458
+ private static readonly SCROLL_SYNC_SETTLE_MS;
459
+ private lastEditorGestureTime;
460
+ private lastPreviewGestureTime;
461
+ private static readonly SCROLL_SYNC_GESTURE_WINDOW_MS;
462
+ private pendingPreviewScrollRestore;
463
+ private bottomDrainGeneration;
464
+ private scrollCatchUpState;
395
465
  private markdownRenderer?;
396
466
  private markdownExtensions;
467
+ private frontMatterOptions;
397
468
  private entryRenderers;
398
469
  private renderingService;
399
470
  private previewMemo;
@@ -462,6 +533,17 @@ export declare class MdzipWorkspaceView {
462
533
  private readonly elPackFilesModeProject;
463
534
  private readonly elPackFilesEntrySelect;
464
535
  private readonly elPackFilesConfirmBtn;
536
+ private readonly elCopyRenderDialog;
537
+ private readonly elCopyRenderHeading;
538
+ private readonly elCopyRenderProgressSection;
539
+ private readonly elCopyRenderProgressBar;
540
+ private readonly elCopyRenderProgressText;
541
+ private readonly elCopyRenderReadyText;
542
+ private readonly elCopyRenderDoneText;
543
+ private readonly elCopyRenderCancelBtn;
544
+ private readonly elCopyRenderReadyCancelBtn;
545
+ private readonly elCopyRenderConfirmBtn;
546
+ private readonly elCopyRenderDismissBtn;
465
547
  private readonly elNavMenu;
466
548
  private readonly elNameDialog;
467
549
  private readonly elNameDialogHeading;
@@ -473,6 +555,7 @@ export declare class MdzipWorkspaceView {
473
555
  private readonly elDeleteConfirmBtn;
474
556
  private readonly elReplaceInput;
475
557
  private readonly elTooltip;
558
+ private readonly elCopyToast;
476
559
  private readonly elEmptyState;
477
560
  constructor(container: HTMLElement, options?: MdzipWorkspaceViewOptions);
478
561
  /**
@@ -545,6 +628,26 @@ export declare class MdzipWorkspaceView {
545
628
  * renderer re-match.
546
629
  */
547
630
  setRenderingOptions(options: MdzipRenderingOptions): void;
631
+ /**
632
+ * The extensions actually handed to `MdzipRenderingService`: the front
633
+ * matter extension first (always on — see `frontMatterOptions`), then
634
+ * whatever extensions the host passed via `markdownExtensions`. A fresh
635
+ * front matter extension instance is created each time this runs (view
636
+ * construction, `setRenderingOptions`), which is fine — its per-render
637
+ * state lives in a `WeakMap` keyed by the render context, not on the
638
+ * extension instance across renders.
639
+ */
640
+ private pipelineMarkdownExtensions;
641
+ /**
642
+ * Chunking options combining every registered extension's
643
+ * {@link MdzipMarkdownRenderExtension.shouldIsolateChunk} — see that doc
644
+ * comment for why a token an extension owns needs its own chunk regardless
645
+ * of the size budget — with the same treatment built in for any token that
646
+ * embeds an image ({@link tokenEmbedsImage}'s doc comment), plus a forced
647
+ * chunk boundary at every heading ({@link tokenIsHeading}'s doc comment)
648
+ * so unrelated sections never share a chunk.
649
+ */
650
+ private chunkOptions;
548
651
  /**
549
652
  * Replaces the view control policy without recreating the workspace or
550
653
  * CodeMirror editor. In particular, `lineNumbers` is reconfigured through a
@@ -562,6 +665,7 @@ export declare class MdzipWorkspaceView {
562
665
  */
563
666
  setPreviewMaxWidth(value: MdzipPreviewMaxWidth | undefined): void;
564
667
  setImageInsertOptions(options: Pick<MdzipWorkspaceViewOptions, 'imageInsertMode' | 'imageInsertHandler'>): void;
668
+ setImageEditOptions(options: Pick<MdzipWorkspaceViewOptions, 'imageEditHandler'>): void;
565
669
  private applyDensityClasses;
566
670
  /**
567
671
  * Tears down all custom rendering state: aborts in-flight renders, destroys
@@ -572,6 +676,8 @@ export declare class MdzipWorkspaceView {
572
676
  private replaceAssetSession;
573
677
  private resetPreviewState;
574
678
  private destroyPreviewHandles;
679
+ /** True when `this.previewMemo` — and by extension `this.previewGeneration` — still represents `snapshot`: nothing that feeds the preview (path, pathType, text, colorScheme) has changed since it was last rendered. */
680
+ private previewMemoMatchesSnapshot;
575
681
  private updatePreview;
576
682
  private applyPreviewHtml;
577
683
  private shouldAnimateImageHydration;
@@ -583,6 +689,160 @@ export declare class MdzipWorkspaceView {
583
689
  * has resolved and had its final `src` assigned (or there are none).
584
690
  */
585
691
  private mountProgressivePreview;
692
+ /**
693
+ * Cheap, attribute-only pass over every `<img>` under `roots`: no DOM tree
694
+ * mutation. Archive-relative sources have their `src` stripped here
695
+ * (immediately, so the browser never fires a bad network request for the
696
+ * archive-relative path) and are returned for {@link hydrateImages};
697
+ * external/data/fragment sources are left untouched (just get their align
698
+ * class, if any — they never get a slot). Returns `[]` without touching
699
+ * anything when there's no asset session to resolve archive images
700
+ * against, matching `mountPreviewHtml`'s no-op image handling.
701
+ *
702
+ * An image this session has *already* resolved once (e.g. it was already
703
+ * on screen before an edit elsewhere caused its chunk to remount) is
704
+ * applied immediately here via `resolveKnownImage` — no slot, no loading
705
+ * class, no `hydrateImages`/`IntersectionObserver` round trip — instead of
706
+ * being handed to the async placeholder-then-swap path meant for an image
707
+ * that's genuinely being resolved for the first time. Skipping that path
708
+ * for a re-mount of an already-known image is what actually avoids the
709
+ * visible flash; still going through it (even instantly) briefly blanks
710
+ * the image, since a fresh `<img>` element never carries over its
711
+ * predecessor's already-resolved `src`.
712
+ */
713
+ private collectPendingImages;
714
+ /**
715
+ * Slot-wraps and resolves `pending` images (from {@link collectPendingImages}),
716
+ * only decompressing/resolving ones near the viewport up front — a
717
+ * document with a few hundred distinct embedded images would otherwise
718
+ * decompress and blob-URL every one of them synchronously, freezing the UI
719
+ * for as long as that takes. Falls back to eager resolution when
720
+ * IntersectionObserver isn't available (e.g. non-browser hosts). Calls
721
+ * `onSettled` once every image in `pending` has resolved (or immediately
722
+ * if `pending` is empty) — callers that want a single "hydrated" signal
723
+ * for a whole preview pass `pending` from every root at once; callers
724
+ * mounting one chunk among several within the same generation (see
725
+ * {@link mountChunkBatch}) pass a no-op instead, so the signal only fires
726
+ * once, for the batch it's semantically tied to.
727
+ */
728
+ private hydrateImages;
729
+ /**
730
+ * Opt-in (`progressiveTextRendering`) alternative to the whole-document
731
+ * `renderMarkdown()` path above: tokenizes the markdown once, then renders
732
+ * and mounts it in chunks near the viewport instead of all at once — see
733
+ * {@link mountChunkedPreview}. Only reachable when
734
+ * `renderingService.supportsChunking` is true (the default marked-based
735
+ * renderer); the caller already checked that.
736
+ */
737
+ private renderChunkedPreview;
738
+ /**
739
+ * Same-document, text-only-edit counterpart to {@link renderChunkedPreview}:
740
+ * tokenizes the new text, then reconciles against the *previous* render's
741
+ * records (`priorState`) instead of tearing everything down. Only reachable
742
+ * when `updatePreview` determined the prior chunked structure is still
743
+ * reuse-eligible (same path/pathType/colorScheme); the caller already
744
+ * checked that.
745
+ */
746
+ private reconcileChunkedPreview;
747
+ /** Destroys one chunk record's mounted DOM and handles (but not the record object itself). */
748
+ private teardownChunkRecord;
749
+ /**
750
+ * Diffs the new tokenize pass's chunks against `priorState.records` by
751
+ * source-text identity (`chunkSourceKey`) and reconciles: chunks in the
752
+ * matched leading/trailing run keep their existing DOM and handles
753
+ * untouched (never re-rendered, never re-mounted — see
754
+ * `chunkSourceKey`'s doc comment for why that matters for extensions like
755
+ * mermaid); only the "middle" range that actually changed is torn down and
756
+ * remounted. Uses prefix/suffix matching rather than a general LCS — a
757
+ * text editor's edits are localized, so this is both simpler and correct
758
+ * for the real-world case.
759
+ */
760
+ private applyChunkReconciliation;
761
+ /**
762
+ * Mounts a reconciliation's replacement "middle" range in full (uncapped —
763
+ * a single edit only ever touches 1-2 chunks in practice; see the plan's
764
+ * disclosed v1 risk for the pathological large-paste case), then carries
765
+ * forward however much of the surviving suffix was already mounted before
766
+ * reconciling, and re-arms the sentinel for whatever's still left.
767
+ */
768
+ private mountReconciledMiddle;
769
+ /**
770
+ * Groups tokens into chunks and mounts them: an initial batch synchronously
771
+ * (enough for a small document to behave exactly like the non-chunked
772
+ * path), then the rest as the user scrolls near a trailing sentinel
773
+ * element, via the same `IntersectionObserver` + time-budgeted-rAF pattern
774
+ * already used for image hydration — just for "need more text" instead of
775
+ * "need this image". `onAssetsHydrated` fires once for the initial batch's
776
+ * images only (not re-fired per later chunk) — `onPreviewRendered` still
777
+ * means "the initial batch is in the DOM", same intent as the non-chunked
778
+ * path, just proportional to the viewport instead of the whole document.
779
+ */
780
+ private mountChunkedPreview;
781
+ /**
782
+ * Mounts every chunk of a non-progressive (`progressiveTextRendering:
783
+ * false`) document up front — same end state as the old monolithic
784
+ * non-chunked path (the whole document mounted before `onPreviewRendered`
785
+ * fires), but yielding a frame between batches (mirroring
786
+ * `drainRemainingChunks`) instead of blocking the main thread for one
787
+ * giant synchronous render.
788
+ */
789
+ private mountAllChunksEagerly;
790
+ /**
791
+ * Updates `chunkedRenderState.cursor` after a batch mounts. A no-op if a
792
+ * newer render generation has already superseded this one. Deliberately
793
+ * never nulls `chunkedRenderState` out just because `cursor` reached
794
+ * `records.length` — the records stay alive for the whole generation so a
795
+ * later same-document edit can reconcile against them (see
796
+ * `applyChunkReconciliation`). Callers that need "is there still an
797
+ * unmounted lazy tail" (Copy All's instant path, the scroll-to-bottom fast
798
+ * path) check `cursor < records.length` directly instead of this field's
799
+ * truthiness.
800
+ */
801
+ private recordChunkProgress;
802
+ /**
803
+ * Renders+appends chunks starting at `startCursor` up to a char budget
804
+ * (enough for one screenful, roughly) *or* a wall-clock time budget,
805
+ * whichever comes first — char count alone is a poor proxy for cost on a
806
+ * document where some chunks are plain text and others are dense with
807
+ * `<img>` tags (parsing + sanitizing + inserting into an already-huge
808
+ * `elPreviewContent` gets measurably more expensive per chunk as an
809
+ * image-heavy document's accumulated DOM grows); the time budget catches
810
+ * what the char budget alone misses. Mounts extensions/code-block controls
811
+ * for exactly the chunks it appended (never re-scanning earlier ones), and
812
+ * returns the cursor to resume from plus those chunks' roots (for the
813
+ * caller to run `collectPendingImages` on). Shared by every caller that
814
+ * mounts chunk batches — the initial batch, every later sentinel-triggered
815
+ * continuation, and Copy All's drain — image hydration itself is *not*
816
+ * included here; see {@link mountChunkBatch} and
817
+ * {@link drainRemainingChunks} for the two different ways callers pace it.
818
+ */
819
+ private renderAndMountChunkBatch;
820
+ /**
821
+ * `renderAndMountChunkBatch` plus its own immediate, independent
822
+ * `hydrateImages` pass — the shape every caller except Copy All's drain
823
+ * wants: mount a batch, then hydrate whatever images it contained,
824
+ * decoupled from mounting the next batch. Scroll-paced callers (the
825
+ * initial batch, every sentinel continuation) are naturally rate-limited
826
+ * by how fast the user scrolls, so one independent hydration loop per
827
+ * batch never has a chance to pile up against another.
828
+ */
829
+ private mountChunkBatch;
830
+ /**
831
+ * Wraps one chunk's rendered HTML in a mount boundary and appends it (or,
832
+ * given `beforeNode`, splices it in ahead of that still-attached node —
833
+ * used by reconciliation to insert a replacement chunk ahead of a
834
+ * surviving mounted suffix). See the `.mdzip-chunk` CSS rules for why.
835
+ */
836
+ private appendChunkHtml;
837
+ /**
838
+ * Appends a trailing sentinel and mounts the next chunk batch once it's
839
+ * within `rootMargin` of the viewport — a larger margin than images'
840
+ * (`hydrateImages`' 600px) since keeping text ahead of scroll is cheap
841
+ * relative to keeping images ahead. Re-arms itself after each batch until
842
+ * every chunk is mounted. Falls back to mounting everything immediately
843
+ * when IntersectionObserver isn't available, matching `hydrateImages`.
844
+ */
845
+ private armChunkSentinel;
586
846
  /**
587
847
  * Reveals a collapsed image slot. Flushing layout before toggling the class
588
848
  * lets the `0fr -> 1fr` grid transition run from the collapsed state instead
@@ -601,6 +861,13 @@ export declare class MdzipWorkspaceView {
601
861
  */
602
862
  private attachImageLoadHandlers;
603
863
  private reportImageLoadFailure;
864
+ /**
865
+ * `root` scopes extension `mount()` calls to a specific chunk instead of
866
+ * the whole preview — used by chunked rendering (see
867
+ * {@link mountChunkedPreview}), where extensions run once per newly
868
+ * appended chunk rather than once over the whole document. Defaults to
869
+ * the whole preview content, matching the non-chunked path exactly.
870
+ */
604
871
  private mountPreviewExtensions;
605
872
  /**
606
873
  * Built-in preview affordances for rendered code blocks: a language-name
@@ -611,6 +878,9 @@ export declare class MdzipWorkspaceView {
611
878
  * extension) — every consumer gets this automatically, gated by policy
612
879
  * rather than opt-in wiring. Runs after `mountPreviewExtensions` so any
613
880
  * extension-provided `pre > code` blocks already exist in the DOM. See #29.
881
+ *
882
+ * `root` scopes the scan to a specific chunk instead of the whole preview
883
+ * — see {@link mountPreviewExtensions} for why.
614
884
  */
615
885
  private mountCodeBlockControls;
616
886
  /**
@@ -621,6 +891,8 @@ export declare class MdzipWorkspaceView {
621
891
  */
622
892
  private enhanceCodeBlock;
623
893
  private firePreviewRendered;
894
+ /** See `pendingPreviewScrollRestore`'s doc comment. */
895
+ private restorePendingPreviewScroll;
624
896
  private fireAssetsHydrated;
625
897
  private flushRenderedWaiters;
626
898
  /**
@@ -656,6 +928,8 @@ export declare class MdzipWorkspaceView {
656
928
  private showTooltipForElementNow;
657
929
  private showTooltip;
658
930
  private hideTooltip;
931
+ /** Briefly shows a status message (e.g. "Text copied to clipboard") near the bottom of the view, auto-hiding after a couple of seconds. */
932
+ private showCopyToast;
659
933
  private save;
660
934
  private downloadSavedBlob;
661
935
  private saveTitle;
@@ -688,6 +962,13 @@ export declare class MdzipWorkspaceView {
688
962
  private handlePaste;
689
963
  private insertImageFile;
690
964
  private insertImageBytes;
965
+ /**
966
+ * Handles a click on an existing image's edit affordance (see
967
+ * `imageEditAffordanceField`/the `click` domEventHandler in
968
+ * createCmEditor). Only reachable when `imageEditHandler` is set — the
969
+ * click handler that dispatches the affordance already gates on that.
970
+ */
971
+ private openImageEditFlow;
691
972
  private resolveImageInsertDecision;
692
973
  private openImageInsertDialog;
693
974
  private resolveImageInsertDialog;
@@ -698,8 +979,126 @@ export declare class MdzipWorkspaceView {
698
979
  private handleEditorMenuAction;
699
980
  private previewMenuItems;
700
981
  private handlePreviewMenuAction;
982
+ /**
983
+ * Shows the copy confirmation either as a brief auto-hiding toast (an
984
+ * instant copy — nothing for the user to have looked away from) or, if
985
+ * the render dialog was showing, by switching that same dialog into a
986
+ * dismissable "done" state instead of hiding it — a copy that took long
987
+ * enough to need a progress dialog shouldn't end in a 2-second toast the
988
+ * user may not be looking at. `outcome` null means there was nothing to
989
+ * copy (empty selection) — silently clean up the dialog if one was
990
+ * showing, no confirmation needed. An `error` outcome carries the actual
991
+ * failure text inline (`onFailed` is a host callback with no guaranteed
992
+ * visible surface — this component has no other way to guarantee the
993
+ * user ever sees why it failed).
994
+ */
995
+ private finishCopyNotification;
996
+ /** Switches the render dialog into its "ready to copy" state, holding `perform` until the user clicks Copy — see `copyReadyState` for why the write can't just happen automatically here. */
997
+ private armCopyReady;
998
+ /** Runs the held write from `copyReadyState` — called directly from the dialog's Copy button click, which is what makes the write's own user-activation check pass. */
999
+ private performReadyCopy;
1000
+ /**
1001
+ * Races `promise` against a timer, rejecting with `timeoutMessage` if the
1002
+ * timer wins. The async Clipboard API has no built-in timeout, and a
1003
+ * write large enough to strain a real OS clipboard (a document with
1004
+ * thousands of embedded images can build a HTML payload well over
1005
+ * 100MB) can apparently hang indefinitely on some machines rather than
1006
+ * rejecting — without this, that leaves the operation permanently
1007
+ * pending, and the progress dialog never resolves into either a done or
1008
+ * a failure state.
1009
+ */
1010
+ private withTimeout;
1011
+ /** Writes `text` to the clipboard. Returns null if there was nothing to copy (a true no-op, not a failure); otherwise a success message or the failure text (also reported via `onFailed`, but that's a host callback with no guaranteed visible surface — the caller needs the text itself to show the user). */
701
1012
  private copyPreviewSelection;
702
- private selectAllPreviewContent;
1013
+ /** Syncs just the copy-render dialog's progress bar/text from `copyRenderDialogState` — see `copyAllPreviewContent` for why this bypasses `render()`. No-op while the dialog isn't showing. */
1014
+ private updateCopyRenderDialogProgress;
1015
+ /**
1016
+ * Copies the entire rendered document as plain text (same fidelity as
1017
+ * `copyPreviewSelection` — no HTML, no `ClipboardItem`), regardless of how
1018
+ * much of it is currently mounted under progressive rendering. If
1019
+ * everything's already mounted (small doc, eager/non-progressive mount, or
1020
+ * the user already scrolled through it) this is instant —
1021
+ * `chunkedRenderState` is either null (no markdown open, or a non-default
1022
+ * renderer) or has nothing left to mount (`cursor >= records.length`) in
1023
+ * exactly that case. Otherwise it force-drains the rest first, showing a
1024
+ * cancelable progress dialog once the wait clears a short debounce so fast
1025
+ * documents never flicker it into view.
1026
+ */
1027
+ private copyAllPreviewContent;
1028
+ /**
1029
+ * Renders the *entire* current document to one HTML string, independent
1030
+ * of whatever's mounted in `elPreviewContent` — no DOM reads or writes.
1031
+ * Copy All with Images needs pristine `<img src="original/path">` markup
1032
+ * to hand to `MdzipAssetSession.rewriteHtmlEmbeddingImages`, and an
1033
+ * already-mounted, possibly-hydrated `<img>` in the live preview may have
1034
+ * had its `src` swapped for a `blob:` URL or stripped entirely pending lazy
1035
+ * load — either way the original archive path is gone. Time-budgeted and
1036
+ * yielded like `renderAndMountChunkBatch`, for the same reason: some
1037
+ * chunks cost far more to render+sanitize than others. Extension `mount()`
1038
+ * hooks are not run — they render into a live DOM (e.g. mermaid diagrams
1039
+ * turning marked-up code fences into SVG), which this never touches, so a
1040
+ * mermaid diagram will paste as its pre-render markup, not a rendered
1041
+ * diagram. Only reachable when `renderingService.supportsChunking` is
1042
+ * true; the caller checks that first.
1043
+ */
1044
+ private renderFullDocumentHtml;
1045
+ /**
1046
+ * Like `copyAllPreviewContent`, but writes a rich `text/html` clipboard
1047
+ * representation (alongside the same `text/plain` fallback) with every
1048
+ * archive image re-embedded as a self-contained `data:` URL — the format
1049
+ * an external app like Word needs, since this document's own `blob:` URLs
1050
+ * only resolve inside this tab. Two phases share one debounced, cancelable
1051
+ * dialog: render the document fresh (`renderFullDocumentHtml`), then embed
1052
+ * its images (`rewriteHtmlEmbeddingImages`). Falls back to
1053
+ * `copyAllPreviewContent`'s plain-text-only behavior when there's no
1054
+ * default renderer to re-render from (a host-supplied custom renderer,
1055
+ * the same escape hatch `progressiveTextRendering` already has) — or, if
1056
+ * the clipboard rejects the rich write for any reason (unsupported
1057
+ * browser, payload too large), falls back to a plain-text write so the
1058
+ * user still gets *something* rather than nothing.
1059
+ */
1060
+ private copyAllWithImagesPreviewContent;
1061
+ /**
1062
+ * Writes an HTML+plain-text `ClipboardItem` when the browser supports it,
1063
+ * falling back to a plain-text-only `writeText` (same as
1064
+ * `copyPreviewSelection`, which reports itself as "Plain text") when it
1065
+ * doesn't, or if the rich write itself throws (e.g. a payload too large
1066
+ * for the OS clipboard) — either way the user ends up with *something* on
1067
+ * their clipboard rather than nothing. Returns the confirmation message
1068
+ * for the caller to show (see `finishCopyNotification`), naming the
1069
+ * actual MIME type that ended up on the clipboard ("HTML" for a
1070
+ * successful rich write, "Plain text" for the fallback) so the user knows
1071
+ * what they're about to paste — `imageCount` only adds to the "HTML"
1072
+ * wording, since even a rich write with zero images is still HTML, not
1073
+ * plain text.
1074
+ */
1075
+ private writeRichClipboard;
1076
+ /**
1077
+ * Mounts every chunk from `state.cursor` onward, yielding to a fresh
1078
+ * animation frame between batches (unlike the sentinel path's rAF-time-
1079
+ * budgeted-per-batch loop, this has no viewport to wait on — it has to
1080
+ * plow through the whole rest of the document, so the explicit yield is
1081
+ * what keeps a huge draw from locking up the tab while it does). Stops
1082
+ * early if `signal` aborts, a newer render generation supersedes this one,
1083
+ * or the chunk state's own render context aborts.
1084
+ *
1085
+ * Deliberately uses `renderAndMountChunkBatch` instead of `mountChunkBatch`
1086
+ * and defers image hydration until the whole drain finishes, running it
1087
+ * once over every image the drain mounted, instead of once per batch:
1088
+ * Copy All only needs the mounted chunks' *text* (`elPreviewContent
1089
+ * .textContent`, which images never contribute to), so there's no reason
1090
+ * to wait on `hydrateImages`' async image resolution at all here — and
1091
+ * racing through batches as fast as this loop can while each one fires
1092
+ * its own independent, un-awaited `hydrateImages` background loop (as
1093
+ * `mountChunkBatch` does for the scroll-paced callers, where that's fine —
1094
+ * scrolling naturally rate-limits how many can ever be in flight at once)
1095
+ * lets dozens of those loops pile up concurrently on an image-heavy
1096
+ * document, each spending its own `CHUNK_BUDGET_MS` in the same frame —
1097
+ * measured 100-200ms frame gaps on a 15,000-image document. One combined
1098
+ * pass over every image at the end avoids the pile-up entirely, without
1099
+ * making Copy All wait on it.
1100
+ */
1101
+ private drainRemainingChunks;
703
1102
  private insertCodeBlock;
704
1103
  private clearSelectionFormatting;
705
1104
  private editorClipboard;
@@ -758,5 +1157,26 @@ export declare class MdzipWorkspaceView {
758
1157
  private validLayoutForSnapshot;
759
1158
  private syncScrollFromPreview;
760
1159
  private syncScrollToPreview;
1160
+ /**
1161
+ * Handles syncScrollToPreview's bottom edge when the preview still has
1162
+ * unmounted chunks: force-mounts the rest (same drain Copy All uses) so
1163
+ * the preview's scrollHeight reflects the whole document, then jumps to
1164
+ * its real bottom — instead of the ordinary ratio-based jump, which would
1165
+ * only land at the bottom of whatever was mounted the instant the sync
1166
+ * fired. Re-checks that the editor is still at its bottom and the document
1167
+ * hasn't changed before applying the jump, since the drain can take long
1168
+ * enough on a huge document for either to no longer hold.
1169
+ *
1170
+ * On the most extreme real documents this drain has been measured at
1171
+ * ~106s (thousands of chunks, tens of thousands of images each needing a
1172
+ * real DOM slot + IntersectionObserver registration) — long enough that a
1173
+ * silent wait looks indistinguishable from a frozen preview pane. Past a
1174
+ * short debounce (so ordinary documents never see it), a small status
1175
+ * toast shows progress, reusing the same element Copy All's confirmation
1176
+ * messages use.
1177
+ */
1178
+ private syncScrollToPreviewBottom;
1179
+ /** Syncs the catch-up toast's text from `scrollCatchUpState`, bypassing `render()` — see `updateCopyRenderDialogProgress` for why. No-op while the toast isn't showing. */
1180
+ private updateScrollCatchUpToast;
761
1181
  }
762
1182
  //# sourceMappingURL=view.d.ts.map