svelte-docsmith 0.2.0 → 0.4.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.
- package/README.md +66 -25
- package/dist/clipboard.svelte.d.ts +1 -1
- package/dist/clipboard.svelte.js +15 -8
- package/dist/config.d.ts +36 -6
- package/dist/config.js +33 -7
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -0
- package/dist/markdown-layout.svelte +14 -1
- package/dist/markdown-layout.svelte.d.ts +2 -0
- package/dist/preprocess.d.ts +4 -5
- package/dist/preprocess.js +15 -7
- package/dist/search/context.svelte.d.ts +7 -0
- package/dist/search/context.svelte.js +20 -0
- package/dist/search/create-search.d.ts +14 -0
- package/dist/search/create-search.js +56 -0
- package/dist/search/snippet.d.ts +7 -0
- package/dist/search/snippet.js +43 -0
- package/dist/search.d.ts +10 -0
- package/dist/search.js +4 -0
- package/dist/theme.css +25 -0
- package/dist/types.d.ts +0 -6
- package/dist/ui/badge.svelte +10 -1
- package/dist/ui/badge.svelte.d.ts +2 -0
- package/dist/ui/callout.svelte +9 -9
- package/dist/ui/callout.svelte.d.ts +3 -3
- package/dist/ui/layouts/docs-header.svelte +5 -11
- package/dist/ui/layouts/docs-header.svelte.d.ts +0 -6
- package/dist/ui/layouts/docs-mobile-header.svelte +40 -8
- package/dist/ui/layouts/docs-mobile-header.svelte.d.ts +5 -4
- package/dist/ui/layouts/docs-shell.svelte +49 -5
- package/dist/ui/layouts/docs-shell.svelte.d.ts +16 -1
- package/dist/ui/layouts/error-page.svelte +82 -0
- package/dist/ui/layouts/error-page.svelte.d.ts +25 -0
- package/dist/ui/layouts/seo-head.svelte +65 -0
- package/dist/ui/layouts/seo-head.svelte.d.ts +11 -0
- package/dist/ui/markdown/h2.svelte +7 -3
- package/dist/ui/markdown/h3.svelte +7 -3
- package/dist/ui/markdown/pre.svelte +79 -29
- package/dist/ui/prop.svelte +71 -36
- package/dist/ui/prop.svelte.d.ts +1 -1
- package/dist/ui/props-table.svelte +10 -62
- package/dist/ui/search-trigger.svelte +31 -0
- package/dist/ui/search-trigger.svelte.d.ts +6 -0
- package/dist/ui/search.svelte +167 -0
- package/dist/ui/search.svelte.d.ts +13 -0
- package/dist/ui/shadcn/accordion/accordion.svelte.d.ts +1 -1
- package/dist/ui/shadcn/command/command-dialog.svelte +42 -0
- package/dist/ui/shadcn/command/command-dialog.svelte.d.ts +14 -0
- package/dist/ui/shadcn/command/command-empty.svelte +17 -0
- package/dist/ui/shadcn/command/command-empty.svelte.d.ts +4 -0
- package/dist/ui/shadcn/command/command-group.svelte +33 -0
- package/dist/ui/shadcn/command/command-group.svelte.d.ts +7 -0
- package/dist/ui/shadcn/command/command-input.svelte +36 -0
- package/dist/ui/shadcn/command/command-input.svelte.d.ts +4 -0
- package/dist/ui/shadcn/command/command-item.svelte +27 -0
- package/dist/ui/shadcn/command/command-item.svelte.d.ts +4 -0
- package/dist/ui/shadcn/command/command-link-item.svelte +20 -0
- package/dist/ui/shadcn/command/command-link-item.svelte.d.ts +4 -0
- package/dist/ui/shadcn/command/command-list.svelte +20 -0
- package/dist/ui/shadcn/command/command-list.svelte.d.ts +4 -0
- package/dist/ui/shadcn/command/command-loading.svelte +7 -0
- package/dist/ui/shadcn/command/command-loading.svelte.d.ts +4 -0
- package/dist/ui/shadcn/command/command-separator.svelte +17 -0
- package/dist/ui/shadcn/command/command-separator.svelte.d.ts +4 -0
- package/dist/ui/shadcn/command/command-shortcut.svelte +23 -0
- package/dist/ui/shadcn/command/command-shortcut.svelte.d.ts +5 -0
- package/dist/ui/shadcn/command/command.svelte +28 -0
- package/dist/ui/shadcn/command/command.svelte.d.ts +8 -0
- package/dist/ui/shadcn/command/index.d.ts +12 -0
- package/dist/ui/shadcn/command/index.js +14 -0
- package/dist/ui/shadcn/dialog/dialog-close.svelte +11 -0
- package/dist/ui/shadcn/dialog/dialog-close.svelte.d.ts +4 -0
- package/dist/ui/shadcn/dialog/dialog-content.svelte +48 -0
- package/dist/ui/shadcn/dialog/dialog-content.svelte.d.ts +13 -0
- package/dist/ui/shadcn/dialog/dialog-description.svelte +20 -0
- package/dist/ui/shadcn/dialog/dialog-description.svelte.d.ts +4 -0
- package/dist/ui/shadcn/dialog/dialog-footer.svelte +32 -0
- package/dist/ui/shadcn/dialog/dialog-footer.svelte.d.ts +8 -0
- package/dist/ui/shadcn/dialog/dialog-header.svelte +20 -0
- package/dist/ui/shadcn/dialog/dialog-header.svelte.d.ts +5 -0
- package/dist/ui/shadcn/dialog/dialog-overlay.svelte +20 -0
- package/dist/ui/shadcn/dialog/dialog-overlay.svelte.d.ts +4 -0
- package/dist/ui/shadcn/dialog/dialog-portal.svelte +7 -0
- package/dist/ui/shadcn/dialog/dialog-portal.svelte.d.ts +3 -0
- package/dist/ui/shadcn/dialog/dialog-title.svelte +17 -0
- package/dist/ui/shadcn/dialog/dialog-title.svelte.d.ts +4 -0
- package/dist/ui/shadcn/dialog/dialog-trigger.svelte +11 -0
- package/dist/ui/shadcn/dialog/dialog-trigger.svelte.d.ts +4 -0
- package/dist/ui/shadcn/dialog/dialog.svelte +7 -0
- package/dist/ui/shadcn/dialog/dialog.svelte.d.ts +3 -0
- package/dist/ui/shadcn/dialog/index.d.ts +11 -0
- package/dist/ui/shadcn/dialog/index.js +13 -0
- package/dist/ui/shadcn/input/index.d.ts +2 -0
- package/dist/ui/shadcn/input/index.js +4 -0
- package/dist/ui/shadcn/input/input.svelte +48 -0
- package/dist/ui/shadcn/input/input.svelte.d.ts +13 -0
- package/dist/ui/shadcn/input-group/index.d.ts +7 -0
- package/dist/ui/shadcn/input-group/index.js +9 -0
- package/dist/ui/shadcn/input-group/input-group-addon.svelte +53 -0
- package/dist/ui/shadcn/input-group/input-group-addon.svelte.d.ts +32 -0
- package/dist/ui/shadcn/input-group/input-group-button.svelte +49 -0
- package/dist/ui/shadcn/input-group/input-group-button.svelte.d.ts +32 -0
- package/dist/ui/shadcn/input-group/input-group-input.svelte +23 -0
- package/dist/ui/shadcn/input-group/input-group-input.svelte.d.ts +11 -0
- package/dist/ui/shadcn/input-group/input-group-text.svelte +22 -0
- package/dist/ui/shadcn/input-group/input-group-text.svelte.d.ts +5 -0
- package/dist/ui/shadcn/input-group/input-group-textarea.svelte +23 -0
- package/dist/ui/shadcn/input-group/input-group-textarea.svelte.d.ts +3 -0
- package/dist/ui/shadcn/input-group/input-group.svelte +24 -0
- package/dist/ui/shadcn/input-group/input-group.svelte.d.ts +5 -0
- package/dist/ui/shadcn/tabs/tabs.svelte.d.ts +1 -1
- package/dist/ui/shadcn/textarea/index.d.ts +2 -0
- package/dist/ui/shadcn/textarea/index.js +4 -0
- package/dist/ui/shadcn/textarea/textarea.svelte +23 -0
- package/dist/ui/shadcn/textarea/textarea.svelte.d.ts +5 -0
- package/dist/ui/tab-item.svelte +35 -9
- package/dist/ui/tab-item.svelte.d.ts +9 -4
- package/dist/ui/tabs-phase.svelte +19 -0
- package/dist/ui/tabs-phase.svelte.d.ts +9 -0
- package/dist/ui/tabs.svelte +47 -24
- package/dist/ui/tabs.svelte.d.ts +16 -6
- package/dist/vite.d.ts +8 -6
- package/dist/vite.js +140 -47
- package/package.json +8 -2
package/dist/ui/tabs.svelte
CHANGED
|
@@ -1,29 +1,52 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import
|
|
3
|
-
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import { getContext } from 'svelte';
|
|
3
|
+
|
|
4
|
+
/** Which render pass a <TabItem> is in. See tabs-phase.svelte. */
|
|
5
|
+
export type TabsPhase = 'collect' | 'list' | 'panel';
|
|
6
|
+
|
|
7
|
+
export const TABS_PHASE = Symbol('docsmith-tabs-phase');
|
|
8
|
+
const TABS_CTX = Symbol('docsmith-tabs-ctx');
|
|
4
9
|
|
|
5
|
-
type
|
|
6
|
-
|
|
7
|
-
value: string;
|
|
10
|
+
type TabsContext = {
|
|
11
|
+
/** Called by each <TabItem> during the collect pass, in document order. */
|
|
12
|
+
register: (value: string) => void;
|
|
8
13
|
};
|
|
9
14
|
|
|
10
|
-
|
|
15
|
+
/** Read the enclosing <Tabs> context from a <TabItem>. */
|
|
16
|
+
export function getTabsContext(): TabsContext | undefined {
|
|
17
|
+
return getContext<TabsContext | undefined>(TABS_CTX);
|
|
18
|
+
}
|
|
11
19
|
</script>
|
|
12
20
|
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
<script lang="ts">
|
|
22
|
+
import { setContext, type Snippet } from 'svelte';
|
|
23
|
+
import * as TabsPrimitive from './shadcn/tabs/index.js';
|
|
24
|
+
import TabsPhaseScope from './tabs-phase.svelte';
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
value,
|
|
28
|
+
children
|
|
29
|
+
}: {
|
|
30
|
+
/** Value/label of the tab selected by default. Defaults to the first tab. */
|
|
31
|
+
value?: string;
|
|
32
|
+
children: Snippet;
|
|
33
|
+
} = $props();
|
|
34
|
+
|
|
35
|
+
// The first <TabItem> to register wins the default selection. Captured during
|
|
36
|
+
// the collect pass, which renders (below) before <Tabs.Root> reads it — so the
|
|
37
|
+
// correct panel is already selected in the server-rendered HTML.
|
|
38
|
+
let firstValue = $state<string | undefined>();
|
|
39
|
+
setContext<TabsContext>(TABS_CTX, {
|
|
40
|
+
register: (v) => (firstValue ??= v)
|
|
41
|
+
});
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<!-- collect: registers each TabItem's value; renders nothing. -->
|
|
45
|
+
<TabsPhaseScope phase="collect">{@render children()}</TabsPhaseScope>
|
|
46
|
+
|
|
47
|
+
<TabsPrimitive.Root value={value ?? firstValue ?? ''} class="my-6">
|
|
48
|
+
<TabsPrimitive.List class="not-prose mb-3">
|
|
49
|
+
<TabsPhaseScope phase="list">{@render children()}</TabsPhaseScope>
|
|
50
|
+
</TabsPrimitive.List>
|
|
51
|
+
<TabsPhaseScope phase="panel">{@render children()}</TabsPhaseScope>
|
|
52
|
+
</TabsPrimitive.Root>
|
package/dist/ui/tabs.svelte.d.ts
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
declare const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
/** Which render pass a <TabItem> is in. See tabs-phase.svelte. */
|
|
2
|
+
export type TabsPhase = 'collect' | 'list' | 'panel';
|
|
3
|
+
export declare const TABS_PHASE: unique symbol;
|
|
4
|
+
type TabsContext = {
|
|
5
|
+
/** Called by each <TabItem> during the collect pass, in document order. */
|
|
6
|
+
register: (value: string) => void;
|
|
7
|
+
};
|
|
8
|
+
/** Read the enclosing <Tabs> context from a <TabItem>. */
|
|
9
|
+
export declare function getTabsContext(): TabsContext | undefined;
|
|
10
|
+
import { type Snippet } from 'svelte';
|
|
11
|
+
type $$ComponentProps = {
|
|
12
|
+
/** Value/label of the tab selected by default. Defaults to the first tab. */
|
|
13
|
+
value?: string;
|
|
14
|
+
children: Snippet;
|
|
15
|
+
};
|
|
16
|
+
declare const Tabs: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
17
|
type Tabs = ReturnType<typeof Tabs>;
|
|
8
18
|
export default Tabs;
|
package/dist/vite.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
|
-
import type { DocsContentItem } from './config.js';
|
|
2
|
+
import type { DocsContentItem, SearchDoc } from './config.js';
|
|
3
3
|
export interface DocsmithViteOptions {
|
|
4
4
|
/** Directory scanned for doc pages. Default: `'src/routes/docs'`. */
|
|
5
5
|
content?: string;
|
|
@@ -23,10 +23,12 @@ export interface DocsmithViteOptions {
|
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
25
|
export declare function docsmith(options?: DocsmithViteOptions): Plugin[];
|
|
26
|
+
export declare function collectDocs(contentDir: string, routesDir: string): DocsContentItem[];
|
|
26
27
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* Build the search records for every page under `contentDir`: title, section,
|
|
29
|
+
* description, heading list, and plain-text body. Served as the lazy-loaded
|
|
30
|
+
* `svelte-docsmith/search` virtual module so search can index bodies without
|
|
31
|
+
* bloating the eagerly-imported nav index. The missing-directory case is
|
|
32
|
+
* already reported by {@link collectDocs}, so this stays quiet.
|
|
31
33
|
*/
|
|
32
|
-
export declare function
|
|
34
|
+
export declare function collectSearchDocs(contentDir: string, routesDir: string): SearchDoc[];
|
package/dist/vite.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `svelte-docsmith/vite` — the build-time half of the framework. Node context
|
|
3
|
-
* only. One `docsmith()` call in `vite.config.ts` wires
|
|
3
|
+
* only. One `docsmith()` call in `vite.config.ts` wires three things:
|
|
4
4
|
*
|
|
5
5
|
* 1. A **content index** served as the virtual module `svelte-docsmith/content`
|
|
6
6
|
* — your doc pages' frontmatter, scanned at build time, so the sidebar nav
|
|
7
|
-
* is derived from content and never hand-written. No
|
|
8
|
-
*
|
|
9
|
-
* 2.
|
|
7
|
+
* is derived from content and never hand-written. No collection config, no
|
|
8
|
+
* aliases.
|
|
9
|
+
* 2. A **search index** served as the virtual module `svelte-docsmith/search`
|
|
10
|
+
* — the same pages reduced to plain-text bodies, in a separate chunk so it
|
|
11
|
+
* can be lazy-loaded only when search opens.
|
|
12
|
+
* 3. The **`?source` transform** powering `LiveExample`: importing
|
|
10
13
|
* `Component.svelte?source` yields that file's Shiki-highlighted source.
|
|
11
14
|
*/
|
|
12
15
|
import fs from 'node:fs';
|
|
13
16
|
import path from 'node:path';
|
|
17
|
+
import GithubSlugger from 'github-slugger';
|
|
14
18
|
import yaml from 'js-yaml';
|
|
15
19
|
import { DEFAULT_THEMES, lazyHighlighter } from './highlight.js';
|
|
16
20
|
/**
|
|
@@ -27,6 +31,8 @@ export function docsmith(options = {}) {
|
|
|
27
31
|
// --- content index -------------------------------------------------------
|
|
28
32
|
const CONTENT_SPECIFIER = 'svelte-docsmith/content';
|
|
29
33
|
const VIRTUAL_CONTENT_ID = '\0svelte-docsmith:content';
|
|
34
|
+
const SEARCH_SPECIFIER = 'svelte-docsmith/search';
|
|
35
|
+
const VIRTUAL_SEARCH_ID = '\0svelte-docsmith:search';
|
|
30
36
|
const PAGE_NAMES = ['+page.md', '+page.svx'];
|
|
31
37
|
function isPageFile(file) {
|
|
32
38
|
return PAGE_NAMES.some((name) => file.endsWith(name));
|
|
@@ -47,18 +53,10 @@ function listPageFiles(contentDir) {
|
|
|
47
53
|
}
|
|
48
54
|
return files;
|
|
49
55
|
}
|
|
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
56
|
/** Strip inline markdown so a heading's TOC label is plain text. */
|
|
60
57
|
function stripInlineMarkdown(text) {
|
|
61
58
|
return text
|
|
59
|
+
.replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
62
60
|
.replace(/`([^`]+)`/g, '$1')
|
|
63
61
|
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
64
62
|
.replace(/\*([^*]+)\*/g, '$1')
|
|
@@ -66,15 +64,56 @@ function stripInlineMarkdown(text) {
|
|
|
66
64
|
.replace(/[_~]/g, '')
|
|
67
65
|
.trim();
|
|
68
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Reduce a markdown page to plain, searchable body text: prose and heading text
|
|
69
|
+
* with frontmatter, `<script>`/`<style>` blocks, fenced code, HTML/Svelte tags,
|
|
70
|
+
* and markdown punctuation removed. Feeds the generated search index. Code
|
|
71
|
+
* samples are intentionally dropped to keep the index small and prose-focused.
|
|
72
|
+
*/
|
|
73
|
+
function extractSearchText(source) {
|
|
74
|
+
let body = source.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '');
|
|
75
|
+
// Component/setup blocks aren't prose; drop them whole before line scanning.
|
|
76
|
+
body = body.replace(/<script[\s\S]*?<\/script>/gi, '').replace(/<style[\s\S]*?<\/style>/gi, '');
|
|
77
|
+
const out = [];
|
|
78
|
+
let fence = null;
|
|
79
|
+
for (const line of body.split('\n')) {
|
|
80
|
+
const f = /^\s*(`{3,}|~{3,})/.exec(line);
|
|
81
|
+
if (f) {
|
|
82
|
+
const ch = f[1][0];
|
|
83
|
+
if (fence === null)
|
|
84
|
+
fence = ch;
|
|
85
|
+
else if (ch === fence)
|
|
86
|
+
fence = null;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (fence !== null)
|
|
90
|
+
continue;
|
|
91
|
+
// Skip table delimiter rows (`| --- | :--: |`) — pure structure, no words.
|
|
92
|
+
if (/^\s*\|?[\s:|-]+\|[\s:|-]*$/.test(line))
|
|
93
|
+
continue;
|
|
94
|
+
const text = stripInlineMarkdown(line
|
|
95
|
+
.replace(/<[^>]+>/g, ' ') // strip HTML/Svelte tags, keep their text content
|
|
96
|
+
.replace(/^\s{0,3}#{1,6}\s+/, '') // heading markers
|
|
97
|
+
.replace(/^\s{0,3}>\s?/, '') // blockquote markers
|
|
98
|
+
.replace(/^\s*[-*+]\s+/, '') // unordered list bullets
|
|
99
|
+
.replace(/^\s*\d+\.\s+/, '') // ordered list markers
|
|
100
|
+
.replace(/\s*\|\s*/g, ' ') // table cell separators → spaces, not "| a | b |"
|
|
101
|
+
).replace(/\s+/g, ' ');
|
|
102
|
+
if (text)
|
|
103
|
+
out.push(text);
|
|
104
|
+
}
|
|
105
|
+
return out.join(' ');
|
|
106
|
+
}
|
|
69
107
|
/**
|
|
70
108
|
* 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
|
|
72
|
-
*
|
|
73
|
-
*
|
|
109
|
+
* server-rendered (no post-hydration pop-in). Skips fenced code blocks. Ids are
|
|
110
|
+
* produced with the same `github-slugger` that `rehype-slug` uses at render
|
|
111
|
+
* time — including its duplicate-suffixing — so the SSR anchors match the real
|
|
112
|
+
* heading ids exactly, not just for common cases.
|
|
74
113
|
*/
|
|
75
114
|
function extractToc(source) {
|
|
76
115
|
const body = source.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '');
|
|
77
|
-
const
|
|
116
|
+
const slugger = new GithubSlugger();
|
|
78
117
|
const toc = [];
|
|
79
118
|
let fence = null;
|
|
80
119
|
for (const line of body.split('\n')) {
|
|
@@ -95,30 +134,37 @@ function extractToc(source) {
|
|
|
95
134
|
const title = stripInlineMarkdown(m[2]);
|
|
96
135
|
if (!title)
|
|
97
136
|
continue;
|
|
98
|
-
|
|
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 });
|
|
137
|
+
toc.push({ id: slugger.slug(title), title, depth: m[1].length });
|
|
102
138
|
}
|
|
103
139
|
return toc;
|
|
104
140
|
}
|
|
105
141
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
142
|
+
* Walk every nav-worthy page under `contentDir` once: a page is nav-worthy when
|
|
143
|
+
* its frontmatter has a string `title`. Yields the raw source, parsed
|
|
144
|
+
* frontmatter, derived URL, and title so both the nav index and the search
|
|
145
|
+
* index can be built from a single read of each file.
|
|
110
146
|
*/
|
|
111
|
-
|
|
112
|
-
const items = [];
|
|
147
|
+
function* eachTitledPage(contentDir, routesDir) {
|
|
113
148
|
for (const file of listPageFiles(contentDir)) {
|
|
114
149
|
const source = fs.readFileSync(file, 'utf-8');
|
|
115
|
-
const front = parseFrontmatter(source);
|
|
150
|
+
const front = parseFrontmatter(source, file);
|
|
116
151
|
if (typeof front.title !== 'string')
|
|
117
|
-
continue;
|
|
152
|
+
continue;
|
|
118
153
|
const dir = path.dirname(file);
|
|
119
154
|
const url = '/' + path.relative(routesDir, dir).split(path.sep).join('/');
|
|
155
|
+
yield { source, front, url, title: front.title };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
export function collectDocs(contentDir, routesDir) {
|
|
159
|
+
if (!fs.existsSync(contentDir)) {
|
|
160
|
+
console.warn(`[svelte-docsmith] content directory not found: ${contentDir}\n` +
|
|
161
|
+
` The sidebar will be empty. Create your doc pages there, or point docsmith() at the right place with \`content\`.`);
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
const items = [];
|
|
165
|
+
for (const { source, front, url, title } of eachTitledPage(contentDir, routesDir)) {
|
|
120
166
|
items.push({
|
|
121
|
-
title
|
|
167
|
+
title,
|
|
122
168
|
path: url,
|
|
123
169
|
description: typeof front.description === 'string' ? front.description : undefined,
|
|
124
170
|
section: typeof front.section === 'string' ? front.section : undefined,
|
|
@@ -126,14 +172,48 @@ export function collectDocs(contentDir, routesDir) {
|
|
|
126
172
|
toc: extractToc(source)
|
|
127
173
|
});
|
|
128
174
|
}
|
|
175
|
+
if (items.length === 0) {
|
|
176
|
+
console.warn(`[svelte-docsmith] no doc pages found under ${contentDir}\n` +
|
|
177
|
+
` Add \`+page.md\` files with at least a \`title:\` in their frontmatter to populate the sidebar.`);
|
|
178
|
+
}
|
|
129
179
|
// Stable output keeps the generated module diff-friendly across rebuilds.
|
|
130
180
|
return items.sort((a, b) => a.path.localeCompare(b.path));
|
|
131
181
|
}
|
|
132
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Build the search records for every page under `contentDir`: title, section,
|
|
184
|
+
* description, heading list, and plain-text body. Served as the lazy-loaded
|
|
185
|
+
* `svelte-docsmith/search` virtual module so search can index bodies without
|
|
186
|
+
* bloating the eagerly-imported nav index. The missing-directory case is
|
|
187
|
+
* already reported by {@link collectDocs}, so this stays quiet.
|
|
188
|
+
*/
|
|
189
|
+
export function collectSearchDocs(contentDir, routesDir) {
|
|
190
|
+
if (!fs.existsSync(contentDir))
|
|
191
|
+
return [];
|
|
192
|
+
const docs = [];
|
|
193
|
+
for (const { source, front, url, title } of eachTitledPage(contentDir, routesDir)) {
|
|
194
|
+
docs.push({
|
|
195
|
+
path: url,
|
|
196
|
+
title,
|
|
197
|
+
section: typeof front.section === 'string' ? front.section : undefined,
|
|
198
|
+
description: typeof front.description === 'string' ? front.description : undefined,
|
|
199
|
+
headings: extractToc(source).map((entry) => entry.title),
|
|
200
|
+
text: extractSearchText(source)
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return docs.sort((a, b) => a.path.localeCompare(b.path));
|
|
204
|
+
}
|
|
205
|
+
function parseFrontmatter(source, file) {
|
|
133
206
|
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(source);
|
|
134
207
|
if (!match)
|
|
135
208
|
return {};
|
|
136
|
-
|
|
209
|
+
let data;
|
|
210
|
+
try {
|
|
211
|
+
data = yaml.load(match[1]);
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
215
|
+
throw new Error(`[svelte-docsmith] invalid YAML frontmatter in ${file}\n${reason}`);
|
|
216
|
+
}
|
|
137
217
|
return data && typeof data === 'object' ? data : {};
|
|
138
218
|
}
|
|
139
219
|
function contentIndexPlugin(options) {
|
|
@@ -145,29 +225,42 @@ function contentIndexPlugin(options) {
|
|
|
145
225
|
resolveId(id) {
|
|
146
226
|
if (id === CONTENT_SPECIFIER)
|
|
147
227
|
return VIRTUAL_CONTENT_ID;
|
|
228
|
+
if (id === SEARCH_SPECIFIER)
|
|
229
|
+
return VIRTUAL_SEARCH_ID;
|
|
148
230
|
},
|
|
149
231
|
load(id) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
232
|
+
// Watch each page file (not the directory) so editing frontmatter or
|
|
233
|
+
// body re-runs this load. A directory here is treated as an
|
|
234
|
+
// unresolvable import by vite:import-analysis; new/removed pages are
|
|
235
|
+
// handled by the watcher in configureServer.
|
|
236
|
+
if (id === VIRTUAL_CONTENT_ID) {
|
|
237
|
+
for (const file of listPageFiles(contentDir))
|
|
238
|
+
this.addWatchFile(file);
|
|
239
|
+
const docs = collectDocs(contentDir, routesDir);
|
|
240
|
+
return `export const docs = ${JSON.stringify(docs, null, 2)};\n`;
|
|
241
|
+
}
|
|
242
|
+
if (id === VIRTUAL_SEARCH_ID) {
|
|
243
|
+
for (const file of listPageFiles(contentDir))
|
|
244
|
+
this.addWatchFile(file);
|
|
245
|
+
const docs = collectSearchDocs(contentDir, routesDir);
|
|
246
|
+
return `export const docs = ${JSON.stringify(docs)};\n`;
|
|
247
|
+
}
|
|
160
248
|
},
|
|
161
249
|
configureServer(server) {
|
|
162
250
|
server.watcher.add(contentDir);
|
|
163
251
|
const onChange = (file) => {
|
|
164
252
|
if (!isPageFile(file))
|
|
165
253
|
return;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
254
|
+
let invalidated = false;
|
|
255
|
+
for (const virtualId of [VIRTUAL_CONTENT_ID, VIRTUAL_SEARCH_ID]) {
|
|
256
|
+
const mod = server.moduleGraph.getModuleById(virtualId);
|
|
257
|
+
if (!mod)
|
|
258
|
+
continue;
|
|
259
|
+
server.moduleGraph.invalidateModule(mod);
|
|
260
|
+
invalidated = true;
|
|
261
|
+
}
|
|
262
|
+
if (invalidated)
|
|
263
|
+
server.ws.send({ type: 'full-reload' });
|
|
171
264
|
};
|
|
172
265
|
server.watcher.on('add', onChange);
|
|
173
266
|
server.watcher.on('unlink', onChange);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-docsmith",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A framework for building beautiful documentation sites with Svelte.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "George Daskalakis",
|
|
@@ -35,6 +35,10 @@
|
|
|
35
35
|
"types": "./dist/content.d.ts",
|
|
36
36
|
"default": "./dist/content.js"
|
|
37
37
|
},
|
|
38
|
+
"./search": {
|
|
39
|
+
"types": "./dist/search.d.ts",
|
|
40
|
+
"default": "./dist/search.js"
|
|
41
|
+
},
|
|
38
42
|
"./theme.css": "./dist/theme.css",
|
|
39
43
|
"./themes/*.css": "./dist/themes/*.css",
|
|
40
44
|
"./package.json": "./package.json"
|
|
@@ -77,12 +81,14 @@
|
|
|
77
81
|
"dependencies": {
|
|
78
82
|
"@fontsource-variable/inter": "^5.2.8",
|
|
79
83
|
"@hbsnow/rehype-sectionize": "^1.0.7",
|
|
80
|
-
"@lucide/svelte": "^1.
|
|
84
|
+
"@lucide/svelte": "^1.24.0",
|
|
81
85
|
"@shikijs/transformers": "^3.3.0",
|
|
82
86
|
"@tailwindcss/typography": "^0.5.16",
|
|
83
87
|
"bits-ui": "^2.18.1",
|
|
84
88
|
"clsx": "^2.1.1",
|
|
85
89
|
"esm-env": "^1.2.2",
|
|
90
|
+
"flexsearch": "^0.8.212",
|
|
91
|
+
"github-slugger": "^2.0.0",
|
|
86
92
|
"js-yaml": "^4.1.0",
|
|
87
93
|
"mdsvex": "^0.12.3",
|
|
88
94
|
"mode-watcher": "^1.0.2",
|