@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,291 @@
1
+ <script lang="ts">
2
+ import AlertTriangleIcon from '@lucide/svelte/icons/alert-triangle';
3
+ import BookOpenIcon from '@lucide/svelte/icons/book-open';
4
+ import CheckCircle2Icon from '@lucide/svelte/icons/check-circle-2';
5
+ import Code2Icon from '@lucide/svelte/icons/code-2';
6
+ import FileTextIcon from '@lucide/svelte/icons/file-text';
7
+ import GraduationCapIcon from '@lucide/svelte/icons/graduation-cap';
8
+ import LightbulbIcon from '@lucide/svelte/icons/lightbulb';
9
+ import ListChecksIcon from '@lucide/svelte/icons/list-checks';
10
+ import MessageSquareIcon from '@lucide/svelte/icons/message-square';
11
+ import NotebookPenIcon from '@lucide/svelte/icons/notebook-pen';
12
+ import SparklesIcon from '@lucide/svelte/icons/sparkles';
13
+ import Table2Icon from '@lucide/svelte/icons/table-2';
14
+ import TrophyIcon from '@lucide/svelte/icons/trophy';
15
+ import StructuredTable from './candidates/StructuredTable.svelte';
16
+ import type { DocsCalloutTone, DocsRevisionBlock } from '../model';
17
+ import DocsMarkdownPreview from './DocsMarkdownPreview.svelte';
18
+ import { BLOCK_TYPE_LABELS } from './docs-workspace-helpers';
19
+
20
+ let {
21
+ block,
22
+ index,
23
+ sectionId,
24
+ headingIdPrefix = 'docs-heading',
25
+ extraClass = '',
26
+ dataDisplaySlideIndex = undefined,
27
+ sourcePath = ''
28
+ }: {
29
+ block: DocsRevisionBlock;
30
+ index: number;
31
+ sectionId?: string;
32
+ headingIdPrefix?: string;
33
+ extraClass?: string;
34
+ dataDisplaySlideIndex?: number;
35
+ sourcePath?: string;
36
+ } = $props();
37
+
38
+ const resolvedSectionId = $derived(sectionId ?? block.id);
39
+ const resolvedHeadingId = $derived(`${headingIdPrefix}-${block.id}`);
40
+ const markdownHeadingIdPrefix = $derived(`docs-md-${block.id}`);
41
+ const sectionClass = $derived(
42
+ [blockClass(block), extraClass].filter(Boolean).join(' ')
43
+ );
44
+ const renderedCodeMarkdown = $derived(
45
+ block.type === 'code' ? codeBlockMarkdown(block.language, block.code) : ''
46
+ );
47
+ const showHeader = $derived(!(block.type === 'markdown' && !block.title?.trim()));
48
+
49
+ function blockTitle(value: DocsRevisionBlock) {
50
+ return value.title?.trim() || BLOCK_TYPE_LABELS[value.type] || value.type;
51
+ }
52
+
53
+ function blockClass(value: DocsRevisionBlock) {
54
+ return `docs-rich-block docs-rich-block--${value.type}`;
55
+ }
56
+
57
+ function calloutLabel(tone: DocsCalloutTone) {
58
+ if (tone === 'critical') return 'Critical';
59
+ return tone[0].toUpperCase() + tone.slice(1);
60
+ }
61
+
62
+ function codeBlockMarkdown(language: string, code: string) {
63
+ const info = language.trim().split(/\s+/)[0]?.replace(/[^\w#+.-]/g, '') ?? '';
64
+ const fence = code.includes('```') ? '````' : '```';
65
+ return `${fence}${info ? info : ''}\n${code}\n${fence}`;
66
+ }
67
+ </script>
68
+
69
+ <section
70
+ class={sectionClass}
71
+ id={resolvedSectionId}
72
+ data-display-slide-index={dataDisplaySlideIndex}
73
+ >
74
+ {#if showHeader}
75
+ <div class="docs-rich-block__header">
76
+ <div class="docs-rich-block__icon">
77
+ {#if block.type === 'concept'}
78
+ <LightbulbIcon aria-hidden="true" />
79
+ {:else if block.type === 'procedure'}
80
+ <ListChecksIcon aria-hidden="true" />
81
+ {:else if block.type === 'prompt'}
82
+ <MessageSquareIcon aria-hidden="true" />
83
+ {:else if block.type === 'code'}
84
+ <Code2Icon aria-hidden="true" />
85
+ {:else if block.type === 'table'}
86
+ <Table2Icon aria-hidden="true" />
87
+ {:else if block.type === 'callout'}
88
+ <AlertTriangleIcon aria-hidden="true" />
89
+ {:else if block.type === 'story'}
90
+ <TrophyIcon aria-hidden="true" />
91
+ {:else if block.type === 'resources'}
92
+ <BookOpenIcon aria-hidden="true" />
93
+ {:else if block.type === 'markdown'}
94
+ <NotebookPenIcon aria-hidden="true" />
95
+ {:else}
96
+ <FileTextIcon aria-hidden="true" />
97
+ {/if}
98
+ </div>
99
+ <div>
100
+ <p>{String(index + 1).padStart(2, '0')} / {BLOCK_TYPE_LABELS[block.type]}</p>
101
+ <h2
102
+ id={resolvedHeadingId}
103
+ data-cv-heading="true"
104
+ data-cv-heading-level="2"
105
+ data-cv-heading-title={blockTitle(block)}
106
+ >
107
+ {blockTitle(block)}
108
+ </h2>
109
+ </div>
110
+ </div>
111
+ {/if}
112
+
113
+ {#if block.type === 'concept'}
114
+ <div class="docs-rich-flow">
115
+ {#if block.summary}
116
+ <p class="docs-rich-lede">{block.summary}</p>
117
+ {/if}
118
+ {#if block.keyPoints.length}
119
+ <ul class="docs-rich-point-list">
120
+ {#each block.keyPoints as point (point)}
121
+ <li><CheckCircle2Icon aria-hidden="true" /> {point}</li>
122
+ {/each}
123
+ </ul>
124
+ {/if}
125
+ {#if block.details}
126
+ <DocsMarkdownPreview
127
+ {sourcePath}
128
+ value={block.details}
129
+ compact
130
+ headingIdPrefix={`${markdownHeadingIdPrefix}-details`}
131
+ />
132
+ {/if}
133
+ </div>
134
+ {:else if block.type === 'procedure'}
135
+ <div class="docs-rich-steps">
136
+ {#each block.steps as step, stepIndex (step)}
137
+ <div class="docs-rich-step">
138
+ <span>{stepIndex + 1}</span>
139
+ <p>{step}</p>
140
+ </div>
141
+ {/each}
142
+ </div>
143
+ {#if block.notes}
144
+ <div class="docs-rich-note">
145
+ <SparklesIcon aria-hidden="true" />
146
+ <DocsMarkdownPreview
147
+ {sourcePath}
148
+ value={block.notes}
149
+ compact
150
+ headingIdPrefix={`${markdownHeadingIdPrefix}-notes`}
151
+ />
152
+ </div>
153
+ {/if}
154
+ {:else if block.type === 'prompt'}
155
+ <div class="docs-rich-flow">
156
+ {#if block.problem}
157
+ <DocsMarkdownPreview
158
+ {sourcePath}
159
+ value={block.problem}
160
+ compact
161
+ headingIdPrefix={`${markdownHeadingIdPrefix}-problem`}
162
+ />
163
+ {/if}
164
+ <div class="docs-rich-grid">
165
+ {#if block.requirements.length}
166
+ <div>
167
+ <h3>Requirements</h3>
168
+ <ul>{#each block.requirements as item (item)}<li>{item}</li>{/each}</ul>
169
+ </div>
170
+ {/if}
171
+ {#if block.traps.length}
172
+ <div>
173
+ <h3>Traps</h3>
174
+ <ul>{#each block.traps as item (item)}<li>{item}</li>{/each}</ul>
175
+ </div>
176
+ {/if}
177
+ {#if block.tests.length}
178
+ <div>
179
+ <h3>Good Tests</h3>
180
+ <ul>{#each block.tests as item (item)}<li>{item}</li>{/each}</ul>
181
+ </div>
182
+ {/if}
183
+ </div>
184
+ {#if block.approach}
185
+ <div class="docs-rich-panel">
186
+ <h3>Approach</h3>
187
+ <DocsMarkdownPreview
188
+ {sourcePath}
189
+ value={block.approach}
190
+ compact
191
+ headingIdPrefix={`${markdownHeadingIdPrefix}-approach`}
192
+ />
193
+ </div>
194
+ {/if}
195
+ {#if block.answer}
196
+ <div class="docs-rich-panel docs-rich-panel--answer">
197
+ <h3>Answer</h3>
198
+ <DocsMarkdownPreview
199
+ {sourcePath}
200
+ value={block.answer}
201
+ compact
202
+ headingIdPrefix={`${markdownHeadingIdPrefix}-answer`}
203
+ />
204
+ </div>
205
+ {/if}
206
+ </div>
207
+ {:else if block.type === 'code'}
208
+ {#if block.caption}
209
+ <p class="docs-rich-lede">{block.caption}</p>
210
+ {/if}
211
+ {#if block.code}
212
+ <DocsMarkdownPreview
213
+ {sourcePath}
214
+ value={renderedCodeMarkdown}
215
+ compact
216
+ floatingToc="off"
217
+ headingIdPrefix={`${markdownHeadingIdPrefix}-code`}
218
+ />
219
+ {/if}
220
+ {:else if block.type === 'table'}
221
+ <StructuredTable columns={block.columns} rows={block.rows} />
222
+ {:else if block.type === 'callout'}
223
+ <div class={`docs-rich-callout docs-rich-callout--${block.tone}`}>
224
+ <strong>{calloutLabel(block.tone)}</strong>
225
+ <DocsMarkdownPreview
226
+ {sourcePath}
227
+ value={block.body}
228
+ compact
229
+ headingIdPrefix={`${markdownHeadingIdPrefix}-body`}
230
+ />
231
+ </div>
232
+ {:else if block.type === 'story'}
233
+ <div class="docs-rich-story-grid">
234
+ {#each ['situation', 'task', 'action', 'result'] as field (field)}
235
+ {@const value = block[field as keyof typeof block] as string}
236
+ {#if value}
237
+ <div>
238
+ <h3>{field[0].toUpperCase() + field.slice(1)}</h3>
239
+ <p>{value}</p>
240
+ </div>
241
+ {/if}
242
+ {/each}
243
+ </div>
244
+ {#if block.useFor.length || block.metrics.length}
245
+ <div class="docs-rich-meta-grid">
246
+ {#if block.useFor.length}
247
+ <div><h3>Use For</h3><p>{block.useFor.join(', ')}</p></div>
248
+ {/if}
249
+ {#if block.metrics.length}
250
+ <div><h3>Metrics</h3><ul>{#each block.metrics as metric (metric)}<li>{metric}</li>{/each}</ul></div>
251
+ {/if}
252
+ </div>
253
+ {/if}
254
+ {:else if block.type === 'resources'}
255
+ <div class="docs-rich-resource-grid">
256
+ {#each block.items as item, itemIndex (itemIndex)}
257
+ <div class="docs-rich-resource">
258
+ <GraduationCapIcon aria-hidden="true" />
259
+ <div>
260
+ {#if item.url}
261
+ <a href={item.url}>{item.title || item.url}</a>
262
+ {:else}
263
+ <strong>{item.title}</strong>
264
+ {/if}
265
+ {#if item.kind || item.notes}
266
+ <p>{[item.kind, item.notes].filter(Boolean).join(' - ')}</p>
267
+ {/if}
268
+ </div>
269
+ </div>
270
+ {/each}
271
+ </div>
272
+ {:else if block.type === 'markdown'}
273
+ {#if block.body}
274
+ <DocsMarkdownPreview
275
+ {sourcePath}
276
+ value={block.body}
277
+ compact
278
+ headingIdPrefix={`${markdownHeadingIdPrefix}-body`}
279
+ />
280
+ {/if}
281
+ {:else}
282
+ {#if block.body}
283
+ <DocsMarkdownPreview
284
+ {sourcePath}
285
+ value={block.body}
286
+ compact
287
+ headingIdPrefix={`${markdownHeadingIdPrefix}-body`}
288
+ />
289
+ {/if}
290
+ {/if}
291
+ </section>
@@ -0,0 +1,13 @@
1
+ import type { DocsRevisionBlock } from '../model';
2
+ type $$ComponentProps = {
3
+ block: DocsRevisionBlock;
4
+ index: number;
5
+ sectionId?: string;
6
+ headingIdPrefix?: string;
7
+ extraClass?: string;
8
+ dataDisplaySlideIndex?: number;
9
+ sourcePath?: string;
10
+ };
11
+ declare const DocsRichBlock: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type DocsRichBlock = ReturnType<typeof DocsRichBlock>;
13
+ export default DocsRichBlock;
@@ -0,0 +1,316 @@
1
+ <script lang="ts">
2
+ import BookOpenIcon from '@lucide/svelte/icons/book-open';
3
+ import Rows3Icon from '@lucide/svelte/icons/rows-3';
4
+ import TargetIcon from '@lucide/svelte/icons/target';
5
+ import MarkdownFloatingToc from './candidates/MarkdownFloatingToc.svelte';
6
+ import { pinReadingChrome } from './pin-reading-chrome';
7
+ import type {
8
+ DocsBookChapterNavigation,
9
+ DocsBookNavigation as DocsBookNavigationModel,
10
+ DocsNote,
11
+ DocsRevisionBlock
12
+ } from '../model';
13
+ import DocsBookNavigation from './DocsBookNavigation.svelte';
14
+ import DocsChapterFooter from './DocsChapterFooter.svelte';
15
+ import DocsChapterScrollRing from './DocsChapterScrollRing.svelte';
16
+ import DocsMarkdownPreview from './DocsMarkdownPreview.svelte';
17
+ import DocsRichBlock from './DocsRichBlock.svelte';
18
+ import {
19
+ clampDisplaySlideIndex,
20
+ displayFocusSelector,
21
+ readVisibleDisplaySlideIndex
22
+ } from './docs-display-focus';
23
+ import { BLOCK_TYPE_LABELS, DOC_TYPE_LABELS } from './docs-workspace-helpers';
24
+
25
+ let {
26
+ note,
27
+ focusSlideIndex = 0,
28
+ bookNavigation = null,
29
+ chapterNavigation = null,
30
+ saving = false,
31
+ onFocusSlideIndexChange = () => {},
32
+ onToggleChapterComplete = () => {},
33
+ onOpenNote = () => {}
34
+ }: {
35
+ note: DocsNote;
36
+ focusSlideIndex?: number;
37
+ bookNavigation?: DocsBookNavigationModel | null;
38
+ chapterNavigation?: DocsBookChapterNavigation | null;
39
+ saving?: boolean;
40
+ onFocusSlideIndexChange?: (index: number) => void;
41
+ onToggleChapterComplete?: (completed: boolean) => void | Promise<void>;
42
+ onOpenNote?: (noteId: string) => void;
43
+ } = $props();
44
+
45
+ let documentElement = $state<HTMLElement | null>(null);
46
+ let ignoreObserver = false;
47
+ let lastScrolledIndex = $state<number | null>(null);
48
+ let trackedNoteId = $state<string | null>(null);
49
+ let chapterScrollPercent = $state(0);
50
+
51
+ const outline = $derived(
52
+ note.blocks
53
+ .map((block, index) => ({
54
+ id: block.id,
55
+ title: blockTitle(block),
56
+ type: BLOCK_TYPE_LABELS[block.type] ?? block.type,
57
+ index: index + 1,
58
+ include: !(block.type === 'markdown' && !block.title?.trim())
59
+ }))
60
+ .filter((item) => item.include)
61
+ );
62
+ const appendixTitle = $derived(note.blocks.length ? 'Additional Notes' : 'Notes');
63
+ const appendixSlideIndex = $derived(note.blocks.length + 1);
64
+ const activeBookNavigation = $derived(bookNavigation ?? null);
65
+ const showReadingChrome = $derived(Boolean(chapterNavigation && activeBookNavigation));
66
+
67
+ function blockTitle(block: DocsRevisionBlock) {
68
+ return block.title?.trim() || BLOCK_TYPE_LABELS[block.type] || block.type;
69
+ }
70
+
71
+ function scrollParentFor(element: HTMLElement) {
72
+ const viewport = element.closest('[data-slot="scroll-area"]')?.querySelector(
73
+ '[data-slot="scroll-area-viewport"]'
74
+ );
75
+ if (viewport instanceof HTMLElement) return viewport;
76
+
77
+ let current = element.parentElement;
78
+ while (current) {
79
+ const style = getComputedStyle(current);
80
+ if (/(auto|scroll)/.test(style.overflowY)) return current;
81
+ current = current.parentElement;
82
+ }
83
+ return null;
84
+ }
85
+
86
+ function readChapterScrollPercent(scrollRoot: Element | null) {
87
+ if (!scrollRoot || !(scrollRoot instanceof HTMLElement)) return 0;
88
+ const maxScroll = scrollRoot.scrollHeight - scrollRoot.clientHeight;
89
+ if (maxScroll <= 0) return scrollRoot.scrollHeight > scrollRoot.clientHeight ? 0 : 100;
90
+ return Math.max(0, Math.min(100, (scrollRoot.scrollTop / maxScroll) * 100));
91
+ }
92
+
93
+ function scrollToFocusIndex(index: number) {
94
+ const root = documentElement;
95
+ if (!root) return;
96
+ const scrollRoot = scrollParentFor(root);
97
+ ignoreObserver = true;
98
+ lastScrolledIndex = index;
99
+ // Aligning the hero with scrollIntoView leaves content padding scrolled away
100
+ // (~1% on long notes). Reset the viewport for the intro slide instead.
101
+ if (index === 0) {
102
+ if (scrollRoot) scrollRoot.scrollTop = 0;
103
+ } else {
104
+ const target = root.querySelector<HTMLElement>(displayFocusSelector(note, index));
105
+ target?.scrollIntoView({ block: 'start', behavior: 'auto' });
106
+ }
107
+ requestAnimationFrame(() => {
108
+ ignoreObserver = false;
109
+ chapterScrollPercent = readChapterScrollPercent(scrollRoot);
110
+ });
111
+ }
112
+
113
+ function publishVisibleIndex() {
114
+ const root = documentElement;
115
+ if (!root || ignoreObserver) return;
116
+ const scrollRoot = scrollParentFor(root);
117
+ const next = readVisibleDisplaySlideIndex(root, scrollRoot);
118
+ if (next === focusSlideIndex) return;
119
+ lastScrolledIndex = next;
120
+ onFocusSlideIndexChange(next);
121
+ }
122
+
123
+ $effect(() => {
124
+ const root = documentElement;
125
+ const noteId = note.id;
126
+ const index = clampDisplaySlideIndex(focusSlideIndex, note);
127
+ if (!root) return;
128
+
129
+ // Note switches reuse this component and the ScrollArea viewport, so always
130
+ // reset scroll when the note changes — even if focus is already slide 0.
131
+ if (trackedNoteId !== noteId) {
132
+ trackedNoteId = noteId;
133
+ lastScrolledIndex = null;
134
+ const scrollRoot = scrollParentFor(root);
135
+ if (scrollRoot) scrollRoot.scrollTop = 0;
136
+ chapterScrollPercent = 0;
137
+ if (index === 0) {
138
+ lastScrolledIndex = 0;
139
+ return;
140
+ }
141
+ }
142
+
143
+ if (lastScrolledIndex === index) return;
144
+ scrollToFocusIndex(index);
145
+ });
146
+
147
+ $effect(() => {
148
+ const root = documentElement;
149
+ if (!root) return;
150
+ const scrollRoot = scrollParentFor(root);
151
+ let allowFocusPublish = false;
152
+ let frame = 0;
153
+
154
+ const updateScrollProgress = () => {
155
+ chapterScrollPercent = readChapterScrollPercent(scrollRoot);
156
+ };
157
+
158
+ const onScroll = () => {
159
+ if (frame) cancelAnimationFrame(frame);
160
+ frame = requestAnimationFrame(() => {
161
+ frame = 0;
162
+ updateScrollProgress();
163
+ if (!allowFocusPublish) return;
164
+ publishVisibleIndex();
165
+ });
166
+ };
167
+ scrollRoot?.addEventListener('scroll', onScroll, { passive: true });
168
+ updateScrollProgress();
169
+
170
+ const resizeObserver =
171
+ scrollRoot && typeof ResizeObserver !== 'undefined'
172
+ ? new ResizeObserver(() => updateScrollProgress())
173
+ : null;
174
+ if (scrollRoot && resizeObserver) resizeObserver.observe(scrollRoot);
175
+
176
+ // Only track scroll after mount so a restored slide focus is not
177
+ // immediately overwritten by the initial layout pass.
178
+ const timer = setTimeout(() => {
179
+ allowFocusPublish = true;
180
+ updateScrollProgress();
181
+ }, 0);
182
+
183
+ return () => {
184
+ clearTimeout(timer);
185
+ if (frame) cancelAnimationFrame(frame);
186
+ resizeObserver?.disconnect();
187
+ scrollRoot?.removeEventListener('scroll', onScroll);
188
+ };
189
+ });
190
+ </script>
191
+
192
+ <article class="docs-rich-document" bind:this={documentElement}>
193
+ <div class="docs-reading-anchor" use:pinReadingChrome>
194
+ <div class="docs-reading-edge">
195
+ {#if showReadingChrome}
196
+ <DocsChapterScrollRing {chapterScrollPercent} />
197
+ {/if}
198
+ <MarkdownFloatingToc root={documentElement} />
199
+ </div>
200
+ {#if activeBookNavigation?.chapters.length}
201
+ <DocsBookNavigation
202
+ variant="floating"
203
+ book={activeBookNavigation}
204
+ activeNoteId={note.id}
205
+ {chapterNavigation}
206
+ {onOpenNote}
207
+ />
208
+ {/if}
209
+ </div>
210
+ <header class="docs-rich-hero" data-display-slide-index="0">
211
+ <div class="docs-rich-hero__eyebrow">
212
+ <BookOpenIcon aria-hidden="true" />
213
+ <span>{DOC_TYPE_LABELS[note.docType] ?? note.docType}</span>
214
+ </div>
215
+ <h1
216
+ id={`docs-heading-${note.id}`}
217
+ data-cv-heading="true"
218
+ data-cv-heading-level="1"
219
+ data-cv-heading-title={note.title}
220
+ >
221
+ {note.title}
222
+ </h1>
223
+ {#if note.summary}
224
+ <p class="docs-rich-hero__summary">{note.summary}</p>
225
+ {/if}
226
+ {#if note.learningGoal}
227
+ <div class="docs-rich-goal">
228
+ <TargetIcon aria-hidden="true" />
229
+ <div>
230
+ <span>Learning Goal</span>
231
+ <p>{note.learningGoal}</p>
232
+ </div>
233
+ </div>
234
+ {/if}
235
+ {#if note.tags.length}
236
+ <div class="docs-rich-tags" aria-label="Tags">
237
+ {#each note.tags as tag (tag)}
238
+ <span>#{tag}</span>
239
+ {/each}
240
+ </div>
241
+ {/if}
242
+ </header>
243
+
244
+ {#if bookNavigation?.indexNoteId === note.id && bookNavigation.chapters.length}
245
+ <DocsBookNavigation variant="toc" book={bookNavigation} activeNoteId={note.id} {onOpenNote} />
246
+ {/if}
247
+
248
+ {#if outline.length}
249
+ <nav class="docs-rich-outline" aria-label="Document outline">
250
+ <div>
251
+ <p>We'll Cover</p>
252
+ <span>{outline.length} sections</span>
253
+ </div>
254
+ <ol>
255
+ {#each outline as item (item.id)}
256
+ <li>
257
+ <a href={`#${item.id}`}>
258
+ <span>{item.index}</span>
259
+ <strong>{item.title}</strong>
260
+ <small>{item.type}</small>
261
+ </a>
262
+ </li>
263
+ {/each}
264
+ </ol>
265
+ </nav>
266
+ {/if}
267
+
268
+ <div class="docs-rich-blocks">
269
+ {#each note.blocks as block, index (block.id)}
270
+ <DocsRichBlock
271
+ {block}
272
+ {index}
273
+ dataDisplaySlideIndex={index + 1}
274
+ sourcePath={note.sourcePath}
275
+ />
276
+ {/each}
277
+ </div>
278
+
279
+ {#if note.body}
280
+ <section
281
+ class="docs-rich-block docs-rich-block--text docs-rich-additional"
282
+ data-display-slide-index={appendixSlideIndex}
283
+ >
284
+ <div class="docs-rich-block__header">
285
+ <div class="docs-rich-block__icon"><Rows3Icon aria-hidden="true" /></div>
286
+ <div>
287
+ <p>Appendix</p>
288
+ <h2
289
+ id={`docs-heading-${note.id}-appendix`}
290
+ data-cv-heading="true"
291
+ data-cv-heading-level="2"
292
+ data-cv-heading-title={appendixTitle}
293
+ >
294
+ {appendixTitle}
295
+ </h2>
296
+ </div>
297
+ </div>
298
+ <DocsMarkdownPreview
299
+ value={note.body}
300
+ floatingToc="off"
301
+ sourcePath={note.sourcePath}
302
+ headingIdPrefix={`docs-md-${note.id}-appendix`}
303
+ />
304
+ </section>
305
+ {/if}
306
+
307
+ {#if showReadingChrome && chapterNavigation}
308
+ <DocsChapterFooter
309
+ {note}
310
+ {chapterNavigation}
311
+ {saving}
312
+ onToggleComplete={onToggleChapterComplete}
313
+ {onOpenNote}
314
+ />
315
+ {/if}
316
+ </article>
@@ -0,0 +1,14 @@
1
+ import type { DocsBookChapterNavigation, DocsBookNavigation as DocsBookNavigationModel, DocsNote } from '../model';
2
+ type $$ComponentProps = {
3
+ note: DocsNote;
4
+ focusSlideIndex?: number;
5
+ bookNavigation?: DocsBookNavigationModel | null;
6
+ chapterNavigation?: DocsBookChapterNavigation | null;
7
+ saving?: boolean;
8
+ onFocusSlideIndexChange?: (index: number) => void;
9
+ onToggleChapterComplete?: (completed: boolean) => void | Promise<void>;
10
+ onOpenNote?: (noteId: string) => void;
11
+ };
12
+ declare const DocsRichDisplay: import("svelte").Component<$$ComponentProps, {}, "">;
13
+ type DocsRichDisplay = ReturnType<typeof DocsRichDisplay>;
14
+ export default DocsRichDisplay;