@lapismd/mira 0.0.5 → 0.1.1

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 (121) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/extensions/index.d.ts +12 -7
  3. package/dist/extensions/index.d.ts.map +1 -1
  4. package/dist/extensions/index.js +13 -3
  5. package/dist/extensions/inline-links.d.ts +8 -0
  6. package/dist/extensions/inline-links.d.ts.map +1 -0
  7. package/dist/extensions/inline-links.js +120 -0
  8. package/dist/extensions/markdown-editing.d.ts +32 -0
  9. package/dist/extensions/markdown-editing.d.ts.map +1 -0
  10. package/dist/extensions/markdown-editing.js +1 -0
  11. package/dist/internal/codemirror/base/index.d.ts.map +1 -1
  12. package/dist/internal/codemirror/base/index.js +3 -0
  13. package/dist/internal/codemirror/markdown/index.d.ts.map +1 -1
  14. package/dist/internal/codemirror/markdown/index.js +2 -1
  15. package/dist/internal/codemirror/rich/block-controls.d.ts.map +1 -1
  16. package/dist/internal/codemirror/rich/block-controls.js +5 -0
  17. package/dist/internal/codemirror/rich/block-gutter-accessibility.d.ts +15 -0
  18. package/dist/internal/codemirror/rich/block-gutter-accessibility.d.ts.map +1 -0
  19. package/dist/internal/codemirror/rich/block-gutter-accessibility.js +67 -0
  20. package/dist/internal/codemirror/rich/index.d.ts.map +1 -1
  21. package/dist/internal/codemirror/rich/index.js +45 -9
  22. package/dist/internal/codemirror/rich/utils/images.d.ts +6 -3
  23. package/dist/internal/codemirror/rich/utils/images.d.ts.map +1 -1
  24. package/dist/internal/codemirror/rich/utils/images.js +16 -29
  25. package/dist/internal/codemirror/rich/utils/measured-indent.d.ts.map +1 -1
  26. package/dist/internal/codemirror/rich/utils/measured-indent.js +13 -1
  27. package/dist/internal/codemirror/rich/utils/references.d.ts +15 -0
  28. package/dist/internal/codemirror/rich/utils/references.d.ts.map +1 -0
  29. package/dist/internal/codemirror/rich/utils/references.js +86 -0
  30. package/dist/internal/codemirror/rich/widgets/preview-widgets.d.ts +3 -0
  31. package/dist/internal/codemirror/rich/widgets/preview-widgets.d.ts.map +1 -1
  32. package/dist/internal/codemirror/rich/widgets/preview-widgets.js +31 -3
  33. package/dist/internal/inline-links/icons.d.ts +115 -0
  34. package/dist/internal/inline-links/icons.d.ts.map +1 -0
  35. package/dist/internal/inline-links/icons.js +148 -0
  36. package/dist/internal/inline-links/live-preview.d.ts +5 -0
  37. package/dist/internal/inline-links/live-preview.d.ts.map +1 -0
  38. package/dist/internal/inline-links/live-preview.js +135 -0
  39. package/dist/internal/inline-links/registry.d.ts +44 -0
  40. package/dist/internal/inline-links/registry.d.ts.map +1 -0
  41. package/dist/internal/inline-links/registry.js +153 -0
  42. package/dist/mira.svelte +2 -0
  43. package/dist/mira.svelte.d.ts +1 -1
  44. package/dist/mira.svelte.d.ts.map +1 -1
  45. package/dist/preview/asset-url-lease.d.ts +6 -0
  46. package/dist/preview/asset-url-lease.d.ts.map +1 -0
  47. package/dist/preview/asset-url-lease.js +28 -0
  48. package/dist/preview/components/embed.svelte +10 -5
  49. package/dist/preview/components/embed.svelte.d.ts.map +1 -1
  50. package/dist/preview/components/image.svelte +7 -2
  51. package/dist/preview/components/image.svelte.d.ts.map +1 -1
  52. package/dist/preview/components/inline-link.svelte +94 -0
  53. package/dist/preview/components/inline-link.svelte.d.ts +16 -0
  54. package/dist/preview/components/inline-link.svelte.d.ts.map +1 -0
  55. package/dist/preview/components/link-destination-tooltip.svelte +63 -0
  56. package/dist/preview/components/link-destination-tooltip.svelte.d.ts +7 -0
  57. package/dist/preview/components/link-destination-tooltip.svelte.d.ts.map +1 -0
  58. package/dist/preview/components/link-site-icon.svelte +39 -0
  59. package/dist/preview/components/link-site-icon.svelte.d.ts +10 -0
  60. package/dist/preview/components/link-site-icon.svelte.d.ts.map +1 -0
  61. package/dist/preview/components/link.svelte +19 -9
  62. package/dist/preview/components/link.svelte.d.ts.map +1 -1
  63. package/dist/preview/editable-markdown-preview/editable-markdown-surface.svelte +6 -1
  64. package/dist/preview/editable-markdown-preview/editable-markdown-surface.svelte.d.ts.map +1 -1
  65. package/dist/preview/editable-markdown-preview/editing-session.d.ts +2 -0
  66. package/dist/preview/editable-markdown-preview/editing-session.d.ts.map +1 -1
  67. package/dist/preview/editable-markdown-preview/editing-session.js +59 -6
  68. package/dist/preview/editable-markdown-preview/serialized-writer.d.ts +6 -8
  69. package/dist/preview/editable-markdown-preview/serialized-writer.d.ts.map +1 -1
  70. package/dist/preview/editable-markdown-preview/serialized-writer.js +76 -6
  71. package/dist/preview/index.d.ts +2 -0
  72. package/dist/preview/index.d.ts.map +1 -1
  73. package/dist/preview/index.js +2 -0
  74. package/dist/preview/markdown-preview.svelte +15 -3
  75. package/dist/preview/markdown-preview.svelte.d.ts.map +1 -1
  76. package/dist/preview/renderer/utils.js +0 -2
  77. package/dist/preview/styles/inline-links.css +38 -0
  78. package/dist/preview/styles/links-embeds-tags.css +2 -2
  79. package/dist/preview/styles/mermaid-highlight.css +4 -3
  80. package/dist/preview/styles/references.css +72 -0
  81. package/dist/preview/styles/source-mode.css +32 -3
  82. package/dist/preview/styles.css +2 -0
  83. package/dist/tables/editor-column.svelte +20 -7
  84. package/dist/tables/editor-column.svelte.d.ts +2 -0
  85. package/dist/tables/editor-column.svelte.d.ts.map +1 -1
  86. package/dist/tables/editor-table.svelte +4 -0
  87. package/dist/tables/editor-table.svelte.d.ts +2 -0
  88. package/dist/tables/editor-table.svelte.d.ts.map +1 -1
  89. package/dist/tables/grid-editor-column.svelte +6 -2
  90. package/dist/tables/grid-editor-column.svelte.d.ts +2 -0
  91. package/dist/tables/grid-editor-column.svelte.d.ts.map +1 -1
  92. package/dist/tables/grid-editor-table.svelte +4 -0
  93. package/dist/tables/grid-editor-table.svelte.d.ts +2 -0
  94. package/dist/tables/grid-editor-table.svelte.d.ts.map +1 -1
  95. package/dist/tables/index.d.ts +1 -0
  96. package/dist/tables/index.d.ts.map +1 -1
  97. package/dist/tables/index.js +1 -0
  98. package/dist/tables/markdown-table-data-cell.svelte +4 -0
  99. package/dist/tables/markdown-table-data-cell.svelte.d.ts +2 -0
  100. package/dist/tables/markdown-table-data-cell.svelte.d.ts.map +1 -1
  101. package/dist/tables/table-reader.svelte +101 -0
  102. package/dist/tables/table-reader.svelte.d.ts +21 -0
  103. package/dist/tables/table-reader.svelte.d.ts.map +1 -0
  104. package/dist/tables/types.d.ts +2 -0
  105. package/dist/tables/types.d.ts.map +1 -1
  106. package/dist/tables/widgets/grid-table-widget.d.ts +1 -1
  107. package/dist/tables/widgets/grid-table-widget.d.ts.map +1 -1
  108. package/dist/tables/widgets/grid-table-widget.js +5 -2
  109. package/dist/tables/widgets/lifecycle.d.ts +4 -0
  110. package/dist/tables/widgets/lifecycle.d.ts.map +1 -0
  111. package/dist/tables/widgets/lifecycle.js +13 -0
  112. package/dist/tables/widgets/pipe-table-widget.d.ts +1 -1
  113. package/dist/tables/widgets/pipe-table-widget.d.ts.map +1 -1
  114. package/dist/tables/widgets/pipe-table-widget.js +5 -2
  115. package/dist/types.d.ts +2 -0
  116. package/dist/types.d.ts.map +1 -1
  117. package/dist/ui/context-menu/context-menu-radio-group.svelte.d.ts +1 -1
  118. package/dist/ui/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +1 -1
  119. package/dist/ui/toolbar/toolbar-group.svelte.d.ts +1 -1
  120. package/inline-links-NOTICE.md +31 -0
  121. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"image.svelte.d.ts","sourceRoot":"","sources":["../../../src/preview/components/image.svelte.ts"],"names":[],"mappings":"AAaE,KAAK,KAAK,GAAG;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC/B,CAAC;AAgNJ,QAAA,MAAM,KAAK,8CAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"image.svelte.d.ts","sourceRoot":"","sources":["../../../src/preview/components/image.svelte.ts"],"names":[],"mappings":"AAcE,KAAK,KAAK,GAAG;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC/B,CAAC;AAqNJ,QAAA,MAAM,KAAK,8CAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
