svelte-docsmith 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 (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +162 -0
  3. package/dist/clipboard.svelte.d.ts +6 -0
  4. package/dist/clipboard.svelte.js +27 -0
  5. package/dist/config.d.ts +79 -0
  6. package/dist/config.js +55 -0
  7. package/dist/content.d.ts +10 -0
  8. package/dist/content.js +4 -0
  9. package/dist/highlight.d.ts +17 -0
  10. package/dist/highlight.js +34 -0
  11. package/dist/index.d.ts +20 -0
  12. package/dist/index.js +26 -0
  13. package/dist/markdown-layout.svelte +21 -0
  14. package/dist/markdown-layout.svelte.d.ts +18 -0
  15. package/dist/preprocess.d.ts +33 -0
  16. package/dist/preprocess.js +65 -0
  17. package/dist/shadcn.d.ts +12 -0
  18. package/dist/shadcn.js +5 -0
  19. package/dist/theme.css +201 -0
  20. package/dist/themes/amethyst.css +76 -0
  21. package/dist/themes/bubblegum.css +79 -0
  22. package/dist/themes/claude.css +79 -0
  23. package/dist/themes/evergreen.css +76 -0
  24. package/dist/themes/graphite.css +79 -0
  25. package/dist/themes/mono.css +79 -0
  26. package/dist/themes/nord.css +79 -0
  27. package/dist/themes/ocean.css +76 -0
  28. package/dist/themes/rose.css +78 -0
  29. package/dist/themes/tangerine.css +81 -0
  30. package/dist/toc/from-content.d.ts +11 -0
  31. package/dist/toc/from-content.js +18 -0
  32. package/dist/toc/index.d.ts +3 -0
  33. package/dist/toc/index.js +3 -0
  34. package/dist/toc/toc.svelte.d.ts +26 -0
  35. package/dist/toc/toc.svelte.js +74 -0
  36. package/dist/toc/types.d.ts +11 -0
  37. package/dist/toc/types.js +1 -0
  38. package/dist/types.d.ts +13 -0
  39. package/dist/types.js +1 -0
  40. package/dist/ui/accordion-item.svelte +24 -0
  41. package/dist/ui/accordion-item.svelte.d.ts +9 -0
  42. package/dist/ui/accordion.svelte +45 -0
  43. package/dist/ui/accordion.svelte.d.ts +15 -0
  44. package/dist/ui/background-pattern.svelte +17 -0
  45. package/dist/ui/background-pattern.svelte.d.ts +26 -0
  46. package/dist/ui/badge.svelte +54 -0
  47. package/dist/ui/badge.svelte.d.ts +12 -0
  48. package/dist/ui/callout.svelte +125 -0
  49. package/dist/ui/callout.svelte.d.ts +13 -0
  50. package/dist/ui/card-grid.svelte +10 -0
  51. package/dist/ui/card-grid.svelte.d.ts +7 -0
  52. package/dist/ui/card.svelte +52 -0
  53. package/dist/ui/card.svelte.d.ts +14 -0
  54. package/dist/ui/copy-button.svelte +30 -0
  55. package/dist/ui/copy-button.svelte.d.ts +9 -0
  56. package/dist/ui/file-tree-item.svelte +70 -0
  57. package/dist/ui/file-tree-item.svelte.d.ts +14 -0
  58. package/dist/ui/file-tree.svelte +34 -0
  59. package/dist/ui/file-tree.svelte.d.ts +7 -0
  60. package/dist/ui/icons/github.svelte +14 -0
  61. package/dist/ui/icons/github.svelte.d.ts +6 -0
  62. package/dist/ui/kbd.svelte +11 -0
  63. package/dist/ui/kbd.svelte.d.ts +7 -0
  64. package/dist/ui/layouts/breadcrumbs.svelte +37 -0
  65. package/dist/ui/layouts/breadcrumbs.svelte.d.ts +10 -0
  66. package/dist/ui/layouts/docs-footer.svelte +40 -0
  67. package/dist/ui/layouts/docs-footer.svelte.d.ts +7 -0
  68. package/dist/ui/layouts/docs-header.svelte +109 -0
  69. package/dist/ui/layouts/docs-header.svelte.d.ts +18 -0
  70. package/dist/ui/layouts/docs-mobile-header.svelte +106 -0
  71. package/dist/ui/layouts/docs-mobile-header.svelte.d.ts +18 -0
  72. package/dist/ui/layouts/docs-shell.svelte +133 -0
  73. package/dist/ui/layouts/docs-shell.svelte.d.ts +25 -0
  74. package/dist/ui/layouts/docs-sidebar.svelte +36 -0
  75. package/dist/ui/layouts/docs-sidebar.svelte.d.ts +8 -0
  76. package/dist/ui/layouts/prev-next-nav.svelte +32 -0
  77. package/dist/ui/layouts/prev-next-nav.svelte.d.ts +8 -0
  78. package/dist/ui/live-example.svelte +67 -0
  79. package/dist/ui/live-example.svelte.d.ts +10 -0
  80. package/dist/ui/markdown/code.svelte +11 -0
  81. package/dist/ui/markdown/code.svelte.d.ts +7 -0
  82. package/dist/ui/markdown/h2.svelte +13 -0
  83. package/dist/ui/markdown/h2.svelte.d.ts +4 -0
  84. package/dist/ui/markdown/h3.svelte +13 -0
  85. package/dist/ui/markdown/h3.svelte.d.ts +4 -0
  86. package/dist/ui/markdown/index.d.ts +5 -0
  87. package/dist/ui/markdown/index.js +5 -0
  88. package/dist/ui/markdown/pre.svelte +88 -0
  89. package/dist/ui/markdown/pre.svelte.d.ts +7 -0
  90. package/dist/ui/markdown/table.svelte +80 -0
  91. package/dist/ui/markdown/table.svelte.d.ts +7 -0
  92. package/dist/ui/markdown/types.d.ts +3 -0
  93. package/dist/ui/markdown/types.js +1 -0
  94. package/dist/ui/prop.svelte +130 -0
  95. package/dist/ui/prop.svelte.d.ts +16 -0
  96. package/dist/ui/props-table.svelte +127 -0
  97. package/dist/ui/props-table.svelte.d.ts +9 -0
  98. package/dist/ui/shadcn/accordion/accordion-content.svelte +27 -0
  99. package/dist/ui/shadcn/accordion/accordion-content.svelte.d.ts +4 -0
  100. package/dist/ui/shadcn/accordion/accordion-item.svelte +17 -0
  101. package/dist/ui/shadcn/accordion/accordion-item.svelte.d.ts +4 -0
  102. package/dist/ui/shadcn/accordion/accordion-trigger.svelte +38 -0
  103. package/dist/ui/shadcn/accordion/accordion-trigger.svelte.d.ts +8 -0
  104. package/dist/ui/shadcn/accordion/accordion.svelte +19 -0
  105. package/dist/ui/shadcn/accordion/accordion.svelte.d.ts +4 -0
  106. package/dist/ui/shadcn/accordion/index.d.ts +5 -0
  107. package/dist/ui/shadcn/accordion/index.js +7 -0
  108. package/dist/ui/shadcn/badge/badge.svelte +50 -0
  109. package/dist/ui/shadcn/badge/badge.svelte.d.ts +38 -0
  110. package/dist/ui/shadcn/badge/index.d.ts +2 -0
  111. package/dist/ui/shadcn/badge/index.js +2 -0
  112. package/dist/ui/shadcn/button/button.svelte +89 -0
  113. package/dist/ui/shadcn/button/button.svelte.d.ts +70 -0
  114. package/dist/ui/shadcn/button/index.d.ts +2 -0
  115. package/dist/ui/shadcn/button/index.js +4 -0
  116. package/dist/ui/shadcn/popover/index.d.ts +9 -0
  117. package/dist/ui/shadcn/popover/index.js +11 -0
  118. package/dist/ui/shadcn/popover/popover-close.svelte +7 -0
  119. package/dist/ui/shadcn/popover/popover-close.svelte.d.ts +4 -0
  120. package/dist/ui/shadcn/popover/popover-content.svelte +31 -0
  121. package/dist/ui/shadcn/popover/popover-content.svelte.d.ts +10 -0
  122. package/dist/ui/shadcn/popover/popover-description.svelte +20 -0
  123. package/dist/ui/shadcn/popover/popover-description.svelte.d.ts +5 -0
  124. package/dist/ui/shadcn/popover/popover-header.svelte +20 -0
  125. package/dist/ui/shadcn/popover/popover-header.svelte.d.ts +5 -0
  126. package/dist/ui/shadcn/popover/popover-portal.svelte +7 -0
  127. package/dist/ui/shadcn/popover/popover-portal.svelte.d.ts +3 -0
  128. package/dist/ui/shadcn/popover/popover-title.svelte +15 -0
  129. package/dist/ui/shadcn/popover/popover-title.svelte.d.ts +5 -0
  130. package/dist/ui/shadcn/popover/popover-trigger.svelte +17 -0
  131. package/dist/ui/shadcn/popover/popover-trigger.svelte.d.ts +4 -0
  132. package/dist/ui/shadcn/popover/popover.svelte +7 -0
  133. package/dist/ui/shadcn/popover/popover.svelte.d.ts +3 -0
  134. package/dist/ui/shadcn/scroll-area/index.d.ts +3 -0
  135. package/dist/ui/shadcn/scroll-area/index.js +5 -0
  136. package/dist/ui/shadcn/scroll-area/scroll-area-scrollbar.svelte +30 -0
  137. package/dist/ui/shadcn/scroll-area/scroll-area-scrollbar.svelte.d.ts +4 -0
  138. package/dist/ui/shadcn/scroll-area/scroll-area.svelte +43 -0
  139. package/dist/ui/shadcn/scroll-area/scroll-area.svelte.d.ts +11 -0
  140. package/dist/ui/shadcn/separator/index.d.ts +2 -0
  141. package/dist/ui/shadcn/separator/index.js +4 -0
  142. package/dist/ui/shadcn/separator/separator.svelte +23 -0
  143. package/dist/ui/shadcn/separator/separator.svelte.d.ts +4 -0
  144. package/dist/ui/shadcn/sheet/index.d.ts +11 -0
  145. package/dist/ui/shadcn/sheet/index.js +13 -0
  146. package/dist/ui/shadcn/sheet/sheet-close.svelte +7 -0
  147. package/dist/ui/shadcn/sheet/sheet-close.svelte.d.ts +4 -0
  148. package/dist/ui/shadcn/sheet/sheet-content.svelte +55 -0
  149. package/dist/ui/shadcn/sheet/sheet-content.svelte.d.ts +15 -0
  150. package/dist/ui/shadcn/sheet/sheet-description.svelte +17 -0
  151. package/dist/ui/shadcn/sheet/sheet-description.svelte.d.ts +4 -0
  152. package/dist/ui/shadcn/sheet/sheet-footer.svelte +20 -0
  153. package/dist/ui/shadcn/sheet/sheet-footer.svelte.d.ts +5 -0
  154. package/dist/ui/shadcn/sheet/sheet-header.svelte +20 -0
  155. package/dist/ui/shadcn/sheet/sheet-header.svelte.d.ts +5 -0
  156. package/dist/ui/shadcn/sheet/sheet-overlay.svelte +17 -0
  157. package/dist/ui/shadcn/sheet/sheet-overlay.svelte.d.ts +4 -0
  158. package/dist/ui/shadcn/sheet/sheet-portal.svelte +7 -0
  159. package/dist/ui/shadcn/sheet/sheet-portal.svelte.d.ts +3 -0
  160. package/dist/ui/shadcn/sheet/sheet-title.svelte +17 -0
  161. package/dist/ui/shadcn/sheet/sheet-title.svelte.d.ts +4 -0
  162. package/dist/ui/shadcn/sheet/sheet-trigger.svelte +7 -0
  163. package/dist/ui/shadcn/sheet/sheet-trigger.svelte.d.ts +4 -0
  164. package/dist/ui/shadcn/sheet/sheet.svelte +7 -0
  165. package/dist/ui/shadcn/sheet/sheet.svelte.d.ts +3 -0
  166. package/dist/ui/shadcn/tabs/index.d.ts +5 -0
  167. package/dist/ui/shadcn/tabs/index.js +7 -0
  168. package/dist/ui/shadcn/tabs/tabs-content.svelte +17 -0
  169. package/dist/ui/shadcn/tabs/tabs-content.svelte.d.ts +4 -0
  170. package/dist/ui/shadcn/tabs/tabs-list.svelte +40 -0
  171. package/dist/ui/shadcn/tabs/tabs-list.svelte.d.ts +25 -0
  172. package/dist/ui/shadcn/tabs/tabs-trigger.svelte +23 -0
  173. package/dist/ui/shadcn/tabs/tabs-trigger.svelte.d.ts +4 -0
  174. package/dist/ui/shadcn/tabs/tabs.svelte +19 -0
  175. package/dist/ui/shadcn/tabs/tabs.svelte.d.ts +4 -0
  176. package/dist/ui/step.svelte +82 -0
  177. package/dist/ui/step.svelte.d.ts +9 -0
  178. package/dist/ui/steps.svelte +71 -0
  179. package/dist/ui/steps.svelte.d.ts +7 -0
  180. package/dist/ui/tab-item.svelte +14 -0
  181. package/dist/ui/tab-item.svelte.d.ts +6 -0
  182. package/dist/ui/table-of-contents.svelte +114 -0
  183. package/dist/ui/table-of-contents.svelte.d.ts +9 -0
  184. package/dist/ui/tabs.svelte +29 -0
  185. package/dist/ui/tabs.svelte.d.ts +8 -0
  186. package/dist/ui/theme-provider.svelte +23 -0
  187. package/dist/ui/theme-provider.svelte.d.ts +10 -0
  188. package/dist/ui/theme-toggle.svelte +22 -0
  189. package/dist/ui/theme-toggle.svelte.d.ts +6 -0
  190. package/dist/vite.d.ts +32 -0
  191. package/dist/vite.js +216 -0
  192. package/package.json +115 -0
@@ -0,0 +1,114 @@
1
+ <script lang="ts">
2
+ import { cn } from '../shadcn.js';
3
+ import type { TocItem } from '../toc/index.js';
4
+ import List from '@lucide/svelte/icons/list';
5
+
6
+ const {
7
+ items = [],
8
+ activeId = null,
9
+ class: className = ''
10
+ }: {
11
+ items?: TocItem[];
12
+ activeId?: string | null;
13
+ class?: string;
14
+ } = $props();
15
+
16
+ // The single primary marker slides along the rail to the active entry. We
17
+ // measure the active anchor's box and animate the marker to it.
18
+ let railEl = $state<HTMLElement | null>(null);
19
+ let markerTop = $state(0);
20
+ let markerHeight = $state(0);
21
+ // Gate the transition so the first placement snaps instead of sliding in.
22
+ let animate = $state(false);
23
+
24
+ $effect(() => {
25
+ const id = activeId;
26
+ void items; // re-measure when the heading set changes
27
+ if (!railEl || !id) {
28
+ markerHeight = 0;
29
+ return;
30
+ }
31
+ const target = railEl.querySelector<HTMLElement>(`[data-toc-id="${CSS.escape(id)}"]`);
32
+ if (!target) {
33
+ markerHeight = 0;
34
+ return;
35
+ }
36
+ markerTop = target.offsetTop;
37
+ markerHeight = target.offsetHeight;
38
+ if (!animate) requestAnimationFrame(() => (animate = true));
39
+ });
40
+ </script>
41
+
42
+ {#if items.length > 0}
43
+ <aside class={cn('sticky top-24 self-start', className)}>
44
+ <nav aria-label="On this page" class="pr-2">
45
+ <p class="text-foreground mb-3 flex items-center gap-2 text-sm font-semibold">
46
+ <List class="size-4" />
47
+ On this page
48
+ </p>
49
+ <div bind:this={railEl} class="toc-rail max-h-[calc(100vh-16rem)] overflow-y-auto text-sm">
50
+ <span
51
+ class="toc-marker"
52
+ class:toc-marker-animate={animate}
53
+ style="transform: translateY({markerTop}px); height: {markerHeight}px; opacity: {markerHeight >
54
+ 0
55
+ ? 1
56
+ : 0};"
57
+ aria-hidden="true"
58
+ ></span>
59
+ <ul>
60
+ {#each items as item (item.id)}
61
+ <li>
62
+ <a
63
+ href="#{item.id}"
64
+ data-toc-id={item.id}
65
+ aria-current={activeId === item.id ? 'location' : undefined}
66
+ class={cn(
67
+ 'text-muted-foreground hover:text-foreground block py-1.5 pl-4 leading-snug transition-colors duration-150',
68
+ item.depth === 3 && 'pl-8',
69
+ activeId === item.id && 'text-primary font-medium'
70
+ )}
71
+ >
72
+ {item.title}
73
+ </a>
74
+ </li>
75
+ {/each}
76
+ </ul>
77
+ </div>
78
+ </nav>
79
+ </aside>
80
+ {/if}
81
+
82
+ <style>
83
+ /* The faint full-height rail; the marker is the bright segment on top of it. */
84
+ .toc-rail {
85
+ position: relative;
86
+ border-left: 1px solid var(--border);
87
+ }
88
+ .toc-rail ul {
89
+ margin: 0;
90
+ padding: 0;
91
+ list-style: none;
92
+ }
93
+ .toc-marker {
94
+ position: absolute;
95
+ left: -1px;
96
+ top: 0;
97
+ width: 2px;
98
+ border-radius: 1px;
99
+ background: var(--primary);
100
+ /* Placement is set inline; only animate once past the first frame. */
101
+ will-change: transform, height;
102
+ }
103
+ .toc-marker-animate {
104
+ transition:
105
+ transform 280ms cubic-bezier(0.22, 1, 0.36, 1),
106
+ height 280ms cubic-bezier(0.22, 1, 0.36, 1),
107
+ opacity 160ms ease;
108
+ }
109
+ @media (prefers-reduced-motion: reduce) {
110
+ .toc-marker-animate {
111
+ transition: opacity 160ms ease;
112
+ }
113
+ }
114
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { TocItem } from '../toc/index.js';
2
+ type $$ComponentProps = {
3
+ items?: TocItem[];
4
+ activeId?: string | null;
5
+ class?: string;
6
+ };
7
+ declare const TableOfContents: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type TableOfContents = ReturnType<typeof TableOfContents>;
9
+ export default TableOfContents;
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import * as Tabs from './shadcn/tabs/index.js';
3
+ import type { WithChildren } from '../types.js';
4
+
5
+ type TabsProps = {
6
+ items: string[];
7
+ value: string;
8
+ };
9
+
10
+ const { children, items, value = items[0] }: WithChildren<TabsProps> = $props();
11
+ </script>
12
+
13
+ <Tabs.Root class="my-6" {value}>
14
+ <Tabs.List class="not-prose mb-3">
15
+ {#each items as item (item)}
16
+ <!-- bits-ui v2 emits data-state="active"; the vendored trigger styles the
17
+ active state with data-active (a skew from the CLI's registry version),
18
+ so paint the active state here to match what's actually emitted. -->
19
+ <Tabs.Trigger
20
+ value={item}
21
+ class="data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm"
22
+ >
23
+ {item}
24
+ </Tabs.Trigger>
25
+ {/each}
26
+ </Tabs.List>
27
+
28
+ {@render children()}
29
+ </Tabs.Root>
@@ -0,0 +1,8 @@
1
+ import * as Tabs from './shadcn/tabs/index.js';
2
+ import type { WithChildren } from '../types.js';
3
+ declare const Tabs: import("svelte").Component<WithChildren<{
4
+ items: string[];
5
+ value: string;
6
+ }>, {}, "">;
7
+ type Tabs = ReturnType<typeof Tabs>;
8
+ export default Tabs;
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { ModeWatcher } from 'mode-watcher';
3
+ import type { Snippet } from 'svelte';
4
+
5
+ const {
6
+ children,
7
+ disableTransitions = true
8
+ }: {
9
+ /** App content. Optional — the provider works as a bare `<ThemeProvider />` too. */
10
+ children?: Snippet;
11
+ /** Suppress the color transition on theme change (avoids a flash). */
12
+ disableTransitions?: boolean;
13
+ } = $props();
14
+ </script>
15
+
16
+ <!--
17
+ Owns light/dark for the whole app: mounts mode-watcher, which toggles the
18
+ `.dark` class on <html> and persists the choice. `DocsShell` renders this
19
+ internally, so a consumer never installs or wires mode-watcher themselves.
20
+ Use it directly to wrap non-DocsShell pages (a marketing/landing route).
21
+ -->
22
+ <ModeWatcher {disableTransitions} />
23
+ {#if children}{@render children()}{/if}
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ /** App content. Optional — the provider works as a bare `<ThemeProvider />` too. */
4
+ children?: Snippet;
5
+ /** Suppress the color transition on theme change (avoids a flash). */
6
+ disableTransitions?: boolean;
7
+ };
8
+ declare const ThemeProvider: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type ThemeProvider = ReturnType<typeof ThemeProvider>;
10
+ export default ThemeProvider;
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import { Button } from './shadcn/button/index.js';
3
+ import Moon from '@lucide/svelte/icons/moon';
4
+ import Sun from '@lucide/svelte/icons/sun';
5
+ import { mode, toggleMode } from 'mode-watcher';
6
+
7
+ const { class: className }: { class?: string } = $props();
8
+ </script>
9
+
10
+ <Button
11
+ size="icon"
12
+ variant="ghost"
13
+ onclick={toggleMode}
14
+ class="text-muted-foreground hover:text-foreground size-8 {className ?? ''}"
15
+ >
16
+ {#if mode.current === 'dark'}
17
+ <Sun class="size-4" />
18
+ {:else}
19
+ <Moon class="size-4" />
20
+ {/if}
21
+ <span class="sr-only">Toggle theme</span>
22
+ </Button>
@@ -0,0 +1,6 @@
1
+ type $$ComponentProps = {
2
+ class?: string;
3
+ };
4
+ declare const ThemeToggle: import("svelte").Component<$$ComponentProps, {}, "">;
5
+ type ThemeToggle = ReturnType<typeof ThemeToggle>;
6
+ export default ThemeToggle;
package/dist/vite.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ import type { Plugin } from 'vite';
2
+ import type { DocsContentItem } from './config.js';
3
+ export interface DocsmithViteOptions {
4
+ /** Directory scanned for doc pages. Default: `'src/routes/docs'`. */
5
+ content?: string;
6
+ /**
7
+ * Routes root used to derive each page's URL from its location.
8
+ * Default: `'src/routes'` (so `src/routes/docs/intro/+page.md` → `/docs/intro`).
9
+ */
10
+ routes?: string;
11
+ /** Shiki themes for the `?source` render. Default: github-light/github-dark. */
12
+ themes?: {
13
+ light: string;
14
+ dark: string;
15
+ };
16
+ }
17
+ /**
18
+ * The svelte-docsmith Vite plugin. Add it to `plugins` in `vite.config.ts`:
19
+ *
20
+ * ```ts
21
+ * import { docsmith } from 'svelte-docsmith/vite';
22
+ * export default defineConfig({ plugins: [docsmith(), tailwindcss(), sveltekit()] });
23
+ * ```
24
+ */
25
+ export declare function docsmith(options?: DocsmithViteOptions): Plugin[];
26
+ /**
27
+ * Scan `contentDir` for `+page.md`/`+page.svx` files and read the frontmatter
28
+ * fields the sidebar needs (plus the heading list for a server-rendered TOC),
29
+ * deriving each page's URL from its directory relative to `routesDir`. Pure and
30
+ * synchronous so it can be unit-tested.
31
+ */
32
+ export declare function collectDocs(contentDir: string, routesDir: string): DocsContentItem[];
package/dist/vite.js ADDED
@@ -0,0 +1,216 @@
1
+ /**
2
+ * `svelte-docsmith/vite` — the build-time half of the framework. Node context
3
+ * only. One `docsmith()` call in `vite.config.ts` wires two things:
4
+ *
5
+ * 1. A **content index** served as the virtual module `svelte-docsmith/content`
6
+ * — your doc pages' frontmatter, scanned at build time, so the sidebar nav
7
+ * is derived from content and never hand-written. No velite, no collection
8
+ * config, no aliases.
9
+ * 2. The **`?source` transform** powering `LiveExample`: importing
10
+ * `Component.svelte?source` yields that file's Shiki-highlighted source.
11
+ */
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import yaml from 'js-yaml';
15
+ import { DEFAULT_THEMES, lazyHighlighter } from './highlight.js';
16
+ /**
17
+ * The svelte-docsmith Vite plugin. Add it to `plugins` in `vite.config.ts`:
18
+ *
19
+ * ```ts
20
+ * import { docsmith } from 'svelte-docsmith/vite';
21
+ * export default defineConfig({ plugins: [docsmith(), tailwindcss(), sveltekit()] });
22
+ * ```
23
+ */
24
+ export function docsmith(options = {}) {
25
+ return [contentIndexPlugin(options), exampleSourcePlugin(options)];
26
+ }
27
+ // --- content index -------------------------------------------------------
28
+ const CONTENT_SPECIFIER = 'svelte-docsmith/content';
29
+ const VIRTUAL_CONTENT_ID = '\0svelte-docsmith:content';
30
+ const PAGE_NAMES = ['+page.md', '+page.svx'];
31
+ function isPageFile(file) {
32
+ return PAGE_NAMES.some((name) => file.endsWith(name));
33
+ }
34
+ /**
35
+ * List the absolute paths of every `+page.md`/`+page.svx` under `contentDir`.
36
+ */
37
+ function listPageFiles(contentDir) {
38
+ if (!fs.existsSync(contentDir))
39
+ return [];
40
+ const entries = fs.readdirSync(contentDir, { recursive: true, withFileTypes: true });
41
+ const files = [];
42
+ for (const entry of entries) {
43
+ if (!entry.isFile() || !isPageFile(entry.name))
44
+ continue;
45
+ const dir = entry.parentPath ?? entry.path ?? contentDir;
46
+ files.push(path.join(dir, entry.name));
47
+ }
48
+ return files;
49
+ }
50
+ /** Slugify heading text the way rehype-slug (github-slugger) does for common
51
+ * cases: lowercase, drop punctuation/symbols, spaces → hyphens. */
52
+ function slugify(text) {
53
+ return text
54
+ .trim()
55
+ .toLowerCase()
56
+ .replace(/[^\p{L}\p{N}\s-]/gu, '')
57
+ .replace(/\s+/g, '-');
58
+ }
59
+ /** Strip inline markdown so a heading's TOC label is plain text. */
60
+ function stripInlineMarkdown(text) {
61
+ return text
62
+ .replace(/`([^`]+)`/g, '$1')
63
+ .replace(/\*\*([^*]+)\*\*/g, '$1')
64
+ .replace(/\*([^*]+)\*/g, '$1')
65
+ .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
66
+ .replace(/[_~]/g, '')
67
+ .trim();
68
+ }
69
+ /**
70
+ * Extract `h2`/`h3` headings from a markdown page so the in-page TOC can be
71
+ * server-rendered (no post-hydration pop-in). Skips fenced code blocks; ids
72
+ * match rehype-slug for typical headings, and the runtime engine's DOM re-scan
73
+ * corrects any edge cases after hydration.
74
+ */
75
+ function extractToc(source) {
76
+ const body = source.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '');
77
+ const seen = new Map();
78
+ const toc = [];
79
+ let fence = null;
80
+ for (const line of body.split('\n')) {
81
+ const f = /^\s*(`{3,}|~{3,})/.exec(line);
82
+ if (f) {
83
+ const ch = f[1][0];
84
+ if (fence === null)
85
+ fence = ch;
86
+ else if (ch === fence)
87
+ fence = null;
88
+ continue;
89
+ }
90
+ if (fence !== null)
91
+ continue;
92
+ const m = /^(#{2,3})\s+(.+?)\s*#*\s*$/.exec(line);
93
+ if (!m)
94
+ continue;
95
+ const title = stripInlineMarkdown(m[2]);
96
+ if (!title)
97
+ continue;
98
+ const base = slugify(title);
99
+ const n = seen.get(base) ?? 0;
100
+ seen.set(base, n + 1);
101
+ toc.push({ id: n > 0 ? `${base}-${n}` : base, title, depth: m[1].length });
102
+ }
103
+ return toc;
104
+ }
105
+ /**
106
+ * Scan `contentDir` for `+page.md`/`+page.svx` files and read the frontmatter
107
+ * fields the sidebar needs (plus the heading list for a server-rendered TOC),
108
+ * deriving each page's URL from its directory relative to `routesDir`. Pure and
109
+ * synchronous so it can be unit-tested.
110
+ */
111
+ export function collectDocs(contentDir, routesDir) {
112
+ const items = [];
113
+ for (const file of listPageFiles(contentDir)) {
114
+ const source = fs.readFileSync(file, 'utf-8');
115
+ const front = parseFrontmatter(source);
116
+ if (typeof front.title !== 'string')
117
+ continue; // a page without a title isn't nav-worthy
118
+ const dir = path.dirname(file);
119
+ const url = '/' + path.relative(routesDir, dir).split(path.sep).join('/');
120
+ items.push({
121
+ title: front.title,
122
+ path: url,
123
+ description: typeof front.description === 'string' ? front.description : undefined,
124
+ section: typeof front.section === 'string' ? front.section : undefined,
125
+ order: typeof front.order === 'number' ? front.order : undefined,
126
+ toc: extractToc(source)
127
+ });
128
+ }
129
+ // Stable output keeps the generated module diff-friendly across rebuilds.
130
+ return items.sort((a, b) => a.path.localeCompare(b.path));
131
+ }
132
+ function parseFrontmatter(source) {
133
+ const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(source);
134
+ if (!match)
135
+ return {};
136
+ const data = yaml.load(match[1]);
137
+ return data && typeof data === 'object' ? data : {};
138
+ }
139
+ function contentIndexPlugin(options) {
140
+ const contentDir = path.resolve(options.content ?? 'src/routes/docs');
141
+ const routesDir = path.resolve(options.routes ?? 'src/routes');
142
+ return {
143
+ name: 'docsmith-content',
144
+ enforce: 'pre',
145
+ resolveId(id) {
146
+ if (id === CONTENT_SPECIFIER)
147
+ return VIRTUAL_CONTENT_ID;
148
+ },
149
+ load(id) {
150
+ if (id !== VIRTUAL_CONTENT_ID)
151
+ return;
152
+ // Watch each page file (not the directory) so editing frontmatter
153
+ // re-runs this load. A directory here is treated as an unresolvable
154
+ // import by vite:import-analysis; new/removed pages are handled by
155
+ // the watcher in configureServer.
156
+ for (const file of listPageFiles(contentDir))
157
+ this.addWatchFile(file);
158
+ const docs = collectDocs(contentDir, routesDir);
159
+ return `export const docs = ${JSON.stringify(docs, null, 2)};\n`;
160
+ },
161
+ configureServer(server) {
162
+ server.watcher.add(contentDir);
163
+ const onChange = (file) => {
164
+ if (!isPageFile(file))
165
+ return;
166
+ const mod = server.moduleGraph.getModuleById(VIRTUAL_CONTENT_ID);
167
+ if (!mod)
168
+ return;
169
+ server.moduleGraph.invalidateModule(mod);
170
+ server.ws.send({ type: 'full-reload' });
171
+ };
172
+ server.watcher.on('add', onChange);
173
+ server.watcher.on('unlink', onChange);
174
+ server.watcher.on('change', onChange);
175
+ }
176
+ };
177
+ }
178
+ // --- ?source transform (LiveExample) ------------------------------------
179
+ const SOURCE_PREFIX = '\0docsmith-source:';
180
+ // The virtual id must NOT end in `.svelte`, or vite-plugin-svelte will try to
181
+ // compile the highlighted-HTML string as a component.
182
+ const SOURCE_EXT = '.docsmith-src';
183
+ const SOURCE_SUFFIX = '?source';
184
+ function exampleSourcePlugin(options) {
185
+ const themes = options.themes ?? DEFAULT_THEMES;
186
+ const getHighlighter = lazyHighlighter([themes.light, themes.dark], ['svelte']);
187
+ return {
188
+ name: 'docsmith-example-source',
189
+ enforce: 'pre',
190
+ async resolveId(id, importer) {
191
+ if (!id.endsWith('.svelte' + SOURCE_SUFFIX))
192
+ return;
193
+ const base = id.slice(0, -SOURCE_SUFFIX.length);
194
+ const resolved = await this.resolve(base, importer, { skipSelf: true });
195
+ if (resolved)
196
+ return SOURCE_PREFIX + resolved.id + SOURCE_EXT;
197
+ },
198
+ async load(id) {
199
+ if (!id.startsWith(SOURCE_PREFIX))
200
+ return;
201
+ const file = id.slice(SOURCE_PREFIX.length, -SOURCE_EXT.length);
202
+ this.addWatchFile(file);
203
+ const code = fs.readFileSync(file, 'utf-8').trimEnd();
204
+ const highlighter = await getHighlighter();
205
+ // Strip Shiki's own surface (the inline light `background-color` and the
206
+ // `--shiki-dark-bg` var) so the highlighted source sits on the
207
+ // consuming component's token background — matching markdown code blocks,
208
+ // and never flashing Shiki's default before component CSS applies.
209
+ const html = highlighter
210
+ .codeToHtml(code, { lang: 'svelte', themes })
211
+ .replace(/background-color:[^;"]*;?/gi, '')
212
+ .replace(/--shiki-dark-bg:[^;"]*;?/gi, '');
213
+ return `export default ${JSON.stringify(html)};`;
214
+ }
215
+ };
216
+ }
package/package.json ADDED
@@ -0,0 +1,115 @@
1
+ {
2
+ "name": "svelte-docsmith",
3
+ "version": "0.1.0",
4
+ "description": "A framework for building beautiful documentation sites with Svelte.",
5
+ "author": {
6
+ "name": "George Daskalakis",
7
+ "email": "george.dask97@gmail.com",
8
+ "url": "https://github.com/geodask"
9
+ },
10
+ "main": "dist/index.js",
11
+ "files": [
12
+ "dist",
13
+ "!dist/**/*.test.*",
14
+ "!dist/**/*.spec.*"
15
+ ],
16
+ "sideEffects": false,
17
+ "svelte": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "svelte": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./preprocess": {
27
+ "types": "./dist/preprocess.d.ts",
28
+ "default": "./dist/preprocess.js"
29
+ },
30
+ "./vite": {
31
+ "types": "./dist/vite.d.ts",
32
+ "default": "./dist/vite.js"
33
+ },
34
+ "./content": {
35
+ "types": "./dist/content.d.ts",
36
+ "default": "./dist/content.js"
37
+ },
38
+ "./theme.css": "./dist/theme.css",
39
+ "./themes/*.css": "./dist/themes/*.css",
40
+ "./package.json": "./package.json"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/geodask/svelte-docsmith.git"
48
+ },
49
+ "license": "MIT",
50
+ "peerDependencies": {
51
+ "@sveltejs/kit": "^2.16.0",
52
+ "svelte": "^5.0.0",
53
+ "tailwindcss": "^4.0.0"
54
+ },
55
+ "devDependencies": {
56
+ "@fontsource-variable/inter": "^5.2.8",
57
+ "@internationalized/date": "^3.12.2",
58
+ "@lucide/svelte": "^1.23.0",
59
+ "@sveltejs/adapter-auto": "^7.0.1",
60
+ "@sveltejs/adapter-vercel": "^6.3.4",
61
+ "@sveltejs/kit": "^2.69.1",
62
+ "@sveltejs/package": "^2.5.8",
63
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
64
+ "@testing-library/jest-dom": "^6.6.3",
65
+ "@testing-library/svelte": "^5.2.4",
66
+ "@types/js-yaml": "^4.0.9",
67
+ "@types/node": "^22",
68
+ "@vitest/coverage-v8": "4.1.9",
69
+ "clsx": "^2.1.1",
70
+ "jsdom": "^26.0.0",
71
+ "publint": "^0.3.2",
72
+ "shadcn-svelte": "^1.3.0",
73
+ "svelte": "^5.56.4",
74
+ "svelte-check": "^4.7.1",
75
+ "tailwind-merge": "^3.6.0",
76
+ "tailwind-variants": "^3.2.2",
77
+ "tailwindcss": "^4.3.2",
78
+ "tw-animate-css": "^1.4.0",
79
+ "typescript": "^5.0.0",
80
+ "vite": "^8.1.3",
81
+ "vitest": "^4.1.9"
82
+ },
83
+ "dependencies": {
84
+ "@hbsnow/rehype-sectionize": "^1.0.7",
85
+ "@shikijs/transformers": "^3.3.0",
86
+ "@tailwindcss/typography": "^0.5.16",
87
+ "bits-ui": "^2.18.1",
88
+ "esm-env": "^1.2.2",
89
+ "js-yaml": "^4.1.0",
90
+ "mdsvex": "^0.12.3",
91
+ "mode-watcher": "^1.0.2",
92
+ "rehype-slug": "^6.0.0",
93
+ "shiki": "^3.2.2"
94
+ },
95
+ "keywords": [
96
+ "svelte",
97
+ "ui",
98
+ "component",
99
+ "docs"
100
+ ],
101
+ "scripts": {
102
+ "dev": "vite dev",
103
+ "build": "vite build && npm run prepack",
104
+ "preview": "vite preview",
105
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
106
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
107
+ "format": "prettier --config ../../.prettierrc --write .",
108
+ "lint": "prettier --config ../../.prettierrc --check . && eslint .",
109
+ "test": "vitest run",
110
+ "test:coverage": "vitest run --coverage",
111
+ "test:unit": "vitest run",
112
+ "typecheck": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
113
+ "size": "size-limit"
114
+ }
115
+ }