@lapis-notes/lapis-plugin-docs 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/README.md +188 -2
  2. package/dist/docs/DocsBlockMarkdownEditor.svelte +37 -0
  3. package/dist/docs/DocsBlockMarkdownEditor.svelte.d.ts +13 -0
  4. package/dist/docs/DocsBlockRefPicker.svelte +75 -0
  5. package/dist/docs/DocsBlockRefPicker.svelte.d.ts +11 -0
  6. package/dist/docs/DocsBookEditor.svelte +141 -0
  7. package/dist/docs/DocsBookEditor.svelte.d.ts +10 -0
  8. package/dist/docs/DocsBookNavigation.svelte +838 -0
  9. package/dist/docs/DocsBookNavigation.svelte.d.ts +11 -0
  10. package/dist/docs/DocsBooksColumn.svelte +82 -0
  11. package/dist/docs/DocsBooksColumn.svelte.d.ts +10 -0
  12. package/dist/docs/DocsCardBackMatter.svelte +32 -0
  13. package/dist/docs/DocsCardBackMatter.svelte.d.ts +10 -0
  14. package/dist/docs/DocsCardBlockExtra.svelte +43 -0
  15. package/dist/docs/DocsCardBlockExtra.svelte.d.ts +9 -0
  16. package/dist/docs/DocsCardPreviewFlip.svelte +143 -0
  17. package/dist/docs/DocsCardPreviewFlip.svelte.d.ts +17 -0
  18. package/dist/docs/DocsCardPreviewStack.svelte +135 -0
  19. package/dist/docs/DocsCardPreviewStack.svelte.d.ts +13 -0
  20. package/dist/docs/DocsCardPrompt.svelte +33 -0
  21. package/dist/docs/DocsCardPrompt.svelte.d.ts +8 -0
  22. package/dist/docs/DocsCardReviewAnswer.svelte +45 -0
  23. package/dist/docs/DocsCardReviewAnswer.svelte.d.ts +8 -0
  24. package/dist/docs/DocsCardTagChips.svelte +19 -0
  25. package/dist/docs/DocsCardTagChips.svelte.d.ts +7 -0
  26. package/dist/docs/DocsCardsPanel.svelte +232 -0
  27. package/dist/docs/DocsCardsPanel.svelte.d.ts +17 -0
  28. package/dist/docs/DocsCardsReview.css +1050 -0
  29. package/dist/docs/DocsChapterFooter.svelte +248 -0
  30. package/dist/docs/DocsChapterFooter.svelte.d.ts +11 -0
  31. package/dist/docs/DocsChapterScrollRing.svelte +101 -0
  32. package/dist/docs/DocsChapterScrollRing.svelte.d.ts +6 -0
  33. package/dist/docs/DocsDisplayPanel.css +627 -0
  34. package/dist/docs/DocsDisplayPanel.svelte +78 -0
  35. package/dist/docs/DocsDisplayPanel.svelte.d.ts +16 -0
  36. package/dist/docs/DocsEditorPanel.css +325 -0
  37. package/dist/docs/DocsEditorPanel.svelte +570 -0
  38. package/dist/docs/DocsEditorPanel.svelte.d.ts +22 -0
  39. package/dist/docs/DocsFlipCard.svelte +68 -0
  40. package/dist/docs/DocsFlipCard.svelte.d.ts +8 -0
  41. package/dist/docs/DocsFolderDirectoryList.svelte +206 -0
  42. package/dist/docs/DocsFolderDirectoryList.svelte.d.ts +14 -0
  43. package/dist/docs/DocsFolderPanel.css +357 -0
  44. package/dist/docs/DocsFolderPanel.svelte +102 -0
  45. package/dist/docs/DocsFolderPanel.svelte.d.ts +17 -0
  46. package/dist/docs/DocsLinksPanel.css +61 -0
  47. package/dist/docs/DocsLinksPanel.svelte +55 -0
  48. package/dist/docs/DocsLinksPanel.svelte.d.ts +11 -0
  49. package/dist/docs/DocsMarkdownField.svelte +93 -0
  50. package/dist/docs/DocsMarkdownField.svelte.d.ts +11 -0
  51. package/dist/docs/DocsMarkdownPreview.svelte +278 -0
  52. package/dist/docs/DocsMarkdownPreview.svelte.d.ts +17 -0
  53. package/dist/docs/DocsReviewPanel.svelte +63 -0
  54. package/dist/docs/DocsReviewPanel.svelte.d.ts +11 -0
  55. package/dist/docs/DocsRichBlock.svelte +291 -0
  56. package/dist/docs/DocsRichBlock.svelte.d.ts +13 -0
  57. package/dist/docs/DocsRichDisplay.svelte +316 -0
  58. package/dist/docs/DocsRichDisplay.svelte.d.ts +14 -0
  59. package/dist/docs/DocsSlidesPreview.css +243 -0
  60. package/dist/docs/DocsSlidesPreview.svelte +206 -0
  61. package/dist/docs/DocsSlidesPreview.svelte.d.ts +10 -0
  62. package/dist/docs/DocsWorkspace.css +570 -0
  63. package/dist/docs/DocsWorkspace.svelte +831 -0
  64. package/dist/docs/DocsWorkspace.svelte.d.ts +48 -0
  65. package/dist/docs/DocsWorkspaceToolbar.svelte +211 -0
  66. package/dist/docs/DocsWorkspaceToolbar.svelte.d.ts +27 -0
  67. package/dist/docs/candidates/ChoiceMenu.svelte +114 -0
  68. package/dist/docs/candidates/ChoiceMenu.svelte.d.ts +10 -0
  69. package/dist/docs/candidates/FlipCardDeck.svelte +800 -0
  70. package/dist/docs/candidates/FlipCardDeck.svelte.d.ts +36 -0
  71. package/dist/docs/candidates/MarkdownFloatingToc.svelte +313 -0
  72. package/dist/docs/candidates/MarkdownFloatingToc.svelte.d.ts +14 -0
  73. package/dist/docs/candidates/SearchableChoicePicker.css +161 -0
  74. package/dist/docs/candidates/SearchableChoicePicker.svelte +208 -0
  75. package/dist/docs/candidates/SearchableChoicePicker.svelte.d.ts +19 -0
  76. package/dist/docs/candidates/StructuredTable.svelte +98 -0
  77. package/dist/docs/candidates/StructuredTable.svelte.d.ts +8 -0
  78. package/dist/docs/candidates/StructuredTableEditor.svelte +374 -0
  79. package/dist/docs/candidates/StructuredTableEditor.svelte.d.ts +12 -0
  80. package/dist/docs/candidates/TagEditor.svelte +31 -0
  81. package/dist/docs/candidates/TagEditor.svelte.d.ts +14 -0
  82. package/dist/docs/candidates/WorkspaceHistoryControls.svelte +112 -0
  83. package/dist/docs/candidates/WorkspaceHistoryControls.svelte.d.ts +8 -0
  84. package/dist/docs/candidates/markdown-floating-toc.css +151 -0
  85. package/dist/docs/candidates/markdown-floating-toc.d.ts +1 -0
  86. package/dist/docs/candidates/markdown-floating-toc.js +1 -0
  87. package/dist/docs/docs-anki-import.d.ts +34 -0
  88. package/dist/docs/docs-anki-import.js +516 -0
  89. package/dist/docs/docs-book-view-model.d.ts +9 -0
  90. package/dist/docs/docs-book-view-model.js +71 -0
  91. package/dist/docs/docs-card-answer-markdown.d.ts +1 -0
  92. package/dist/docs/docs-card-answer-markdown.js +63 -0
  93. package/dist/docs/docs-card-display.d.ts +10 -0
  94. package/dist/docs/docs-card-display.js +18 -0
  95. package/dist/docs/docs-display-focus.d.ts +7 -0
  96. package/dist/docs/docs-display-focus.js +42 -0
  97. package/dist/docs/docs-document-surface.d.ts +7 -0
  98. package/dist/docs/docs-document-surface.js +25 -0
  99. package/dist/docs/docs-folder-directory.d.ts +21 -0
  100. package/dist/docs/docs-folder-directory.js +56 -0
  101. package/dist/docs/docs-link-mode.d.ts +16 -0
  102. package/dist/docs/docs-link-mode.js +94 -0
  103. package/dist/docs/docs-workspace-helpers.d.ts +25 -0
  104. package/dist/docs/docs-workspace-helpers.js +195 -0
  105. package/dist/docs/docs.tokens.css +13 -0
  106. package/dist/docs/import-anki-package.d.ts +10 -0
  107. package/dist/docs/import-anki-package.js +30 -0
  108. package/dist/docs/lapis-markdown-extensions.d.ts +9 -0
  109. package/dist/docs/lapis-markdown-extensions.js +128 -0
  110. package/dist/docs/pin-reading-chrome.d.ts +4 -0
  111. package/dist/docs/pin-reading-chrome.js +32 -0
  112. package/dist/docs/sidebar/DocsFoldersSidebar.svelte +134 -0
  113. package/dist/docs/sidebar/DocsFoldersSidebar.svelte.d.ts +15 -0
  114. package/dist/docs/sidebar/DocsLinksSidebar.svelte +105 -0
  115. package/dist/docs/sidebar/DocsLinksSidebar.svelte.d.ts +11 -0
  116. package/dist/docs/sidebar/DocsSidebar.css +346 -0
  117. package/dist/docs/sidebar/DocsSidebar.svelte +430 -0
  118. package/dist/docs/sidebar/DocsSidebar.svelte.d.ts +18 -0
  119. package/dist/docs/sidebar/DocsSidebarHost.svelte +139 -0
  120. package/dist/docs/sidebar/DocsSidebarHost.svelte.d.ts +12 -0
  121. package/dist/docs/sidebar/DocsTagsSidebar.svelte +176 -0
  122. package/dist/docs/sidebar/DocsTagsSidebar.svelte.d.ts +12 -0
  123. package/dist/docs/sidebar/docs-book-note-tree.d.ts +23 -0
  124. package/dist/docs/sidebar/docs-book-note-tree.js +88 -0
  125. package/dist/docs/sidebar/docs-note-sort.d.ts +26 -0
  126. package/dist/docs/sidebar/docs-note-sort.js +54 -0
  127. package/dist/docs/sidebar/docs-sidebar-filters.d.ts +17 -0
  128. package/dist/docs/sidebar/docs-sidebar-filters.js +74 -0
  129. package/dist/docs/sidebar/docs-sidebar-ui-storage.d.ts +24 -0
  130. package/dist/docs/sidebar/docs-sidebar-ui-storage.js +87 -0
  131. package/dist/docs/sidebar/docs-source-folder-tree.d.ts +16 -0
  132. package/dist/docs/sidebar/docs-source-folder-tree.js +143 -0
  133. package/dist/docs/sidebar/docs-tag-tree.d.ts +17 -0
  134. package/dist/docs/sidebar/docs-tag-tree.js +84 -0
  135. package/dist/docs-cards-view.d.ts +18 -0
  136. package/dist/docs-cards-view.js +52 -0
  137. package/dist/docs-document-view.d.ts +24 -0
  138. package/dist/docs-document-view.js +127 -0
  139. package/dist/docs-document-workspace.svelte +215 -0
  140. package/dist/docs-document-workspace.svelte.d.ts +16 -0
  141. package/dist/docs-index.d.ts +5 -0
  142. package/dist/docs-index.js +49 -0
  143. package/dist/docs-path.d.ts +6 -0
  144. package/dist/docs-path.js +10 -0
  145. package/dist/docs-plugin.d.ts +21 -0
  146. package/dist/docs-plugin.js +140 -0
  147. package/dist/docs-repository.d.ts +15 -0
  148. package/dist/docs-repository.js +75 -0
  149. package/dist/docs-review-view.d.ts +18 -0
  150. package/dist/docs-review-view.js +52 -0
  151. package/dist/docs-settings-tab.d.ts +8 -0
  152. package/dist/docs-settings-tab.js +25 -0
  153. package/dist/docs-settings.d.ts +17 -0
  154. package/dist/docs-settings.js +38 -0
  155. package/dist/docs-view.d.ts +22 -0
  156. package/dist/docs-view.js +51 -0
  157. package/dist/docs-workspace.svelte +394 -0
  158. package/dist/docs-workspace.svelte.d.ts +15 -0
  159. package/dist/index.d.ts +12 -0
  160. package/dist/index.js +12 -0
  161. package/dist/model/book.d.ts +6 -0
  162. package/dist/model/book.js +101 -0
  163. package/dist/model/coding-runtime.d.ts +4 -0
  164. package/dist/model/coding-runtime.js +9 -0
  165. package/dist/model/docs-card-context.d.ts +32 -0
  166. package/dist/model/docs-card-context.js +89 -0
  167. package/dist/model/docs-card-preview-stack-motion.d.ts +51 -0
  168. package/dist/model/docs-card-preview-stack-motion.js +94 -0
  169. package/dist/model/docs-media-context.d.ts +9 -0
  170. package/dist/model/docs-media-context.js +18 -0
  171. package/dist/model/docs-note-list.d.ts +8 -0
  172. package/dist/model/docs-note-list.js +25 -0
  173. package/dist/model/docs-relative-media.d.ts +30 -0
  174. package/dist/model/docs-relative-media.js +116 -0
  175. package/dist/model/docs-review-constants.d.ts +4 -0
  176. package/dist/model/docs-review-constants.js +13 -0
  177. package/dist/model/fsrs.d.ts +14 -0
  178. package/dist/model/fsrs.js +153 -0
  179. package/dist/model/graph.d.ts +12 -0
  180. package/dist/model/graph.js +169 -0
  181. package/dist/model/index.d.ts +14 -0
  182. package/dist/model/index.js +14 -0
  183. package/dist/model/markdown-normalize.d.ts +11 -0
  184. package/dist/model/markdown-normalize.js +755 -0
  185. package/dist/model/markdown.d.ts +167 -0
  186. package/dist/model/markdown.js +365 -0
  187. package/dist/model/sample.d.ts +7 -0
  188. package/dist/model/sample.js +524 -0
  189. package/dist/model/types.d.ts +328 -0
  190. package/dist/model/types.js +1 -0
  191. package/dist/model/workspace.d.ts +6 -0
  192. package/dist/model/workspace.js +81 -0
  193. package/dist/storybook.css +52 -0
  194. package/dist/styles.css +1 -0
  195. package/manifest.json +11 -0
  196. package/package.json +147 -4
