svelte-docsmith 0.9.0 → 0.10.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 (45) hide show
  1. package/bin/svelte-docsmith.mjs +161 -0
  2. package/dist/buildtime/archive.d.ts +31 -0
  3. package/dist/buildtime/archive.js +50 -0
  4. package/dist/buildtime/markdown-source.d.ts +31 -0
  5. package/dist/buildtime/markdown-source.js +90 -0
  6. package/dist/buildtime/vite/collect.d.ts +4 -3
  7. package/dist/buildtime/vite/collect.js +31 -12
  8. package/dist/buildtime/vite/extract.js +9 -35
  9. package/dist/buildtime/vite/frontmatter.js +4 -3
  10. package/dist/buildtime/vite/index.d.ts +9 -0
  11. package/dist/buildtime/vite/index.js +11 -4
  12. package/dist/components/chrome/copy-button.svelte +3 -2
  13. package/dist/components/chrome/search.svelte +16 -4
  14. package/dist/components/chrome/search.svelte.d.ts +4 -1
  15. package/dist/components/chrome/version-banner.svelte +82 -0
  16. package/dist/components/chrome/version-banner.svelte.d.ts +10 -0
  17. package/dist/components/chrome/version-switcher.svelte +63 -0
  18. package/dist/components/chrome/version-switcher.svelte.d.ts +14 -0
  19. package/dist/components/layouts/docs-header.svelte +26 -3
  20. package/dist/components/layouts/docs-header.svelte.d.ts +9 -1
  21. package/dist/components/layouts/docs-mobile-header.svelte +25 -2
  22. package/dist/components/layouts/docs-mobile-header.svelte.d.ts +9 -1
  23. package/dist/components/layouts/docs-shell.svelte +84 -66
  24. package/dist/components/layouts/docs-shell.svelte.d.ts +11 -2
  25. package/dist/components/layouts/error-page.svelte +23 -3
  26. package/dist/components/layouts/error-page.svelte.d.ts +8 -2
  27. package/dist/components/layouts/seo-head.svelte +8 -1
  28. package/dist/components/layouts/seo-head.svelte.d.ts +2 -0
  29. package/dist/core/content.d.ts +9 -0
  30. package/dist/core/docs-page.d.ts +70 -0
  31. package/dist/core/docs-page.js +55 -0
  32. package/dist/core/index.d.ts +2 -0
  33. package/dist/core/index.js +2 -0
  34. package/dist/core/version.d.ts +80 -0
  35. package/dist/core/version.js +85 -0
  36. package/dist/fallbacks/content.d.ts +2 -0
  37. package/dist/fallbacks/content.js +1 -0
  38. package/dist/index.d.ts +1 -0
  39. package/dist/index.js +5 -0
  40. package/dist/search/context.svelte.d.ts +2 -0
  41. package/dist/search/context.svelte.js +2 -0
  42. package/dist/search/create-search.d.ts +8 -2
  43. package/dist/search/create-search.js +11 -3
  44. package/dist/theme.css +21 -0
  45. package/package.json +7 -2
