@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,394 @@
1
+ <script lang="ts">
2
+ import type { App } from "@lapis-notes/api";
3
+ import { Button } from "@lapismd/design-core/shadcn/button";
4
+ import { ScrollArea } from "@lapismd/design-core/shadcn/scroll-area";
5
+ import { Mira } from "@lapismd/mira";
6
+ import "@lapismd/mira/styles.css";
7
+ import "@lapismd/mira/themes/obsidian.css";
8
+ import { onMount } from "svelte";
9
+ import type { DocsRepository } from "./docs-repository";
10
+ import {
11
+ DEFAULT_DOCS_PLUGIN_SETTINGS,
12
+ type DocsPluginSettings,
13
+ } from "./docs-settings";
14
+ import type { DocsSettingsSource } from "./docs-view";
15
+ import DocsFlipCard from "./docs/DocsFlipCard.svelte";
16
+ import {
17
+ REVIEW_RATINGS,
18
+ REVIEW_RATING_LABELS,
19
+ buildDocsReviewQueue,
20
+ buildDocsWorkspace,
21
+ renderDocsNoteMarkdown,
22
+ reviewDocsCard,
23
+ type DocsCard,
24
+ type DocsNote,
25
+ type DocsReviewQueueItem,
26
+ type DocsReviewRating,
27
+ } from "./model";
28
+
29
+ let {
30
+ app,
31
+ repository,
32
+ clock = () => new Date(),
33
+ settingsSource,
34
+ mode = "workspace" as "workspace" | "cards" | "review",
35
+ }: {
36
+ app: App;
37
+ repository: DocsRepository;
38
+ clock?: () => Date;
39
+ settingsSource?: DocsSettingsSource;
40
+ mode?: "workspace" | "cards" | "review";
41
+ } = $props();
42
+
43
+ let settings = $state<DocsPluginSettings>(DEFAULT_DOCS_PLUGIN_SETTINGS);
44
+ let notes = $state<DocsNote[]>([]);
45
+ let selectedId = $state<string | null>(null);
46
+ let panel = $state<"display" | "cards" | "review">("display");
47
+ let loadError = $state<string | null>(null);
48
+ let flipped = $state(false);
49
+ let reviewIndex = $state(0);
50
+
51
+ const selected = $derived(
52
+ notes.find((note) => note.id === selectedId) ?? notes[0] ?? null,
53
+ );
54
+ const coreCards = $derived(
55
+ (selected?.cards ?? []).filter(
56
+ (card) => card.type === "basic" || card.type === "cloze",
57
+ ),
58
+ );
59
+ const markdown = $derived(
60
+ selected ? renderDocsNoteMarkdown(selected) : "",
61
+ );
62
+ const workspace = $derived(buildDocsWorkspace(notes));
63
+ const reviewQueue = $derived(buildDocsReviewQueue(notes, clock()));
64
+ const activeReview = $derived(
65
+ reviewQueue[reviewIndex] ?? reviewQueue[0] ?? null,
66
+ );
67
+
68
+ async function refresh() {
69
+ try {
70
+ notes = await repository.listNotes(settings.docsRoot);
71
+ if (!selectedId && notes[0]) selectedId = notes[0].id;
72
+ loadError = null;
73
+ } catch (error) {
74
+ loadError = error instanceof Error ? error.message : String(error);
75
+ }
76
+ }
77
+
78
+ async function rate(rating: DocsReviewRating) {
79
+ if (!activeReview) return;
80
+ const note = notes.find((entry) => entry.id === activeReview.noteId);
81
+ if (!note) return;
82
+ const cardIndex = note.cards.findIndex(
83
+ (card) => card.id === activeReview.card.id,
84
+ );
85
+ if (cardIndex < 0) return;
86
+ const reviewed = reviewDocsCard(note.cards[cardIndex]!, rating, clock());
87
+ const nextCards = note.cards.slice();
88
+ nextCards[cardIndex] = reviewed;
89
+ const nextNote: DocsNote = {
90
+ ...note,
91
+ cards: nextCards,
92
+ updatedAt: clock().toISOString(),
93
+ };
94
+ await repository.writeNote(nextNote);
95
+ await refresh();
96
+ flipped = false;
97
+ reviewIndex = Math.min(reviewIndex, Math.max(0, reviewQueue.length - 1));
98
+ }
99
+
100
+ onMount(() => {
101
+ if (mode === "cards") panel = "cards";
102
+ else if (mode === "review") panel = "review";
103
+ settings = settingsSource?.get() ?? DEFAULT_DOCS_PLUGIN_SETTINGS;
104
+ const unsubscribe = settingsSource?.subscribe((next) => {
105
+ settings = next;
106
+ void refresh();
107
+ });
108
+ void refresh();
109
+ return () => unsubscribe?.();
110
+ });
111
+ </script>
112
+
113
+ <div
114
+ class="docs-workspace"
115
+ data-ui-component="docs-workspace"
116
+ data-mode={mode}
117
+ data-docs-ready={notes.length > 0 ? "true" : "false"}
118
+ data-app-owned={app ? "true" : "false"}
119
+ >
120
+ <aside class="docs-workspace__sidebar">
121
+ <header class="docs-workspace__sidebar-header">
122
+ <h2>Docs</h2>
123
+ <p>{notes.length} notes · {workspace.reviewQueue.length} due</p>
124
+ </header>
125
+ <div class="docs-workspace__scroll">
126
+ <ScrollArea>
127
+ <ul class="docs-workspace__list">
128
+ {#each notes as note (note.id)}
129
+ <li>
130
+ <button
131
+ type="button"
132
+ class="docs-workspace__note"
133
+ data-selected={selected?.id === note.id ? "true" : "false"}
134
+ onclick={() => {
135
+ selectedId = note.id;
136
+ flipped = false;
137
+ if (note.sourcePath) {
138
+ const file = app.vault.getFileByPath(note.sourcePath);
139
+ if (file) {
140
+ const leaf = app.workspace.getLeaf("tab");
141
+ void leaf.openFile(file).then(() => {
142
+ app.workspace.setActiveLeaf(leaf, { focus: true });
143
+ });
144
+ }
145
+ }
146
+ }}
147
+ >
148
+ <span class="docs-workspace__note-title">{note.title}</span>
149
+ <span class="docs-workspace__note-meta">{note.docType}</span>
150
+ </button>
151
+ </li>
152
+ {/each}
153
+ </ul>
154
+ </ScrollArea>
155
+ </div>
156
+ </aside>
157
+
158
+ <section class="docs-workspace__main">
159
+ <header class="docs-workspace__toolbar">
160
+ <div class="docs-workspace__tabs" role="tablist" aria-label="Docs panels">
161
+ <Button
162
+ variant={panel === "display" ? "default" : "outline"}
163
+ size="sm"
164
+ onclick={() => (panel = "display")}
165
+ >
166
+ Display
167
+ </Button>
168
+ <Button
169
+ variant={panel === "cards" ? "default" : "outline"}
170
+ size="sm"
171
+ onclick={() => (panel = "cards")}
172
+ >
173
+ Cards
174
+ </Button>
175
+ <Button
176
+ variant={panel === "review" ? "default" : "outline"}
177
+ size="sm"
178
+ onclick={() => (panel = "review")}
179
+ >
180
+ Review
181
+ </Button>
182
+ </div>
183
+ {#if selected}
184
+ <p class="docs-workspace__summary">{selected.summary}</p>
185
+ {/if}
186
+ </header>
187
+
188
+ {#if loadError}
189
+ <p class="docs-workspace__error" role="alert">{loadError}</p>
190
+ {:else if !selected}
191
+ <p class="docs-workspace__empty">No Docs notes under `{settings.docsRoot}`.</p>
192
+ {:else if panel === "display"}
193
+ <div class="docs-workspace__scroll docs-workspace__scroll--padded">
194
+ <ScrollArea>
195
+ <Mira
196
+ value={markdown}
197
+ mode="preview"
198
+ readonly
199
+ toolbar={false}
200
+ theme="obsidian"
201
+ colorMode="inherit"
202
+ spellcheck={false}
203
+ blockControls={false}
204
+ />
205
+ </ScrollArea>
206
+ </div>
207
+ {:else if panel === "cards"}
208
+ <div class="docs-workspace__scroll docs-workspace__scroll--padded">
209
+ <ScrollArea>
210
+ <div class="docs-workspace__cards">
211
+ {#each coreCards as card (card.id)}
212
+ <article class="docs-workspace__card">
213
+ <h3>{card.prompt}</h3>
214
+ <p>{card.answer}</p>
215
+ <p class="docs-workspace__note-meta">{card.type} · {card.status}</p>
216
+ </article>
217
+ {:else}
218
+ <p class="docs-workspace__empty">No basic or cloze cards on this note.</p>
219
+ {/each}
220
+ </div>
221
+ </ScrollArea>
222
+ </div>
223
+ {:else}
224
+ <div class="docs-workspace__review">
225
+ {#if activeReview}
226
+ {@const card: DocsCard = activeReview.card}
227
+ {@const item: DocsReviewQueueItem = activeReview}
228
+ <p class="docs-workspace__note-meta">
229
+ {item.noteTitle} · {reviewQueue.length} due
230
+ </p>
231
+ <DocsFlipCard bind:flipped front={card.prompt} back={card.answer} />
232
+ <div class="docs-workspace__ratings">
233
+ {#each REVIEW_RATINGS as rating (rating)}
234
+ <Button
235
+ variant="outline"
236
+ size="sm"
237
+ onclick={() => void rate(rating)}
238
+ >
239
+ {REVIEW_RATING_LABELS[rating]}
240
+ </Button>
241
+ {/each}
242
+ </div>
243
+ {:else}
244
+ <p class="docs-workspace__empty">No cards are due for review.</p>
245
+ {/if}
246
+ </div>
247
+ {/if}
248
+ </section>
249
+ </div>
250
+
251
+ <style>
252
+ .docs-workspace {
253
+ box-sizing: border-box;
254
+ display: grid;
255
+ grid-template-columns: minmax(14rem, 18rem) minmax(0, 1fr);
256
+ width: 100%;
257
+ height: 100%;
258
+ min-height: 0;
259
+ overflow: hidden;
260
+ background: var(--ui-docs-background);
261
+ color: var(--ui-docs-foreground);
262
+ }
263
+
264
+ .docs-workspace__sidebar,
265
+ .docs-workspace__main {
266
+ display: flex;
267
+ min-height: 0;
268
+ min-width: 0;
269
+ flex-direction: column;
270
+ }
271
+
272
+ .docs-workspace__sidebar {
273
+ border-inline-end: 1px solid var(--ui-docs-border);
274
+ background: var(--ui-docs-muted);
275
+ }
276
+
277
+ .docs-workspace__sidebar-header,
278
+ .docs-workspace__toolbar {
279
+ padding: 0.75rem 1rem;
280
+ border-block-end: 1px solid var(--ui-docs-border);
281
+ }
282
+
283
+ .docs-workspace__sidebar-header h2,
284
+ .docs-workspace__sidebar-header p,
285
+ .docs-workspace__summary,
286
+ .docs-workspace__empty,
287
+ .docs-workspace__error,
288
+ .docs-workspace__note-meta {
289
+ margin: 0;
290
+ }
291
+
292
+ .docs-workspace__sidebar-header p,
293
+ .docs-workspace__summary,
294
+ .docs-workspace__note-meta {
295
+ color: var(--ui-docs-muted-foreground);
296
+ font-size: 0.8125rem;
297
+ }
298
+
299
+ .docs-workspace__scroll {
300
+ flex: 1 1 auto;
301
+ min-height: 0;
302
+ }
303
+
304
+ .docs-workspace__scroll--padded {
305
+ padding: 1rem;
306
+ }
307
+
308
+ .docs-workspace__list {
309
+ margin: 0;
310
+ padding: 0.5rem;
311
+ list-style: none;
312
+ }
313
+
314
+ .docs-workspace__note {
315
+ display: flex;
316
+ width: 100%;
317
+ flex-direction: column;
318
+ gap: 0.15rem;
319
+ margin: 0 0 0.35rem;
320
+ padding: 0.65rem 0.75rem;
321
+ border: 1px solid transparent;
322
+ border-radius: var(--ui-docs-radius);
323
+ background: transparent;
324
+ color: inherit;
325
+ text-align: start;
326
+ cursor: pointer;
327
+ }
328
+
329
+ .docs-workspace__note[data-selected="true"] {
330
+ border-color: var(--ui-docs-border);
331
+ background: var(--ui-docs-card);
332
+ }
333
+
334
+ .docs-workspace__note-title {
335
+ font-weight: 600;
336
+ }
337
+
338
+ .docs-workspace__toolbar {
339
+ display: flex;
340
+ flex-wrap: wrap;
341
+ align-items: center;
342
+ gap: 0.75rem 1rem;
343
+ }
344
+
345
+ .docs-workspace__tabs {
346
+ display: flex;
347
+ gap: 0.5rem;
348
+ }
349
+
350
+ .docs-workspace__cards {
351
+ display: grid;
352
+ gap: 0.75rem;
353
+ }
354
+
355
+ .docs-workspace__card {
356
+ padding: 1rem;
357
+ border: 1px solid var(--ui-docs-border);
358
+ border-radius: var(--ui-docs-radius);
359
+ background: var(--ui-docs-card);
360
+ }
361
+
362
+ .docs-workspace__card h3,
363
+ .docs-workspace__card p {
364
+ margin: 0;
365
+ }
366
+
367
+ .docs-workspace__card h3 {
368
+ margin-block-end: 0.5rem;
369
+ }
370
+
371
+ .docs-workspace__review {
372
+ display: flex;
373
+ flex: 1 1 auto;
374
+ min-height: 0;
375
+ flex-direction: column;
376
+ gap: 1rem;
377
+ padding: 1rem;
378
+ }
379
+
380
+ .docs-workspace__ratings {
381
+ display: flex;
382
+ flex-wrap: wrap;
383
+ gap: 0.5rem;
384
+ }
385
+
386
+ .docs-workspace__empty,
387
+ .docs-workspace__error {
388
+ padding: 1rem;
389
+ }
390
+
391
+ .docs-workspace__error {
392
+ color: var(--ui-destructive, #b91c1c);
393
+ }
394
+ </style>
@@ -0,0 +1,15 @@
1
+ import type { App } from "@lapis-notes/api";
2
+ import "@lapismd/mira/styles.css";
3
+ import "@lapismd/mira/themes/obsidian.css";
4
+ import type { DocsRepository } from "./docs-repository";
5
+ import type { DocsSettingsSource } from "./docs-view";
6
+ type $$ComponentProps = {
7
+ app: App;
8
+ repository: DocsRepository;
9
+ clock?: () => Date;
10
+ settingsSource?: DocsSettingsSource;
11
+ mode?: "workspace" | "cards" | "review";
12
+ };
13
+ declare const DocsWorkspace: import("svelte").Component<$$ComponentProps, {}, "">;
14
+ type DocsWorkspace = ReturnType<typeof DocsWorkspace>;
15
+ export default DocsWorkspace;
@@ -0,0 +1,12 @@
1
+ export { DOCS_DOCUMENT_VIEW_TYPE, DOCS_EXTENSIONS, DOCS_FILE_GLOB, DOCS_FILENAME_PATTERNS, docsDocumentExtension, isDocsDocumentPath, } from "./docs-path";
2
+ export { DocsDocumentView } from "./docs-document-view";
3
+ export { DocsPlugin, DOCS_PLUGIN_ID, default } from "./docs-plugin";
4
+ export { DOCS_VIEW_TYPE, DocsView, type DocsSettingsSource, } from "./docs-view";
5
+ export { DOCS_CARDS_VIEW_TYPE, DocsCardsView, } from "./docs-cards-view";
6
+ export { DOCS_REVIEW_VIEW_TYPE, DocsReviewView, } from "./docs-review-view";
7
+ export { DEFAULT_DOCS_PLUGIN_SETTINGS, DOCS_PLUGIN_DATA_VERSION, cloneDocsPluginSettings, mergeDocsPluginSettings, migrateDocsPluginData, normalizeDocsRoot, patchDocsPluginSettings, type DocsPluginDataV1, type DocsPluginSettings, type DocsPluginSettingsPatch, } from "./docs-settings";
8
+ export { DocsRepository, createVaultDocsFileStore, type DocsFileStore, } from "./docs-repository";
9
+ export { DOCS_NOTE_PROJECTION_FIELDS, DOCS_NOTE_PROJECTION_ID, DOCS_NOTE_PROJECTION_VERSION, createDocsProjectionRegistration, } from "./docs-index";
10
+ export { default as DocsWorkspace } from "./docs-workspace.svelte";
11
+ export { default as DocsFlipCard } from "./docs/DocsFlipCard.svelte";
12
+ export * from "./model";
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ export { DOCS_DOCUMENT_VIEW_TYPE, DOCS_EXTENSIONS, DOCS_FILE_GLOB, DOCS_FILENAME_PATTERNS, docsDocumentExtension, isDocsDocumentPath, } from "./docs-path";
2
+ export { DocsDocumentView } from "./docs-document-view";
3
+ export { DocsPlugin, DOCS_PLUGIN_ID, default } from "./docs-plugin";
4
+ export { DOCS_VIEW_TYPE, DocsView, } from "./docs-view";
5
+ export { DOCS_CARDS_VIEW_TYPE, DocsCardsView, } from "./docs-cards-view";
6
+ export { DOCS_REVIEW_VIEW_TYPE, DocsReviewView, } from "./docs-review-view";
7
+ export { DEFAULT_DOCS_PLUGIN_SETTINGS, DOCS_PLUGIN_DATA_VERSION, cloneDocsPluginSettings, mergeDocsPluginSettings, migrateDocsPluginData, normalizeDocsRoot, patchDocsPluginSettings, } from "./docs-settings";
8
+ export { DocsRepository, createVaultDocsFileStore, } from "./docs-repository";
9
+ export { DOCS_NOTE_PROJECTION_FIELDS, DOCS_NOTE_PROJECTION_ID, DOCS_NOTE_PROJECTION_VERSION, createDocsProjectionRegistration, } from "./docs-index";
10
+ export { default as DocsWorkspace } from "./docs-workspace.svelte";
11
+ export { default as DocsFlipCard } from "./docs/DocsFlipCard.svelte";
12
+ export * from "./model";
@@ -0,0 +1,6 @@
1
+ import type { DocsBookChapterNavigation, DocsBookNavigation, DocsNote } from "./types";
2
+ export declare function buildDocsBooks(notes: DocsNote[]): {
3
+ books: DocsBookNavigation[];
4
+ bookChaptersByNoteId: Record<string, DocsBookChapterNavigation>;
5
+ };
6
+ export declare function resolveDocsBookNavigation(books: DocsBookNavigation[], chapterNavigation: DocsBookChapterNavigation | null | undefined): DocsBookNavigation | null;
@@ -0,0 +1,101 @@
1
+ function compareSourcePath(left, right) {
2
+ const sourceCompare = left.sourcePath.localeCompare(right.sourcePath);
3
+ if (sourceCompare !== 0)
4
+ return sourceCompare;
5
+ return left.id.localeCompare(right.id);
6
+ }
7
+ function chapterFromRef(noteId, index, notesById) {
8
+ const note = notesById.get(noteId);
9
+ if (!note) {
10
+ return {
11
+ index,
12
+ noteId,
13
+ title: noteId,
14
+ summary: "",
15
+ sourcePath: "",
16
+ resolved: false,
17
+ completed: false,
18
+ };
19
+ }
20
+ const completedAt = note.completedAt?.trim() || undefined;
21
+ return {
22
+ index,
23
+ noteId,
24
+ title: note.title,
25
+ summary: note.summary,
26
+ sourcePath: note.sourcePath,
27
+ resolved: true,
28
+ completed: Boolean(completedAt),
29
+ ...(completedAt ? { completedAt } : {}),
30
+ };
31
+ }
32
+ function bookProgress(chapters) {
33
+ const resolvedChapters = chapters.filter((chapter) => chapter.resolved);
34
+ const completedCount = resolvedChapters.filter((chapter) => chapter.completed).length;
35
+ const resolvedCount = resolvedChapters.length;
36
+ const progressPercent = resolvedCount === 0 ? 0 : Math.round((completedCount / resolvedCount) * 100);
37
+ return { completedCount, resolvedCount, progressPercent };
38
+ }
39
+ function navigationForChapter(book, resolvedChapters, resolvedIndex) {
40
+ return {
41
+ bookId: book.id,
42
+ bookIndexNoteId: book.indexNoteId,
43
+ bookTitle: book.title,
44
+ progressPercent: book.progressPercent,
45
+ completedCount: book.completedCount,
46
+ resolvedCount: book.resolvedCount,
47
+ chapter: resolvedChapters[resolvedIndex],
48
+ previousChapter: resolvedChapters[resolvedIndex - 1],
49
+ nextChapter: resolvedChapters[resolvedIndex + 1],
50
+ };
51
+ }
52
+ export function buildDocsBooks(notes) {
53
+ const notesById = new Map(notes.map((note) => [note.id, note]));
54
+ const indexNotes = notes
55
+ .filter((note) => note.book?.chapters.length)
56
+ .sort(compareSourcePath);
57
+ const books = indexNotes.map((note) => {
58
+ const chapters = note.book?.chapters.map((chapter, index) => chapterFromRef(chapter.noteId, index, notesById)) ?? [];
59
+ return {
60
+ id: note.id,
61
+ indexNoteId: note.id,
62
+ title: note.title,
63
+ summary: note.summary,
64
+ sourcePath: note.sourcePath,
65
+ chapters,
66
+ ...bookProgress(chapters),
67
+ };
68
+ });
69
+ const booksById = new Map(books.map((book) => [book.id, book]));
70
+ const bookChaptersByNoteId = {};
71
+ // Explicit bookId wins when the note appears in that book's TOC.
72
+ for (const note of notes) {
73
+ const bookId = note.bookId?.trim();
74
+ if (!bookId)
75
+ continue;
76
+ const book = booksById.get(bookId);
77
+ if (!book)
78
+ continue;
79
+ const resolvedChapters = book.chapters.filter((chapter) => chapter.resolved);
80
+ const resolvedIndex = resolvedChapters.findIndex((chapter) => chapter.noteId === note.id);
81
+ if (resolvedIndex < 0)
82
+ continue;
83
+ bookChaptersByNoteId[note.id] = navigationForChapter(book, resolvedChapters, resolvedIndex);
84
+ }
85
+ // Legacy / remaining: first TOC membership in book sourcePath order.
86
+ for (const book of books) {
87
+ const resolvedChapters = book.chapters.filter((chapter) => chapter.resolved);
88
+ for (let resolvedIndex = 0; resolvedIndex < resolvedChapters.length; resolvedIndex += 1) {
89
+ const chapter = resolvedChapters[resolvedIndex];
90
+ if (bookChaptersByNoteId[chapter.noteId])
91
+ continue;
92
+ bookChaptersByNoteId[chapter.noteId] = navigationForChapter(book, resolvedChapters, resolvedIndex);
93
+ }
94
+ }
95
+ return { books, bookChaptersByNoteId };
96
+ }
97
+ export function resolveDocsBookNavigation(books, chapterNavigation) {
98
+ if (!chapterNavigation)
99
+ return null;
100
+ return books.find((book) => book.id === chapterNavigation.bookId) ?? null;
101
+ }
@@ -0,0 +1,4 @@
1
+ import type { DocsCodingLanguage } from "./types";
2
+ /** Parse-time defaults for coding-drill metadata. Practice UI is out of Core Docs scope. */
3
+ export declare const CODING_RUNTIME_VERSIONS: Record<DocsCodingLanguage, string>;
4
+ export declare function codingRuntimeVersion(language: DocsCodingLanguage): string;
@@ -0,0 +1,9 @@
1
+ /** Parse-time defaults for coding-drill metadata. Practice UI is out of Core Docs scope. */
2
+ export const CODING_RUNTIME_VERSIONS = {
3
+ python: "3.14.6",
4
+ java: "25.0.3",
5
+ kotlin: "2.4.0",
6
+ };
7
+ export function codingRuntimeVersion(language) {
8
+ return CODING_RUNTIME_VERSIONS[language];
9
+ }
@@ -0,0 +1,32 @@
1
+ import type { DocsNote, DocsRevisionBlock } from "./types";
2
+ export type ResolvedCardBlockRef = {
3
+ ref: string;
4
+ block: DocsRevisionBlock;
5
+ };
6
+ export type MissingCardBlockRef = {
7
+ ref: string;
8
+ reason: "missing";
9
+ };
10
+ export type CardBlockRefResolution = {
11
+ resolved: ResolvedCardBlockRef[];
12
+ missing: MissingCardBlockRef[];
13
+ };
14
+ export declare function resolveCardBlockRefs(note: Pick<DocsNote, "blocks">, blockRefs: string[]): CardBlockRefResolution;
15
+ export declare function cardHasBackMatter(card: Pick<import("./types").DocsCard, "blockRefs">): boolean;
16
+ export declare function docsBlockPlainText(block: DocsRevisionBlock): string;
17
+ export declare function docsBlockSnippet(block: DocsRevisionBlock, limit?: number): string;
18
+ export declare function docsBlockRefMarker(id: string): string;
19
+ export declare function docsBlockIdFromRef(ref: string): string;
20
+ export declare function docsBlockReferenceIndex(blocks: DocsRevisionBlock[]): {
21
+ references: {
22
+ id: string;
23
+ ref: string;
24
+ marker: string;
25
+ path: string;
26
+ type: string;
27
+ label: string;
28
+ excerpt: string;
29
+ duplicate: boolean;
30
+ }[];
31
+ duplicates: {};
32
+ };
@@ -0,0 +1,89 @@
1
+ const BLOCK_REF_MARKER = /^\[\^([a-z0-9][a-z0-9-]{0,79})\]$/;
2
+ const BLOCK_TYPE_LABELS = {
3
+ text: "Text",
4
+ markdown: "Markdown",
5
+ concept: "Concept",
6
+ procedure: "Procedure",
7
+ prompt: "Prompt",
8
+ code: "Code",
9
+ table: "Table",
10
+ callout: "Callout",
11
+ story: "Story",
12
+ resources: "Resources",
13
+ };
14
+ export function resolveCardBlockRefs(note, blockRefs) {
15
+ const byId = new Map(note.blocks.map((block) => [block.id, block]));
16
+ const resolved = [];
17
+ const missing = [];
18
+ const seen = new Set();
19
+ for (const ref of blockRefs) {
20
+ const trimmed = ref.trim();
21
+ if (!trimmed || seen.has(trimmed))
22
+ continue;
23
+ seen.add(trimmed);
24
+ const block = byId.get(trimmed);
25
+ if (block)
26
+ resolved.push({ ref: trimmed, block });
27
+ else
28
+ missing.push({ ref: trimmed, reason: "missing" });
29
+ }
30
+ return { resolved, missing };
31
+ }
32
+ export function cardHasBackMatter(card) {
33
+ return card.blockRefs.some((ref) => ref.trim());
34
+ }
35
+ function plain(value) {
36
+ return (value ?? "").replace(/\s+/g, " ").trim();
37
+ }
38
+ export function docsBlockPlainText(block) {
39
+ switch (block.type) {
40
+ case "text":
41
+ case "markdown":
42
+ case "callout":
43
+ return plain(block.body);
44
+ case "concept":
45
+ return plain([block.summary, ...block.keyPoints, block.details].join(" "));
46
+ case "procedure":
47
+ return plain([...block.steps, block.notes].join(" "));
48
+ case "prompt":
49
+ return plain(block.problem);
50
+ case "code":
51
+ return plain(block.caption || block.code);
52
+ case "table":
53
+ return plain(block.rows[0]?.join(", ") || block.columns.join(", "));
54
+ case "story":
55
+ return plain(block.situation || block.action || block.result);
56
+ case "resources":
57
+ return plain(block.items
58
+ .map((item) => item.title || item.notes)
59
+ .filter(Boolean)
60
+ .join(" · "));
61
+ }
62
+ }
63
+ export function docsBlockSnippet(block, limit = 140) {
64
+ const text = docsBlockPlainText(block);
65
+ if (text.length <= limit)
66
+ return text;
67
+ return `${text.slice(0, limit - 1).trimEnd()}…`;
68
+ }
69
+ export function docsBlockRefMarker(id) {
70
+ return `[^${id}]`;
71
+ }
72
+ export function docsBlockIdFromRef(ref) {
73
+ return BLOCK_REF_MARKER.exec(ref.trim())?.[1] ?? ref.trim();
74
+ }
75
+ export function docsBlockReferenceIndex(blocks) {
76
+ return {
77
+ references: blocks.map((block) => ({
78
+ id: block.id,
79
+ ref: docsBlockRefMarker(block.id),
80
+ marker: block.id,
81
+ path: block.id,
82
+ type: BLOCK_TYPE_LABELS[block.type],
83
+ label: block.title?.trim() || BLOCK_TYPE_LABELS[block.type],
84
+ excerpt: docsBlockSnippet(block),
85
+ duplicate: false,
86
+ })),
87
+ duplicates: {},
88
+ };
89
+ }