@@ -0,0 +1,61 @@
1
+ .docs-link-list {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 0;
5
+ padding-top: 0.45rem;
6
+ }
7
+
8
+ .docs-links-panel {
9
+ max-width: 64rem;
10
+ margin-top: 1.25rem;
11
+ }
12
+
13
+ .docs-link-card {
14
+ display: flex;
15
+ min-width: 0;
16
+ flex-direction: column;
17
+ align-items: stretch;
18
+ gap: 0.2rem;
19
+ border: 0;
20
+ border-radius: 0.35rem;
21
+ background: transparent;
22
+ color: inherit;
23
+ cursor: default;
24
+ font: inherit;
25
+ text-align: left;
26
+ padding: 0.5rem;
27
+ }
28
+
29
+ .docs-link-card:has(.docs-link-card__target:hover),
30
+ .docs-link-card:has(.docs-link-card__target:focus-visible) {
31
+ background: var(--docs-subtle);
32
+ }
33
+
34
+ .docs-link-card__target {
35
+ display: inline-flex;
36
+ width: fit-content;
37
+ min-width: 0;
38
+ border: 0;
39
+ background: transparent;
40
+ color: inherit;
41
+ cursor: pointer;
42
+ font: inherit;
43
+ text-align: left;
44
+ padding: 0;
45
+ }
46
+
47
+ .docs-link-card__target:hover,
48
+ .docs-link-card__target:focus-visible {
49
+ outline: 0;
50
+ }
51
+
52
+ .docs-link-card .application-markdown-preview {
53
+ min-width: 0;
54
+ overflow-wrap: anywhere;
55
+ font-size: 0.82rem;
56
+ }
57
+
58
+ .docs-link-card .application-markdown-preview :is(.carta-renderer, .carta-viewer) {
59
+ min-width: 0;
60
+ overflow-wrap: anywhere;
61
+ }
@@ -0,0 +1,55 @@
1
+ <script lang="ts">
2
+ import './DocsLinksPanel.css';
3
+ import AlertCircleIcon from '@lucide/svelte/icons/alert-circle';
4
+ import MarkdownPreview from "./DocsMarkdownPreview.svelte";
5
+ import type { DocsBacklink, DocsWikiLink } from '../model';
6
+
7
+ let {
8
+ ambiguousLinks,
9
+ outgoingLinks,
10
+ backlinks,
11
+ onActiveNoteChange
12
+ }: {
13
+ ambiguousLinks: DocsWikiLink[];
14
+ outgoingLinks: DocsWikiLink[];
15
+ backlinks: DocsBacklink[];
16
+ onActiveNoteChange: (noteId: string | null) => void;
17
+ } = $props();
18
+ </script>
19
+
20
+ <section class="docs-links-panel">
21
+ <div class="docs-links-panel__heading">
22
+ <h3>Links</h3>
23
+ <p>Backlinks and outgoing wikilinks.</p>
24
+ </div>
25
+ {#if ambiguousLinks.length}
26
+ <div class="docs-alert small">
27
+ <AlertCircleIcon />
28
+ <span>Some wikilinks match multiple notes. Use a stable note id.</span>
29
+ </div>
30
+ {/if}
31
+ <div class="docs-link-list">
32
+ {#each outgoingLinks as link (`${link.raw}-${link.index}`)}
33
+ <article class="docs-link-card">
34
+ <small>Out</small>
35
+ <MarkdownPreview value={link.raw} compact />
36
+ </article>
37
+ {/each}
38
+ {#each backlinks as backlink (`${backlink.sourceNoteId}-${backlink.raw}`)}
39
+ <article class="docs-link-card">
40
+ <button
41
+ type="button"
42
+ class="docs-link-card__target"
43
+ onclick={() => onActiveNoteChange(backlink.sourceNoteId)}
44
+ >
45
+ <small>Backlink from {backlink.sourceTitle}</small>
46
+ </button>
47
+ <MarkdownPreview value={backlink.excerpt} compact />
48
+ </article>
49
+ {:else}
50
+ {#if outgoingLinks.length === 0}
51
+ <p class="docs-muted">No links yet.</p>
52
+ {/if}
53
+ {/each}
54
+ </div>
55
+ </section>
@@ -0,0 +1,11 @@
1
+ import './DocsLinksPanel.css';
2
+ import type { DocsBacklink, DocsWikiLink } from '../model';
3
+ type $$ComponentProps = {
4
+ ambiguousLinks: DocsWikiLink[];
5
+ outgoingLinks: DocsWikiLink[];
6
+ backlinks: DocsBacklink[];
7
+ onActiveNoteChange: (noteId: string | null) => void;
8
+ };
9
+ declare const DocsLinksPanel: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type DocsLinksPanel = ReturnType<typeof DocsLinksPanel>;
11
+ export default DocsLinksPanel;
@@ -0,0 +1,93 @@
1
+ <script lang="ts">
2
+ import { MarkdownComposer } from "@lapismd/design-core/chat";
3
+ import { FormField } from "@lapismd/design-core/forms";
4
+
5
+ let {
6
+ value,
7
+ label,
8
+ placeholder = "",
9
+ showLabel = true,
10
+ onChange,
11
+ }: {
12
+ value: string;
13
+ label: string;
14
+ placeholder?: string;
15
+ /** False when a parent form field already shows the visible label. */
16
+ showLabel?: boolean;
17
+ onChange: (value: string) => void;
18
+ } = $props();
19
+ </script>
20
+
21
+ <div class="docs-markdown-field">
22
+ {#if showLabel}
23
+ <FormField {label} as="div">
24
+ {@render composer()}
25
+ </FormField>
26
+ {:else}
27
+ {@render composer()}
28
+ {/if}
29
+ </div>
30
+
31
+ {#snippet composer()}
32
+ <MarkdownComposer
33
+ {value}
34
+ {label}
35
+ inputLabel={label}
36
+ placeholder={placeholder || label}
37
+ presentation="document"
38
+ showSendButton={false}
39
+ showHint={false}
40
+ submitOnEnter={false}
41
+ onChange={onChange}
42
+ onSubmit={() => {}}
43
+ />
44
+ {/snippet}
45
+
46
+ <style>
47
+ .docs-markdown-field {
48
+ min-width: 0;
49
+ }
50
+
51
+ .docs-markdown-field :global(.ui-markdown-composer__editor.mira) {
52
+ height: auto;
53
+ min-height: 1.4rem;
54
+ }
55
+
56
+ .docs-markdown-field :global(.mira__body) {
57
+ flex: none;
58
+ height: auto;
59
+ min-height: 0;
60
+ }
61
+
62
+ .docs-markdown-field :global(.cm-editor),
63
+ .docs-markdown-field :global(.cm-scroller),
64
+ .docs-markdown-field :global(.cm-content) {
65
+ height: auto;
66
+ min-height: 0;
67
+ background: transparent;
68
+ }
69
+
70
+ .docs-markdown-field :global(.ui-markdown-composer[data-presentation="document"]) {
71
+ --_composer-avatar-width: 0px;
72
+ --_composer-avatar-gap: 0px;
73
+ --_composer-editor-padding: 0px;
74
+ --ui-markdown-composer-editor-padding: 0px;
75
+ --ui-markdown-composer-min-height: 1.4rem;
76
+ --ui-markdown-composer-document-min-height: 1.4rem;
77
+ }
78
+
79
+ .docs-markdown-field :global(.ui-markdown-composer[data-presentation="document"] .ui-markdown-composer__input-row) {
80
+ grid-template-columns: minmax(0, 1fr);
81
+ }
82
+
83
+ /* The leading icon is centered in an icon-sm button; shift the row by that inset. */
84
+ .docs-markdown-field :global(.ui-markdown-composer__actions) {
85
+ margin-inline-start: calc(
86
+ (var(--ui-button-icon-size, calc(var(--spacing) * 4)) - (var(--spacing) * 8)) / 2
87
+ );
88
+ }
89
+
90
+ .docs-markdown-field :global(.ui-markdown-composer__avatar) {
91
+ display: none;
92
+ }
93
+ </style>
@@ -0,0 +1,11 @@
1
+ type $$ComponentProps = {
2
+ value: string;
3
+ label: string;
4
+ placeholder?: string;
5
+ /** False when a parent form field already shows the visible label. */
6
+ showLabel?: boolean;
7
+ onChange: (value: string) => void;
8
+ };
9
+ declare const DocsMarkdownField: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type DocsMarkdownField = ReturnType<typeof DocsMarkdownField>;
11
+ export default DocsMarkdownField;
@@ -0,0 +1,278 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Docs markdown reading surface.
4
+ * Uses the host MarkdownRenderer when the app provides one, and Mira preview otherwise.
5
+ */
6
+ import { MarkdownPreview } from "@lapismd/mira/preview";
7
+ import "@lapismd/mira/styles.css";
8
+ import "@lapismd/mira/themes/obsidian.css";
9
+ import {
10
+ Component,
11
+ MarkdownRenderer,
12
+ useApplicationState,
13
+ type App,
14
+ } from "@lapis-notes/api";
15
+ import { onDestroy } from "svelte";
16
+ import {
17
+ lapisMarkdownExtensions,
18
+ providedMarkdownRenderer,
19
+ } from "./lapis-markdown-extensions";
20
+ import type { MarkdownFloatingTocMode } from "./candidates/markdown-floating-toc";
21
+ import {
22
+ DOCS_MEDIA_SENTINEL_PREFIX,
23
+ decodeDocsMediaSentinel,
24
+ resolveDocsMediaRelativePath,
25
+ } from "../model/docs-relative-media";
26
+ import { getDocsMediaResolver } from "../model/docs-media-context";
27
+
28
+ class DocsMarkdownLifecycle extends Component {}
29
+
30
+ let {
31
+ value = "",
32
+ empty = "",
33
+ compact = false,
34
+ collapsedMaxHeight = 0,
35
+ expandLabel = "Show more",
36
+ collapseLabel = "Show less",
37
+ floatingToc = "auto" as MarkdownFloatingTocMode,
38
+ sourcePath = "",
39
+ headingIdPrefix = "",
40
+ }: {
41
+ value?: string;
42
+ empty?: string;
43
+ compact?: boolean;
44
+ collapsedMaxHeight?: number;
45
+ expandLabel?: string;
46
+ collapseLabel?: string;
47
+ floatingToc?: MarkdownFloatingTocMode;
48
+ sourcePath?: string;
49
+ headingIdPrefix?: string;
50
+ } = $props();
51
+
52
+ let application: App | null = null;
53
+ try {
54
+ application = useApplicationState();
55
+ } catch {
56
+ application = null;
57
+ }
58
+
59
+ const blobUrls = new Set<string>();
60
+ let previewRoot = $state<HTMLDivElement | null>(null);
61
+ let resolveGeneration = 0;
62
+ let expanded = $state(false);
63
+
64
+ const mediaResolver = getDocsMediaResolver();
65
+ const displayValue = $derived(value.trim() ? value : empty);
66
+ const markdownExtensions = $derived(
67
+ lapisMarkdownExtensions(application, sourcePath, displayValue),
68
+ );
69
+ const useHostRenderer = $derived(
70
+ providedMarkdownRenderer(application) !== null,
71
+ );
72
+ const collapseEnabled = $derived(collapsedMaxHeight > 0 && !expanded);
73
+ let host = $state<HTMLElement | null>(null);
74
+ let renderRevision = 0;
75
+
76
+ $effect(() => {
77
+ const target = host;
78
+ const value = displayValue;
79
+ const path = sourcePath;
80
+ const app = application;
81
+ if (!useHostRenderer || !app || !target || !value) return;
82
+
83
+ const revision = ++renderRevision;
84
+ const lifecycle = new DocsMarkdownLifecycle();
85
+ lifecycle.load();
86
+ target.replaceChildren();
87
+ void MarkdownRenderer.render(app, value, target, path, lifecycle).catch(
88
+ (error) => {
89
+ if (revision !== renderRevision) return;
90
+ target.textContent =
91
+ error instanceof Error ? error.message : String(error);
92
+ },
93
+ );
94
+
95
+ return () => {
96
+ renderRevision += 1;
97
+ lifecycle.unload();
98
+ };
99
+ });
100
+
101
+ function revokeBlobUrls() {
102
+ for (const url of blobUrls) URL.revokeObjectURL(url);
103
+ blobUrls.clear();
104
+ }
105
+
106
+ function mediaPathFromImage(
107
+ img: HTMLImageElement,
108
+ notePath: string,
109
+ ): string | null {
110
+ const dataPath = img.getAttribute("data-cv-docs-media");
111
+ if (dataPath) return dataPath;
112
+
113
+ const src = img.getAttribute("src") ?? "";
114
+ if (!src) return null;
115
+ if (src.startsWith(DOCS_MEDIA_SENTINEL_PREFIX)) {
116
+ return decodeDocsMediaSentinel(src);
117
+ }
118
+ if (/^(https?:|data:|blob:)/i.test(src) || src.startsWith("/api/")) {
119
+ return null;
120
+ }
121
+ if (!notePath) return null;
122
+ return resolveDocsMediaRelativePath(src, notePath);
123
+ }
124
+
125
+ async function resolveSentinelImages(
126
+ root: HTMLElement,
127
+ generation: number,
128
+ ) {
129
+ const notePath = sourcePath.trim();
130
+ const images = root.querySelectorAll<HTMLImageElement>(
131
+ 'img[data-cv-docs-media], img[src^="/__cvstudio-docs-media__/"], img[src^="../"], img[src^="./"], img[src^="media/"]',
132
+ );
133
+ await Promise.all(
134
+ [...images].map(async (img) => {
135
+ const relativePath = mediaPathFromImage(img, notePath);
136
+ if (!relativePath) return;
137
+ try {
138
+ const resolved = await mediaResolver(relativePath);
139
+ if (generation !== resolveGeneration) return;
140
+ if (resolved.startsWith("blob:")) blobUrls.add(resolved);
141
+ img.removeAttribute("data-cv-docs-media");
142
+ img.src = resolved;
143
+ } catch {
144
+ if (generation !== resolveGeneration) return;
145
+ img.removeAttribute("src");
146
+ img.removeAttribute("data-cv-docs-media");
147
+ img.alt = img.alt || "Media unavailable";
148
+ }
149
+ }),
150
+ );
151
+ }
152
+
153
+ $effect(() => {
154
+ const root = previewRoot;
155
+ const _value = value;
156
+ const _sourcePath = sourcePath;
157
+ if (!root) return;
158
+
159
+ revokeBlobUrls();
160
+ const generation = ++resolveGeneration;
161
+
162
+ const run = () => {
163
+ if (generation !== resolveGeneration || !previewRoot) return;
164
+ void resolveSentinelImages(previewRoot, generation);
165
+ };
166
+
167
+ const frame = requestAnimationFrame(run);
168
+ const retry = setTimeout(run, 160);
169
+ const observer = new MutationObserver(run);
170
+ observer.observe(root, { childList: true, subtree: true });
171
+
172
+ return () => {
173
+ cancelAnimationFrame(frame);
174
+ clearTimeout(retry);
175
+ observer.disconnect();
176
+ };
177
+ });
178
+
179
+ onDestroy(() => {
180
+ resolveGeneration += 1;
181
+ revokeBlobUrls();
182
+ });
183
+ </script>
184
+
185
+ <div
186
+ class="docs-markdown-preview-root"
187
+ class:compact
188
+ class:collapsed={collapseEnabled}
189
+ style:--docs-preview-max-height={collapseEnabled
190
+ ? `${collapsedMaxHeight}px`
191
+ : undefined}
192
+ data-heading-prefix={headingIdPrefix || undefined}
193
+ data-floating-toc={floatingToc}
194
+ bind:this={previewRoot}
195
+ >
196
+ {#if displayValue && useHostRenderer}
197
+ <div bind:this={host} class="docs-mira-markdown-preview docs-api-markdown"></div>
198
+ {:else if displayValue}
199
+ <MarkdownPreview
200
+ value={displayValue}
201
+ {sourcePath}
202
+ embed={compact}
203
+ extensions={markdownExtensions}
204
+ class="docs-mira-markdown-preview"
205
+ />
206
+ {:else if empty}
207
+ <p class="docs-markdown-preview-empty">{empty}</p>
208
+ {/if}
209
+ {#if collapsedMaxHeight > 0}
210
+ <button
211
+ type="button"
212
+ class="docs-markdown-preview-expand"
213
+ onclick={() => (expanded = !expanded)}
214
+ >
215
+ {expanded ? collapseLabel : expandLabel}
216
+ </button>
217
+ {/if}
218
+ </div>
219
+
220
+ <style>
221
+ .docs-markdown-preview-root {
222
+ min-width: 0;
223
+ }
224
+
225
+ .docs-markdown-preview-root.collapsed {
226
+ max-height: var(--docs-preview-max-height);
227
+ overflow: hidden;
228
+ }
229
+
230
+ .docs-markdown-preview-empty {
231
+ margin: 0;
232
+ color: var(--muted-foreground, inherit);
233
+ }
234
+
235
+ .docs-markdown-preview-expand {
236
+ margin-top: 0.5rem;
237
+ }
238
+
239
+ .docs-markdown-preview-root :global(.docs-mira-markdown-preview) {
240
+ --mira-preview-padding: 0;
241
+ --mira-preview-bottom-padding: 0;
242
+ height: auto;
243
+ padding: 0;
244
+ background: transparent;
245
+ color: inherit;
246
+ text-align: start;
247
+ }
248
+
249
+ .docs-markdown-preview-root :global(.docs-mira-markdown-preview .cm-editor),
250
+ .docs-markdown-preview-root :global(.docs-mira-markdown-preview .cm-scroller),
251
+ .docs-markdown-preview-root :global(.docs-mira-markdown-preview .markdown-rendered),
252
+ .docs-markdown-preview-root :global(.docs-mira-markdown-preview .markdown-preview-view) {
253
+ background: transparent;
254
+ color: inherit;
255
+ }
256
+
257
+ .docs-markdown-preview-root :global(.docs-mira-markdown-preview .cm-sizer) {
258
+ margin-inline: 0;
259
+ max-width: none;
260
+ min-height: 0;
261
+ width: 100%;
262
+ }
263
+
264
+ .docs-markdown-preview-root :global(.docs-mira-markdown-preview .markdown-view__document) {
265
+ padding-bottom: 0;
266
+ }
267
+
268
+ .docs-markdown-preview-root :global(.markdown-rendered a),
269
+ .docs-markdown-preview-root :global(.markdown-preview-surface a),
270
+ .docs-markdown-preview-root :global(a[data-mira-internal-link]),
271
+ .docs-markdown-preview-root :global(.mira-link-preview__trigger) {
272
+ color: var(--card-color, var(--column-color, var(--kanban-accent, var(--docs-brand, #2563eb))));
273
+ cursor: pointer;
274
+ font-weight: 850;
275
+ text-decoration: underline;
276
+ text-underline-offset: 0.18rem;
277
+ }
278
+ </style>
@@ -0,0 +1,17 @@
1
+ import "@lapismd/mira/styles.css";
2
+ import "@lapismd/mira/themes/obsidian.css";
3
+ import type { MarkdownFloatingTocMode } from "./candidates/markdown-floating-toc";
4
+ type $$ComponentProps = {
5
+ value?: string;
6
+ empty?: string;
7
+ compact?: boolean;
8
+ collapsedMaxHeight?: number;
9
+ expandLabel?: string;
10
+ collapseLabel?: string;
11
+ floatingToc?: MarkdownFloatingTocMode;
12
+ sourcePath?: string;
13
+ headingIdPrefix?: string;
14
+ };
15
+ declare const DocsMarkdownPreview: import("svelte").Component<$$ComponentProps, {}, "">;
16
+ type DocsMarkdownPreview = ReturnType<typeof DocsMarkdownPreview>;
17
+ export default DocsMarkdownPreview;
@@ -0,0 +1,63 @@
1
+ <script lang="ts">
2
+ import DocsCardPreviewStack from './DocsCardPreviewStack.svelte';
3
+ import type { DocsReviewQueueItem, DocsReviewRating, DocsReviewScope, DocsRevisionBlock } from '../model';
4
+ import * as ScrollArea from "@lapismd/design-core/shadcn/scroll-area";
5
+
6
+ let {
7
+ dueCards,
8
+ reviewScope = 'note',
9
+ getNoteBlocks = () => [],
10
+ reviewAnswerOpenById = $bindable({}),
11
+ onReviewCard
12
+ }: {
13
+ dueCards: DocsReviewQueueItem[];
14
+ reviewScope?: DocsReviewScope;
15
+ getNoteBlocks?: (noteId: string) => DocsRevisionBlock[];
16
+ reviewAnswerOpenById: Record<string, boolean>;
17
+ onReviewCard: (
18
+ noteId: string,
19
+ cardId: string,
20
+ rating: DocsReviewRating
21
+ ) => void | Promise<void>;
22
+ } = $props();
23
+
24
+ const stackItems = $derived(
25
+ dueCards.map((item) => ({
26
+ key: `${item.noteId}:${item.cardId}`,
27
+ card: item.card,
28
+ noteId: item.noteId,
29
+ noteTitle: item.noteTitle,
30
+ noteSourcePath: item.noteSourcePath,
31
+ noteBlocks: getNoteBlocks(item.noteId),
32
+ dueAt: item.dueAt.slice(0, 10)
33
+ }))
34
+ );
35
+ </script>
36
+
37
+ <ScrollArea.Root class="docs-review-view">
38
+ <section class="docs-review-view__content">
39
+ {#if dueCards.length === 0}
40
+ <p class="docs-muted">
41
+ {reviewScope === 'workspace'
42
+ ? 'No cards are due.'
43
+ : 'No cards are due for this note.'}
44
+ </p>
45
+ {:else}
46
+ {#if reviewScope === 'workspace'}
47
+ <header class="docs-review-view__header">
48
+ <p class="docs-review-view__summary">
49
+ {dueCards.length} {dueCards.length === 1 ? 'card' : 'cards'} due across your docs
50
+ </p>
51
+ </header>
52
+ {/if}
53
+ <DocsCardPreviewStack
54
+ items={stackItems}
55
+ ariaLabel={reviewScope === 'workspace' ? 'All due cards' : 'Due cards'}
56
+ mode="study"
57
+ blockSwipeWhenRevealed={true}
58
+ bind:previewAnswerOpenById={reviewAnswerOpenById}
59
+ {onReviewCard}
60
+ />
61
+ {/if}
62
+ </section>
63
+ </ScrollArea.Root>
@@ -0,0 +1,11 @@
1
+ import type { DocsReviewQueueItem, DocsReviewRating, DocsReviewScope, DocsRevisionBlock } from '../model';
2
+ type $$ComponentProps = {
3
+ dueCards: DocsReviewQueueItem[];
4
+ reviewScope?: DocsReviewScope;
5
+ getNoteBlocks?: (noteId: string) => DocsRevisionBlock[];
6
+ reviewAnswerOpenById: Record<string, boolean>;
7
+ onReviewCard: (noteId: string, cardId: string, rating: DocsReviewRating) => void | Promise<void>;
8
+ };
9
+ declare const DocsReviewPanel: import("svelte").Component<$$ComponentProps, {}, "reviewAnswerOpenById">;
10
+ type DocsReviewPanel = ReturnType<typeof DocsReviewPanel>;
11
+ export default DocsReviewPanel;