@@ -0,0 +1,85 @@
1
+ import { navFromContent, flattenNav } from './nav.js';
2
+ import { normalizePath } from '../utils/normalize-path.js';
3
+ /** True when `pathname` is `base` or sits beneath it on a segment boundary. */
4
+ function underBase(pathname, base) {
5
+ return pathname === base || pathname.startsWith(base + '/');
6
+ }
7
+ /**
8
+ * Resolve author versions against the docs URL base (e.g. `/docs`) and the
9
+ * collected content: compute each version's `basePath` and its `landing` (first
10
+ * page in sidebar reading order). Emitted as the `versions` manifest in
11
+ * `svelte-docsmith/content`, current first, then archives in declared order.
12
+ *
13
+ * Returns an empty manifest for an unversioned site, which makes every
14
+ * downstream scoping, switcher and banner step a no-op.
15
+ */
16
+ export function resolveVersions(versions, docsBase, content) {
17
+ if (!versions)
18
+ return [];
19
+ const base = normalizePath(docsBase);
20
+ const resolve = (version, basePath, current) => {
21
+ const items = content.filter((item) => item.version === version.id);
22
+ return {
23
+ ...version,
24
+ basePath,
25
+ landing: flattenNav(navFromContent(items))[0]?.url ?? basePath,
26
+ current,
27
+ noindex: version.noindex ?? false
28
+ };
29
+ };
30
+ return [
31
+ resolve(versions.current, base, true),
32
+ ...(versions.archived ?? []).map((version) => resolve(version, normalizePath(base + '/' + version.id), false))
33
+ ];
34
+ }
35
+ /**
36
+ * The resolved version owning `pathname`, by longest matching `basePath`. An
37
+ * archive's base is longer than the current version's, so `/docs/v1/x` picks
38
+ * `v1` while `/docs/x` falls to the current version. `undefined` off the docs
39
+ * tree entirely, or on an unversioned site.
40
+ */
41
+ export function activeVersion(versions, pathname) {
42
+ const path = normalizePath(pathname);
43
+ let best;
44
+ for (const v of versions) {
45
+ if (underBase(path, v.basePath) && (!best || v.basePath.length > best.basePath.length)) {
46
+ best = v;
47
+ }
48
+ }
49
+ return best;
50
+ }
51
+ /** The current version: the docs for the latest release. */
52
+ export function currentVersion(versions) {
53
+ return versions.find((v) => v.current);
54
+ }
55
+ /**
56
+ * Keep only the current version's pages, for `sitemap.xml` and `llms.txt`, so
57
+ * search engines and LLMs index one canonical set. A no-op when the site
58
+ * declares no versions. Works over any record carrying `version`.
59
+ */
60
+ export function currentOnly(items, versions) {
61
+ const current = currentVersion(versions);
62
+ return current ? items.filter((item) => item.version === current.id) : items;
63
+ }
64
+ /**
65
+ * Content scoped to one version. With no version id (unversioned site) the
66
+ * content passes through unchanged, so every caller stays a no-op off the
67
+ * versioned path.
68
+ */
69
+ export function scopeContent(content, versionId) {
70
+ if (!versionId)
71
+ return content;
72
+ return content.filter((item) => item.version === versionId);
73
+ }
74
+ /**
75
+ * Map the current page to its equivalent under `to`: swap the version base, and
76
+ * fall back to the target's landing page when that doc doesn't exist there.
77
+ * `targetPaths` is the set of the target version's page URLs.
78
+ */
79
+ export function mapPathToVersion(pathname, from, to, targetPaths) {
80
+ const path = normalizePath(pathname);
81
+ const rel = underBase(path, from.basePath) ? path.slice(from.basePath.length) : '';
82
+ const candidate = normalizePath(to.basePath + rel);
83
+ const set = targetPaths instanceof Set ? targetPaths : new Set(targetPaths);
84
+ return set.has(candidate) ? candidate : to.landing;
85
+ }
@@ -7,4 +7,6 @@
7
7
  * {@link missingPluginError}.
8
8
  */
9
9
  import type { DocsContentItem } from '../core/content.js';
10
+ import type { ResolvedVersion } from '../core/version.js';
10
11
  export declare const docs: DocsContentItem[];
12
+ export declare const versions: ResolvedVersion[];
@@ -1,3 +1,4 @@
1
1
  import { missingPluginError } from './missing-plugin.js';
2
2
  export const docs = [];
3
+ export const versions = [];
3
4
  throw missingPluginError('content');
package/dist/index.d.ts CHANGED
@@ -24,6 +24,7 @@ export { default as Feature } from './components/landing/feature.svelte';
24
24
  export { default as CTA } from './components/landing/cta.svelte';
25
25
  export { default as Action } from './components/landing/action.svelte';
26
26
  export { defineConfig, type DocsmithConfig, type DocsContentItem, type SearchDoc, type LlmsDoc } from './core/index.js';
27
+ export { currentOnly, type DocsVersion, type DocsVersions, type ResolvedVersion } from './core/index.js';
27
28
  export { createSearchEngine, type SearchEngine, type SearchResult } from './search/create-search.js';
28
29
  export { generateSitemap, type SitemapEntry } from './generate/sitemap.js';
29
30
  export { generateLlmsTxt, generateLlmsFullTxt, type LlmsSite } from './generate/llms.js';
package/dist/index.js CHANGED
@@ -31,6 +31,11 @@ export { default as CTA } from './components/landing/cta.svelte';
31
31
  export { default as Action } from './components/landing/action.svelte';
32
32
  // Config
33
33
  export { defineConfig } from './core/index.js';
34
+ // Versioned docs. Declare `versions` in the `docsmith()` vite plugin; the
35
+ // resolved manifest is exported from `svelte-docsmith/content` and passed to
36
+ // `DocsShell`. `currentOnly` scopes sitemap.xml / llms.txt to the current
37
+ // version, so archived releases don't compete with it in search results.
38
+ export { currentOnly } from './core/index.js';
34
39
  // Search engine (framework-agnostic; for building a custom search UI over the
35
40
  // generated `svelte-docsmith/search` index)
36
41
  export { createSearchEngine } from './search/create-search.js';
@@ -1,5 +1,7 @@
1
1
  export declare class SearchState {
2
2
  open: boolean;
3
+ /** Active version id on a versioned site; scopes results. `undefined` otherwise. */
4
+ version: string | undefined;
3
5
  }
4
6
  /** Create the search state and publish it on context. Call in `DocsShell`. */
5
7
  export declare function createSearchState(): SearchState;