@@ -0,0 +1,94 @@
1
+ <script lang="ts">
2
+ import { getContext, type Snippet } from "svelte";
3
+ import {
4
+ miraAppearanceContextKey,
5
+ resolveMiraThemeAttribute,
6
+ resolveMiraColorModeAttribute,
7
+ type MiraAppearanceContext,
8
+ } from "../../internal/appearance-context";
9
+ import type { HTMLAnchorAttributes } from "svelte/elements";
10
+ import * as Tooltip from "../../ui/tooltip";
11
+ import LinkSiteIcon from "./link-site-icon.svelte";
12
+ import {
13
+ defaultMiraLinkIconRegistry,
14
+ parseInlineLinkUrl,
15
+ type MiraLinkIconRegistry,
16
+ } from "../../internal/inline-links/registry";
17
+ let {
18
+ href = "",
19
+ label,
20
+ registry = defaultMiraLinkIconRegistry,
21
+ children,
22
+ title,
23
+ class: className = "",
24
+ target = "_blank",
25
+ rel,
26
+ ref = $bindable(null),
27
+ tagName: _tagName,
28
+ sourcePath: _sourcePath,
29
+ dialog: _dialog,
30
+ ...rest
31
+ }: Omit<HTMLAnchorAttributes, "children"> & {
32
+ registry?: MiraLinkIconRegistry;
33
+ label?: string;
34
+ children?: Snippet;
35
+ ref?: HTMLAnchorElement | null;
36
+ tagName?: string;
37
+ sourcePath?: string;
38
+ dialog?: boolean;
39
+ } = $props();
40
+ const safeHref = $derived(parseInlineLinkUrl(href ?? "")?.href);
41
+ const appearance = getContext<MiraAppearanceContext | undefined>(
42
+ miraAppearanceContextKey,
43
+ );
44
+ const appearanceTheme = $derived(
45
+ resolveMiraThemeAttribute(appearance?.theme, ref),
46
+ );
47
+ const appearanceMode = $derived(
48
+ resolveMiraColorModeAttribute(appearance?.colorMode, ref),
49
+ );
50
+ const showDestination = $derived(
51
+ Boolean(safeHref && (title || children || (label && label !== href))),
52
+ );
53
+ const safeRel = $derived(
54
+ target === "_blank"
55
+ ? [
56
+ ...new Set(`${rel ?? ""} noopener noreferrer`.trim().split(/\s+/u)),
57
+ ].join(" ")
58
+ : rel,
59
+ );
60
+ </script>
61
+
62
+ <Tooltip.Provider delayDuration={200}>
63
+ <Tooltip.Root disabled={!showDestination}>
64
+ <Tooltip.Trigger>
65
+ {#snippet child({ props })}
66
+ <a
67
+ {...props}
68
+ {...rest}
69
+ bind:this={ref}
70
+ class={`mira-inline-link ${className}`.trim()}
71
+ href={safeHref}
72
+ {title}
73
+ {target}
74
+ rel={safeRel}
75
+ >
76
+ {#if safeHref}<LinkSiteIcon href={safeHref} {registry} />{/if}<span
77
+ >{#if children}{@render children()}{:else}{label || href}{/if}</span
78
+ >
79
+ </a>
80
+ {/snippet}
81
+ </Tooltip.Trigger>
82
+ <Tooltip.Content
83
+ role="tooltip"
84
+ side="top"
85
+ sideOffset={6}
86
+ data-mira-theme={appearanceTheme}
87
+ data-mira-color-mode={appearanceMode}
88
+ >
89
+ {#if title}<span>{title}</span><br />{/if}<span
90
+ class="mira-inline-link__destination">{safeHref}</span
91
+ >
92
+ </Tooltip.Content>
93
+ </Tooltip.Root>
94
+ </Tooltip.Provider>
@@ -0,0 +1,16 @@
1
+ import { type Snippet } from "svelte";
2
+ import type { HTMLAnchorAttributes } from "svelte/elements";
3
+ import { type MiraLinkIconRegistry } from "../../internal/inline-links/registry";
4
+ type $$ComponentProps = Omit<HTMLAnchorAttributes, "children"> & {
5
+ registry?: MiraLinkIconRegistry;
6
+ label?: string;
7
+ children?: Snippet;
8
+ ref?: HTMLAnchorElement | null;
9
+ tagName?: string;
10
+ sourcePath?: string;
11
+ dialog?: boolean;
12
+ };
13
+ declare const InlineLink: import("svelte").Component<$$ComponentProps, {}, "ref">;
14
+ type InlineLink = ReturnType<typeof InlineLink>;
15
+ export default InlineLink;
16
+ //# sourceMappingURL=inline-link.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-link.svelte.d.ts","sourceRoot":"","sources":["../../../src/preview/components/inline-link.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAOlD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAG5D,OAAO,EAGH,KAAK,oBAAoB,EAC1B,MAAM,sCAAsC,CAAC;AAE/C,KAAK,gBAAgB,GAAI,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC,GAAG;IAC/D,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,GAAG,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AA8DJ,QAAA,MAAM,UAAU,yDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,63 @@
1
+ <script lang="ts">
2
+ import * as Tooltip from "../../ui/tooltip";
3
+ let { anchor }: { anchor: HTMLAnchorElement } = $props();
4
+ const id = $props.id();
5
+ let open = $state(false);
6
+ const theme = $derived(
7
+ anchor.closest<HTMLElement>("[data-mira-theme]")?.dataset.miraTheme,
8
+ );
9
+ const colorMode = $derived(
10
+ anchor.closest<HTMLElement>("[data-mira-color-mode]")?.dataset
11
+ .miraColorMode,
12
+ );
13
+ $effect(() => {
14
+ const show = () => (open = true);
15
+ const hide = () => (open = false);
16
+ const escape = (event: KeyboardEvent) => {
17
+ if (event.key === "Escape") hide();
18
+ };
19
+ anchor.addEventListener("pointerenter", show);
20
+ anchor.addEventListener("pointerleave", hide);
21
+ anchor.addEventListener("focus", show);
22
+ anchor.addEventListener("blur", hide);
23
+ anchor.addEventListener("keydown", escape);
24
+ if (anchor.matches(":focus, :hover")) show();
25
+ return () => {
26
+ anchor.removeEventListener("pointerenter", show);
27
+ anchor.removeEventListener("pointerleave", hide);
28
+ anchor.removeEventListener("focus", show);
29
+ anchor.removeEventListener("blur", hide);
30
+ anchor.removeEventListener("keydown", escape);
31
+ };
32
+ });
33
+ $effect(() => {
34
+ if (!open) return;
35
+ const previous = anchor.getAttribute("aria-describedby");
36
+ anchor.setAttribute(
37
+ "aria-describedby",
38
+ [previous, id].filter(Boolean).join(" "),
39
+ );
40
+ return () => {
41
+ if (previous) anchor.setAttribute("aria-describedby", previous);
42
+ else anchor.removeAttribute("aria-describedby");
43
+ };
44
+ });
45
+ </script>
46
+
47
+ <Tooltip.Provider>
48
+ <Tooltip.Root bind:open>
49
+ <Tooltip.Content
50
+ {id}
51
+ role="tooltip"
52
+ customAnchor={anchor}
53
+ side="top"
54
+ sideOffset={6}
55
+ data-mira-theme={theme}
56
+ data-mira-color-mode={colorMode}
57
+ >
58
+ {#if anchor.title}<span>{anchor.title}</span><br />{/if}<span
59
+ class="mira-inline-link__destination">{anchor.href}</span
60
+ >
61
+ </Tooltip.Content>
62
+ </Tooltip.Root>
63
+ </Tooltip.Provider>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ anchor: HTMLAnchorElement;
3
+ };
4
+ declare const LinkDestinationTooltip: import("svelte").Component<$$ComponentProps, {}, "">;
5
+ type LinkDestinationTooltip = ReturnType<typeof LinkDestinationTooltip>;
6
+ export default LinkDestinationTooltip;
7
+ //# sourceMappingURL=link-destination-tooltip.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-destination-tooltip.svelte.d.ts","sourceRoot":"","sources":["../../../src/preview/components/link-destination-tooltip.svelte.ts"],"names":[],"mappings":"AAKC,KAAK,gBAAgB,GAAI;IAAE,MAAM,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAyDxD,QAAA,MAAM,sBAAsB,sDAAwC,CAAC;AACrE,KAAK,sBAAsB,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC;AACxE,eAAe,sBAAsB,CAAC"}
@@ -0,0 +1,39 @@
1
+ <script lang="ts">
2
+ import {
3
+ defaultMiraLinkIconRegistry,
4
+ type MiraLinkIconRegistry,
5
+ } from "../../internal/inline-links/registry";
6
+ let {
7
+ href,
8
+ registry = defaultMiraLinkIconRegistry,
9
+ class: className = "",
10
+ }: {
11
+ href: string;
12
+ registry?: MiraLinkIconRegistry;
13
+ class?: string;
14
+ } = $props();
15
+ const resolved = $derived(registry.resolve(href));
16
+ </script>
17
+
18
+ {#if resolved?.icon}
19
+ <svg
20
+ class={`mira-link-site-icon ${className}`.trim()}
21
+ data-link-site={resolved.id}
22
+ viewBox={resolved.icon.viewBox ?? "0 0 24 24"}
23
+ fill="currentColor"
24
+ aria-hidden="true"
25
+ focusable="false"
26
+ >
27
+ {#each resolved.icon.paths as path}
28
+ <path
29
+ d={path.d}
30
+ fill={path.fill}
31
+ stroke={path.stroke}
32
+ stroke-width={path.strokeWidth}
33
+ stroke-linecap={path.strokeLinecap}
34
+ stroke-linejoin={path.strokeLinejoin}
35
+ fill-rule={path.fillRule}
36
+ />
37
+ {/each}
38
+ </svg>
39
+ {/if}
@@ -0,0 +1,10 @@
1
+ import { type MiraLinkIconRegistry } from "../../internal/inline-links/registry";
2
+ type $$ComponentProps = {
3
+ href: string;
4
+ registry?: MiraLinkIconRegistry;
5
+ class?: string;
6
+ };
7
+ declare const LinkSiteIcon: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type LinkSiteIcon = ReturnType<typeof LinkSiteIcon>;
9
+ export default LinkSiteIcon;
10
+ //# sourceMappingURL=link-site-icon.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-site-icon.svelte.d.ts","sourceRoot":"","sources":["../../../src/preview/components/link-site-icon.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAEH,KAAK,oBAAoB,EAC1B,MAAM,sCAAsC,CAAC;AAE/C,KAAK,gBAAgB,GAAI;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAqBJ,QAAA,MAAM,YAAY,sDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -19,6 +19,7 @@
19
19
  import { useMarkdownContext } from "../renderer/context.svelte";
20
20
  import EditableMarkdownSurface from "../editable-markdown-preview/editable-markdown-surface.svelte";
21
21
  import EmbeddedMarkdownPreview from "./embedded-markdown-preview.svelte";
22
+ import { createAssetUrlLease } from "../asset-url-lease";
22
23
 
23
24
  type Props = {
24
25
  href?: string;
@@ -69,7 +70,9 @@
69
70
  const target = $derived(id || href);
70
71
  const displayText = $derived(text || label || target);
71
72
  const activeSourcePath = $derived(sourcePath || markdown.sourcePath);
72
- const parsedTarget = $derived(parseMiraFileTarget(target, activeSourcePath));
73
+ const parsedTarget = $derived(
74
+ parseMiraFileTarget(target, activeSourcePath, { decodeUri: !id }),
75
+ );
73
76
  const hasFileAdapter = $derived(Boolean(markdown.fileAdapter));
74
77
  const isExternal = $derived(isExternalHref(target));
75
78
  const resolvedHref = $derived(
@@ -126,6 +129,14 @@
126
129
  ).then(
127
130
  (file) => {
128
131
  if (!cancelled) {
132
+ if (
133
+ resolvedFile?.path === file?.path &&
134
+ resolvedFile?.name === file?.name &&
135
+ resolvedFile?.extension === file?.extension &&
136
+ resolvedFile?.kind === file?.kind
137
+ ) {
138
+ return;
139
+ }
129
140
  if (resolvedFile?.path !== file?.path) {
130
141
  previewMarkdown = null;
131
142
  previewAssetUrl = null;
@@ -174,14 +185,12 @@
174
185
  }
175
186
 
176
187
  let cancelled = false;
177
- const markdownPromise = adapter.readMarkdown
178
- ? Promise.resolve(adapter.readMarkdown(file))
179
- : Promise.resolve(null);
180
- const assetPromise = adapter.readAssetUrl
181
- ? Promise.resolve(adapter.readAssetUrl(file))
182
- : Promise.resolve(null);
188
+ const markdownPromise = Promise.resolve().then(
189
+ () => adapter.readMarkdown?.(file) ?? null,
190
+ );
191
+ const asset = createAssetUrlLease(adapter, file);
183
192
 
184
- Promise.all([markdownPromise, assetPromise]).then(
193
+ Promise.all([markdownPromise, asset.result]).then(
185
194
  ([nextMarkdown, nextAssetUrl]) => {
186
195
  if (cancelled) {
187
196
  return;
@@ -205,6 +214,7 @@
205
214
 
206
215
  return () => {
207
216
  cancelled = true;
217
+ asset.release();
208
218
  stopWatching?.();
209
219
  };
210
220
  });
@@ -324,7 +334,7 @@
324
334
  src={previewAssetUrl}
325
335
  alt={resolvedFile.name || displayText}
326
336
  />
327
- {:else if previewMarkdown}
337
+ {:else if previewMarkdown !== null}
328
338
  {#snippet renderedPreview(value: string)}
329
339
  <EmbeddedMarkdownPreview
330
340
  class="mira-link-preview__markdown"
@@ -1 +1 @@
1
- {"version":3,"file":"link.svelte.d.ts","sourceRoot":"","sources":["../../../src/preview/components/link.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAqBhD,KAAK,KAAK,GAAG;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,GAAG,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CAC1B,CAAC;AAySJ,QAAA,MAAM,IAAI,8CAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"link.svelte.d.ts","sourceRoot":"","sources":["../../../src/preview/components/link.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAsBhD,KAAK,KAAK,GAAG;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,GAAG,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CAC1B,CAAC;AAmTJ,QAAA,MAAM,IAAI,8CAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
@@ -131,9 +131,10 @@
131
131
  }
132
132
 
133
133
  let current = true;
134
+ let persist = write;
134
135
  const activeWriter = new SerializedMarkdownWriter({
135
136
  value: initialValue,
136
- write,
137
+ write: (contents) => persist(contents),
137
138
  onStateChange(state) {
138
139
  if (!current) return;
139
140
  writerState = state;
@@ -151,8 +152,12 @@
151
152
  register: adapter?.registerEditingSession
152
153
  ? (path, session) => adapter.registerEditingSession?.(path, session)
153
154
  : undefined,
155
+ bind: adapter?.bindMarkdownEditingSession
156
+ ? (path, session) => adapter.bindMarkdownEditingSession!(path, session)
157
+ : undefined,
154
158
  synchronizeProtection: tick,
155
159
  });
160
+ persist = attached.write ?? write;
156
161
 
157
162
  return () => {
158
163
  current = false;
@@ -1 +1 @@
1
- {"version":3,"file":"editable-markdown-surface.svelte.d.ts","sourceRoot":"","sources":["../../../src/preview/editable-markdown-preview/editable-markdown-surface.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AACrD,OAAO,KAAK,EACR,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,2BAA2B,EAC5B,MAAM,0BAA0B,CAAC;AAclC,KAAK,mBAAmB,GAAG,CACzB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,KACP,IAAI,CAAC;AAEV,KAAK,KAAK,GAAG;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3B,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,aAAa,EAAE,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,SAAS,CAAC,EAAE,2BAA2B,CAAC;IACxC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,mBAAmB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACrE,CAAC;AAgUJ,QAAA,MAAM,uBAAuB;iBA1CF,OAAO,CAAC,OAAO,CAAC;gBAIjB,OAAO,CAAC,OAAO,CAAC;iCAsC2B,CAAC;AACtE,KAAK,uBAAuB,GAAG,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAC1E,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"editable-markdown-surface.svelte.d.ts","sourceRoot":"","sources":["../../../src/preview/editable-markdown-preview/editable-markdown-surface.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AACrD,OAAO,KAAK,EACR,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,2BAA2B,EAC5B,MAAM,0BAA0B,CAAC;AAclC,KAAK,mBAAmB,GAAG,CACzB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,KACP,IAAI,CAAC;AAEV,KAAK,KAAK,GAAG;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3B,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,aAAa,EAAE,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,SAAS,CAAC,EAAE,2BAA2B,CAAC;IACxC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,mBAAmB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACrE,CAAC;AAqUJ,QAAA,MAAM,uBAAuB;iBA1CF,OAAO,CAAC,OAAO,CAAC;gBAIjB,OAAO,CAAC,OAAO,CAAC;iCAsC2B,CAAC;AACtE,KAAK,uBAAuB,GAAG,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAC1E,eAAe,uBAAuB,CAAC"}
@@ -5,9 +5,11 @@ export declare function attachMarkdownEditingSession(options: {
5
5
  writer: SerializedMarkdownWriter;
6
6
  sourcePath?: string;
7
7
  register?: MiraFileAdapter["registerEditingSession"];
8
+ bind?: MiraFileAdapter["bindMarkdownEditingSession"];
8
9
  synchronizeProtection?: () => Promise<void>;
9
10
  }): {
10
11
  session: MiraMarkdownEditingSession;
12
+ write?: (value: string) => void | Promise<void>;
11
13
  detach(): void;
12
14
  };
13
15
  //# sourceMappingURL=editing-session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"editing-session.d.ts","sourceRoot":"","sources":["../../../src/preview/editable-markdown-preview/editing-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAEpE,+FAA+F;AAC/F,wBAAgB,4BAA4B,CAAC,OAAO,EAAE;IACpD,MAAM,EAAE,wBAAwB,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,eAAe,CAAC,wBAAwB,CAAC,CAAC;IACrD,qBAAqB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C,GAAG;IAAE,OAAO,EAAE,0BAA0B,CAAC;IAAC,MAAM,IAAI,IAAI,CAAA;CAAE,CA6C1D"}
1
+ {"version":3,"file":"editing-session.d.ts","sourceRoot":"","sources":["../../../src/preview/editable-markdown-preview/editing-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAEpE,+FAA+F;AAC/F,wBAAgB,4BAA4B,CAAC,OAAO,EAAE;IACpD,MAAM,EAAE,wBAAwB,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,eAAe,CAAC,wBAAwB,CAAC,CAAC;IACrD,IAAI,CAAC,EAAE,eAAe,CAAC,4BAA4B,CAAC,CAAC;IACrD,qBAAqB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C,GAAG;IACF,OAAO,EAAE,0BAA0B,CAAC;IACpC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,IAAI,IAAI,CAAC;CAChB,CAiGA"}
@@ -3,18 +3,38 @@ export function attachMarkdownEditingSession(options) {
3
3
  const { writer } = options;
4
4
  let detached = false;
5
5
  let remove;
6
+ let stop;
7
+ let releaseError;
8
+ let write;
9
+ function releaseIfClean() {
10
+ const state = writer.state;
11
+ if (!detached || state.dirty || state.saving || state.error !== null)
12
+ return;
13
+ try {
14
+ remove?.();
15
+ remove = undefined;
16
+ releaseError = undefined;
17
+ stop?.();
18
+ stop = undefined;
19
+ }
20
+ catch (error) {
21
+ releaseError = error ?? new Error("Markdown binding release failed");
22
+ }
23
+ }
6
24
  const session = {
7
25
  get dirty() {
8
26
  const state = writer.state;
9
- return state.dirty || state.saving || state.error !== null;
27
+ return (state.dirty ||
28
+ state.saving ||
29
+ state.error !== null ||
30
+ releaseError !== undefined);
10
31
  },
11
32
  async flush() {
12
33
  if (!(await writer.flush()))
13
34
  throw writer.state.error ?? new Error("Unable to save Markdown");
14
- if (detached && remove) {
15
- remove();
16
- remove = undefined;
17
- }
35
+ releaseIfClean();
36
+ if (releaseError !== undefined)
37
+ throw releaseError;
18
38
  },
19
39
  async protect() {
20
40
  const release = await writer.protect();
@@ -27,10 +47,42 @@ export function attachMarkdownEditingSession(options) {
27
47
  throw error;
28
48
  }
29
49
  },
50
+ text: {
51
+ get state() {
52
+ return writer.state;
53
+ },
54
+ subscribe: (listener) => writer.subscribe(listener),
55
+ async protectRecovery() {
56
+ const lease = await writer.protectRecovery();
57
+ try {
58
+ await options.synchronizeProtection?.();
59
+ return lease;
60
+ }
61
+ catch (error) {
62
+ lease.release();
63
+ throw error;
64
+ }
65
+ },
66
+ },
30
67
  };
31
68
  try {
32
- if (options.sourcePath)
69
+ if (options.bind) {
70
+ if (!options.sourcePath)
71
+ throw new Error("A Markdown binding requires a stable source path");
72
+ const binding = options.bind(options.sourcePath, session);
73
+ if (!binding ||
74
+ typeof binding.write !== "function" ||
75
+ typeof binding.release !== "function") {
76
+ if (typeof binding?.release === "function")
77
+ binding.release();
78
+ throw new Error("Invalid Markdown editing binding");
79
+ }
80
+ remove = () => binding.release();
81
+ write = (value) => binding.write(value);
82
+ }
83
+ else if (options.sourcePath)
33
84
  remove = options.register?.(options.sourcePath, session);
85
+ stop = writer.subscribe(releaseIfClean);
34
86
  }
35
87
  catch (error) {
36
88
  writer.destroy();
@@ -38,6 +90,7 @@ export function attachMarkdownEditingSession(options) {
38
90
  }
39
91
  return {
40
92
  session,
93
+ write,
41
94
  detach() {
42
95
  if (detached)
43
96
  return;
@@ -1,11 +1,5 @@
1
- export type SerializedMarkdownWriterState = {
2
- readonly: boolean;
3
- dirty: boolean;
4
- saving: boolean;
5
- error: Error | null;
6
- value: string;
7
- persistedValue: string;
8
- };
1
+ import type { MiraMarkdownRecoveryLease, MiraMarkdownTextState } from "../../extensions/markdown-editing";
2
+ export type SerializedMarkdownWriterState = MiraMarkdownTextState;
9
3
  type Options = {
10
4
  value: string;
11
5
  write: (value: string) => void | Promise<void>;
@@ -22,11 +16,15 @@ export declare class SerializedMarkdownWriter {
22
16
  private persistedValue;
23
17
  private saving;
24
18
  private protections;
19
+ private recoveries;
20
+ private readonly listeners;
25
21
  private destroyed;
26
22
  private error;
27
23
  constructor(options: Options);
28
24
  get state(): SerializedMarkdownWriterState;
29
25
  update(value: string): boolean;
26
+ subscribe(listener: (state: SerializedMarkdownWriterState) => void): () => void;
27
+ protectRecovery(): Promise<MiraMarkdownRecoveryLease>;
30
28
  protect(): Promise<() => void>;
31
29
  replaceExternal(value: string): boolean;
32
30
  flush(): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"serialized-writer.d.ts","sourceRoot":"","sources":["../../../src/preview/editable-markdown-preview/serialized-writer.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,OAAO,GAAG;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,6BAA6B,KAAK,IAAI,CAAC;CAChE,CAAC;AAEF,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmB;IAEzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAE/B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAA2B;IAE1D,OAAO,CAAC,KAAK,CAA8C;IAE3D,OAAO,CAAC,YAAY,CAAiC;IAErD,OAAO,CAAC,KAAK,CAAS;IAEtB,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO,CAAC,WAAW,CAAK;IAExB,OAAO,CAAC,SAAS,CAAS;IAE1B,OAAO,CAAC,KAAK,CAAsB;gBAEvB,OAAO,EAAE,OAAO;IAS5B,IAAI,KAAK,IAAI,6BAA6B,CASzC;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IASxB,OAAO,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC;IAoBpC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAYjC,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAkB/B,OAAO,IAAI,IAAI;IASf,OAAO,CAAC,QAAQ;YAYF,WAAW;IA0BzB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,IAAI;CAGb"}
1
+ {"version":3,"file":"serialized-writer.d.ts","sourceRoot":"","sources":["../../../src/preview/editable-markdown-preview/serialized-writer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,qBAAqB,EACtB,MAAM,mCAAmC,CAAC;AAC3C,MAAM,MAAM,6BAA6B,GAAG,qBAAqB,CAAC;AAElE,KAAK,OAAO,GAAG;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,6BAA6B,KAAK,IAAI,CAAC;CAChE,CAAC;AAEF,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmB;IAEzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAE/B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAA2B;IAE1D,OAAO,CAAC,KAAK,CAA8C;IAE3D,OAAO,CAAC,YAAY,CAAiC;IAErD,OAAO,CAAC,KAAK,CAAS;IAEtB,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAEtB;IAEJ,OAAO,CAAC,SAAS,CAAS;IAE1B,OAAO,CAAC,KAAK,CAAsB;gBAEvB,OAAO,EAAE,OAAO;IAS5B,IAAI,KAAK,IAAI,6BAA6B,CASzC;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAQ9B,SAAS,CACP,QAAQ,EAAE,CAAC,KAAK,EAAE,6BAA6B,KAAK,IAAI,GACvD,MAAM,IAAI;IAQP,eAAe,IAAI,OAAO,CAAC,yBAAyB,CAAC;IA2CrD,OAAO,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC;IAoBpC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAiBjC,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IA0B/B,OAAO,IAAI,IAAI;IASf,OAAO,CAAC,QAAQ;YAYF,WAAW;IA2BzB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,IAAI;CAIb"}
@@ -8,6 +8,8 @@ export class SerializedMarkdownWriter {
8
8
  persistedValue;
9
9
  saving = false;
10
10
  protections = 0;
11
+ recoveries = 0;
12
+ listeners = new Set();
11
13
  destroyed = false;
12
14
  error = null;
13
15
  constructor(options) {
@@ -32,11 +34,63 @@ export class SerializedMarkdownWriter {
32
34
  if (this.protections > 0 || this.destroyed)
33
35
  return false;
34
36
  this.value = value;
35
- this.error = null;
36
37
  this.schedule();
37
38
  this.emit();
38
39
  return true;
39
40
  }
41
+ subscribe(listener) {
42
+ this.listeners.add(listener);
43
+ listener(this.state);
44
+ return () => {
45
+ this.listeners.delete(listener);
46
+ };
47
+ }
48
+ async protectRecovery() {
49
+ this.protections++;
50
+ this.recoveries++;
51
+ let active = true;
52
+ const release = () => {
53
+ if (!active)
54
+ return;
55
+ active = false;
56
+ this.protections--;
57
+ this.recoveries--;
58
+ this.emit();
59
+ };
60
+ const assertHeld = () => {
61
+ if (!active)
62
+ throw new Error("Markdown recovery lease expired");
63
+ if (this.saving)
64
+ throw new Error("Markdown recovery must settle persistence first");
65
+ };
66
+ try {
67
+ this.clearTimer();
68
+ this.emit();
69
+ const flight = this.flushPromise;
70
+ if (flight)
71
+ await flight;
72
+ return {
73
+ acceptLocal: () => {
74
+ assertHeld();
75
+ this.persistedValue = this.value;
76
+ this.error = null;
77
+ this.emit();
78
+ },
79
+ useExternal: (value) => {
80
+ assertHeld();
81
+ this.value = value;
82
+ this.persistedValue = value;
83
+ this.error = null;
84
+ this.emit();
85
+ },
86
+ release,
87
+ };
88
+ }
89
+ catch (error) {
90
+ release();
91
+ throw error;
92
+ }
93
+ }
40
94
  async protect() {
41
95
  this.protections++;
42
96
  this.emit();
@@ -59,7 +113,10 @@ export class SerializedMarkdownWriter {
59
113
  }
60
114
  }
61
115
  replaceExternal(value) {
62
- if (this.state.dirty || this.saving) {
116
+ if (this.recoveries > 0 ||
117
+ this.state.dirty ||
118
+ this.saving ||
119
+ this.error !== null) {
63
120
  return false;
64
121
  }
65
122
  this.value = value;
@@ -70,6 +127,8 @@ export class SerializedMarkdownWriter {
70
127
  }
71
128
  async flush() {
72
129
  this.clearTimer();
130
+ if (this.recoveries > 0)
131
+ return false;
73
132
  if (this.flushPromise) {
74
133
  const saved = await this.flushPromise;
75
134
  if (!saved) {
@@ -77,7 +136,14 @@ export class SerializedMarkdownWriter {
77
136
  }
78
137
  return this.state.dirty ? this.flush() : true;
79
138
  }
80
- this.flushPromise = this.flushLatest();
139
+ // Reserve the flight before notifying subscribers, which may request recovery.
140
+ let resolve;
141
+ let reject;
142
+ this.flushPromise = new Promise((done, fail) => {
143
+ resolve = done;
144
+ reject = fail;
145
+ });
146
+ void this.flushLatest().then(resolve, reject);
81
147
  try {
82
148
  return await this.flushPromise;
83
149
  }
@@ -89,13 +155,13 @@ export class SerializedMarkdownWriter {
89
155
  this.destroyed = true;
90
156
  this.clearTimer();
91
157
  this.emit();
92
- if (this.state.dirty) {
158
+ if (this.state.dirty || this.error !== null) {
93
159
  void this.flush();
94
160
  }
95
161
  }
96
162
  schedule() {
97
163
  this.clearTimer();
98
- if (!this.state.dirty) {
164
+ if (!this.state.dirty && this.error === null) {
99
165
  return;
100
166
  }
101
167
  this.timer = setTimeout(() => {
@@ -104,7 +170,9 @@ export class SerializedMarkdownWriter {
104
170
  }, this.delay);
105
171
  }
106
172
  async flushLatest() {
107
- while (this.value !== this.persistedValue) {
173
+ while (this.value !== this.persistedValue || this.error !== null) {
174
+ if (this.recoveries > 0)
175
+ return false;
108
176
  const snapshot = this.value;
109
177
  this.saving = true;
110
178
  this.error = null;
@@ -134,5 +202,7 @@ export class SerializedMarkdownWriter {
134
202
  }
135
203
  emit() {
136
204
  this.onStateChange?.(this.state);
205
+ for (const listener of this.listeners)
206
+ listener(this.state);
137
207
  }
138
208
  }