@@ -7,6 +7,8 @@ import { getContext, setContext } from 'svelte';
7
7
  const KEY = Symbol('docsmith-search');
8
8
  export class SearchState {
9
9
  open = $state(false);
10
+ /** Active version id on a versioned site; scopes results. `undefined` otherwise. */
11
+ version = $state(undefined);
10
12
  }
11
13
  /** Create the search state and publish it on context. Call in `DocsShell`. */
12
14
  export function createSearchState() {
@@ -5,10 +5,16 @@ export type SearchResult = {
5
5
  title: string;
6
6
  section?: string;
7
7
  snippet: string;
8
+ /** The page's version id, so a versioned site can scope results. */
9
+ version?: string;
8
10
  };
9
11
  export type SearchEngine = {
10
- /** Ranked results for `query`, at most `limit` (default 8). */
11
- search(query: string, limit?: number): SearchResult[];
12
+ /**
13
+ * Ranked results for `query`, at most `limit` (default 8). Pass `version` to
14
+ * return only that version's pages (over-fetches internally so the limit is
15
+ * still met after filtering).
16
+ */
17
+ search(query: string, limit?: number, version?: string): SearchResult[];
12
18
  };
13
19
  /** Build a search engine over `docs`. Indexing is synchronous and one-time. */
14
20
  export declare function createSearchEngine(docs: SearchDoc[]): SearchEngine;
@@ -32,23 +32,31 @@ export function createSearchEngine(docs) {
32
32
  });
33
33
  });
34
34
  return {
35
- search(query, limit = 8) {
35
+ search(query, limit = 8, version) {
36
36
  const trimmed = query.trim();
37
37
  if (!trimmed)
38
38
  return [];
39
+ // When scoping to a version, over-fetch so the limit still holds after
40
+ // filtering out the other versions' hits.
41
+ const fetch = version ? limit * 5 : limit;
39
42
  // `merge: true` returns unified, unique ids ranked across fields.
40
- const hits = index.search(trimmed, { limit, merge: true });
43
+ const hits = index.search(trimmed, { limit: fetch, merge: true });
41
44
  const results = [];
42
45
  for (const { id } of hits) {
43
46
  const doc = docs[id];
44
47
  if (!doc)
45
48
  continue;
49
+ if (version && doc.version !== version)
50
+ continue;
46
51
  results.push({
47
52
  path: doc.path,
48
53
  title: doc.title,
49
54
  section: doc.section,
50
- snippet: buildSnippet(doc.text || doc.description || '', trimmed)
55
+ snippet: buildSnippet(doc.text || doc.description || '', trimmed),
56
+ version: doc.version
51
57
  });
58
+ if (results.length >= limit)
59
+ break;
52
60
  }
53
61
  return results;
54
62
  }
package/dist/theme.css CHANGED
@@ -210,6 +210,27 @@
210
210
  }
211
211
  }
212
212
 
213
+ /*
214
+ * Reduced motion. When a reader asks the OS to minimise motion, neutralise the
215
+ * framework's movement in one place: the overlay slide/scale (dialog, sheet,
216
+ * popover, dropdown) and accordion height animations come from tw-animate-css,
217
+ * which ships no guard of its own, and those overlays portal to <body> — outside
218
+ * any shell wrapper — so the rule has to be global to reach them. Durations
219
+ * collapse to ~0 rather than `none` so the animation/transition *end* events
220
+ * bits-ui relies on to mount and unmount still fire. Opacity-only fades are the
221
+ * accessible alternative to motion and are intentionally left to play.
222
+ */
223
+ @media (prefers-reduced-motion: reduce) {
224
+ *,
225
+ *::before,
226
+ *::after {
227
+ animation-duration: 0.01ms !important;
228
+ animation-iteration-count: 1 !important;
229
+ transition-duration: 0.01ms !important;
230
+ scroll-behavior: auto !important;
231
+ }
232
+ }
233
+
213
234
  /*
214
235
  * Shiki dual-theme output: light colors are inline, dark colors ship as
215
236
  * --shiki-dark/--shiki-dark-bg variables. Flip to them under `.dark`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-docsmith",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "A framework for building beautiful documentation sites with Svelte.",
5
5
  "author": {
6
6
  "name": "George Daskalakis",
@@ -8,10 +8,15 @@
8
8
  "url": "https://github.com/geodask"
9
9
  },
10
10
  "main": "dist/index.js",
11
+ "bin": {
12
+ "svelte-docsmith": "./bin/svelte-docsmith.mjs"
13
+ },
11
14
  "files": [
12
15
  "dist",
16
+ "bin",
13
17
  "!dist/**/*.test.*",
14
- "!dist/**/*.spec.*"
18
+ "!dist/**/*.spec.*",
19
+ "!dist/**/_fixtures/**"
15
20
  ],
16
21
  "sideEffects": false,
17
22
  "svelte": "./dist/index.js",