svedocs 0.1.0-beta.10

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 (286) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +85 -0
  3. package/dist/ai/openai-compatible.d.ts +13 -0
  4. package/dist/ai/openai-compatible.d.ts.map +1 -0
  5. package/dist/ai/openai-compatible.js +92 -0
  6. package/dist/ai/providers.d.ts +20 -0
  7. package/dist/ai/providers.d.ts.map +1 -0
  8. package/dist/ai/providers.js +185 -0
  9. package/dist/ai/rate-limit.d.ts +18 -0
  10. package/dist/ai/rate-limit.d.ts.map +1 -0
  11. package/dist/ai/rate-limit.js +52 -0
  12. package/dist/ai/response.d.ts +4 -0
  13. package/dist/ai/response.d.ts.map +1 -0
  14. package/dist/ai/response.js +151 -0
  15. package/dist/ai/runtime.d.ts +32 -0
  16. package/dist/ai/runtime.d.ts.map +1 -0
  17. package/dist/ai/runtime.js +111 -0
  18. package/dist/ai/types.d.ts +93 -0
  19. package/dist/ai/types.d.ts.map +1 -0
  20. package/dist/ai/types.js +1 -0
  21. package/dist/ai.d.ts +7 -0
  22. package/dist/ai.d.ts.map +1 -0
  23. package/dist/ai.js +6 -0
  24. package/dist/ambient.d.ts +5 -0
  25. package/dist/cloudflare.d.ts +29 -0
  26. package/dist/cloudflare.d.ts.map +1 -0
  27. package/dist/cloudflare.js +149 -0
  28. package/dist/config.d.ts +373 -0
  29. package/dist/config.d.ts.map +1 -0
  30. package/dist/config.js +299 -0
  31. package/dist/core/checks.d.ts +4 -0
  32. package/dist/core/checks.d.ts.map +1 -0
  33. package/dist/core/checks.js +267 -0
  34. package/dist/core/config.d.ts +6 -0
  35. package/dist/core/config.d.ts.map +1 -0
  36. package/dist/core/config.js +178 -0
  37. package/dist/core/content.d.ts +7 -0
  38. package/dist/core/content.d.ts.map +1 -0
  39. package/dist/core/content.js +340 -0
  40. package/dist/core/i18n.d.ts +5 -0
  41. package/dist/core/i18n.d.ts.map +1 -0
  42. package/dist/core/i18n.js +117 -0
  43. package/dist/core/links.d.ts +5 -0
  44. package/dist/core/links.d.ts.map +1 -0
  45. package/dist/core/links.js +60 -0
  46. package/dist/core/messages.d.ts +149 -0
  47. package/dist/core/messages.d.ts.map +1 -0
  48. package/dist/core/messages.js +163 -0
  49. package/dist/core/navigation.d.ts +5 -0
  50. package/dist/core/navigation.d.ts.map +1 -0
  51. package/dist/core/navigation.js +144 -0
  52. package/dist/core/routes.d.ts +36 -0
  53. package/dist/core/routes.d.ts.map +1 -0
  54. package/dist/core/routes.js +152 -0
  55. package/dist/core/search.d.ts +5 -0
  56. package/dist/core/search.d.ts.map +1 -0
  57. package/dist/core/search.js +41 -0
  58. package/dist/core/types.d.ts +468 -0
  59. package/dist/core/types.d.ts.map +1 -0
  60. package/dist/core/types.js +4 -0
  61. package/dist/core/utils.d.ts +13 -0
  62. package/dist/core/utils.d.ts.map +1 -0
  63. package/dist/core/utils.js +60 -0
  64. package/dist/core.d.ts +9 -0
  65. package/dist/core.d.ts.map +1 -0
  66. package/dist/core.js +8 -0
  67. package/dist/index.d.ts +6 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +3 -0
  70. package/dist/mdx/ast.d.ts +32 -0
  71. package/dist/mdx/ast.d.ts.map +1 -0
  72. package/dist/mdx/ast.js +148 -0
  73. package/dist/mdx/code.d.ts +17 -0
  74. package/dist/mdx/code.d.ts.map +1 -0
  75. package/dist/mdx/code.js +327 -0
  76. package/dist/mdx/compile.d.ts +29 -0
  77. package/dist/mdx/compile.d.ts.map +1 -0
  78. package/dist/mdx/compile.js +59 -0
  79. package/dist/mdx/diff.d.ts +9 -0
  80. package/dist/mdx/diff.d.ts.map +1 -0
  81. package/dist/mdx/diff.js +171 -0
  82. package/dist/mdx/headings.d.ts +4 -0
  83. package/dist/mdx/headings.d.ts.map +1 -0
  84. package/dist/mdx/headings.js +42 -0
  85. package/dist/mdx/links.d.ts +4 -0
  86. package/dist/mdx/links.d.ts.map +1 -0
  87. package/dist/mdx/links.js +144 -0
  88. package/dist/mdx/utils.d.ts +10 -0
  89. package/dist/mdx/utils.d.ts.map +1 -0
  90. package/dist/mdx/utils.js +64 -0
  91. package/dist/og/image.d.ts +25 -0
  92. package/dist/og/image.d.ts.map +1 -0
  93. package/dist/og/image.js +224 -0
  94. package/dist/og/metadata.d.ts +7 -0
  95. package/dist/og/metadata.d.ts.map +1 -0
  96. package/dist/og/metadata.js +198 -0
  97. package/dist/og/response.d.ts +4 -0
  98. package/dist/og/response.d.ts.map +1 -0
  99. package/dist/og/response.js +49 -0
  100. package/dist/og/rss.d.ts +4 -0
  101. package/dist/og/rss.d.ts.map +1 -0
  102. package/dist/og/rss.js +96 -0
  103. package/dist/og/sitemap.d.ts +7 -0
  104. package/dist/og/sitemap.d.ts.map +1 -0
  105. package/dist/og/sitemap.js +90 -0
  106. package/dist/og/types.d.ts +65 -0
  107. package/dist/og/types.d.ts.map +1 -0
  108. package/dist/og/types.js +1 -0
  109. package/dist/og.d.ts +6 -0
  110. package/dist/og.d.ts.map +1 -0
  111. package/dist/og.js +5 -0
  112. package/dist/routes.d.ts +2 -0
  113. package/dist/routes.d.ts.map +1 -0
  114. package/dist/routes.js +1 -0
  115. package/dist/search/algolia.d.ts +10 -0
  116. package/dist/search/algolia.d.ts.map +1 -0
  117. package/dist/search/algolia.js +82 -0
  118. package/dist/search/cloudflare-sync.d.ts +5 -0
  119. package/dist/search/cloudflare-sync.d.ts.map +1 -0
  120. package/dist/search/cloudflare-sync.js +169 -0
  121. package/dist/search/cloudflare.d.ts +11 -0
  122. package/dist/search/cloudflare.d.ts.map +1 -0
  123. package/dist/search/cloudflare.js +126 -0
  124. package/dist/search/local.d.ts +9 -0
  125. package/dist/search/local.d.ts.map +1 -0
  126. package/dist/search/local.js +165 -0
  127. package/dist/search/runtime.d.ts +41 -0
  128. package/dist/search/runtime.d.ts.map +1 -0
  129. package/dist/search/runtime.js +152 -0
  130. package/dist/search/types.d.ts +172 -0
  131. package/dist/search/types.d.ts.map +1 -0
  132. package/dist/search/types.js +1 -0
  133. package/dist/search/typesense.d.ts +11 -0
  134. package/dist/search/typesense.d.ts.map +1 -0
  135. package/dist/search/typesense.js +91 -0
  136. package/dist/search/utils.d.ts +11 -0
  137. package/dist/search/utils.d.ts.map +1 -0
  138. package/dist/search/utils.js +93 -0
  139. package/dist/search.d.ts +8 -0
  140. package/dist/search.d.ts.map +1 -0
  141. package/dist/search.js +7 -0
  142. package/dist/svelte.d.ts +27 -0
  143. package/dist/svelte.d.ts.map +1 -0
  144. package/dist/svelte.js +45 -0
  145. package/dist/testing.d.ts +3 -0
  146. package/dist/testing.d.ts.map +1 -0
  147. package/dist/testing.js +23 -0
  148. package/dist/theme/Article.svelte +145 -0
  149. package/dist/theme/Article.svelte.d.ts +36 -0
  150. package/dist/theme/Article.svelte.d.ts.map +1 -0
  151. package/dist/theme/AskAiPanel.svelte +241 -0
  152. package/dist/theme/AskAiPanel.svelte.d.ts +31 -0
  153. package/dist/theme/AskAiPanel.svelte.d.ts.map +1 -0
  154. package/dist/theme/Brand.svelte +19 -0
  155. package/dist/theme/Brand.svelte.d.ts +22 -0
  156. package/dist/theme/Brand.svelte.d.ts.map +1 -0
  157. package/dist/theme/Button.svelte +34 -0
  158. package/dist/theme/Button.svelte.d.ts +41 -0
  159. package/dist/theme/Button.svelte.d.ts.map +1 -0
  160. package/dist/theme/Checkbox.svelte +63 -0
  161. package/dist/theme/Checkbox.svelte.d.ts +45 -0
  162. package/dist/theme/Checkbox.svelte.d.ts.map +1 -0
  163. package/dist/theme/CodeBlock.svelte +6 -0
  164. package/dist/theme/CodeBlock.svelte.d.ts +22 -0
  165. package/dist/theme/CodeBlock.svelte.d.ts.map +1 -0
  166. package/dist/theme/DocPage.svelte +15 -0
  167. package/dist/theme/DocPage.svelte.d.ts +33 -0
  168. package/dist/theme/DocPage.svelte.d.ts.map +1 -0
  169. package/dist/theme/DocsApp.svelte +194 -0
  170. package/dist/theme/DocsApp.svelte.d.ts +60 -0
  171. package/dist/theme/DocsApp.svelte.d.ts.map +1 -0
  172. package/dist/theme/DocsLayout.svelte +99 -0
  173. package/dist/theme/DocsLayout.svelte.d.ts +39 -0
  174. package/dist/theme/DocsLayout.svelte.d.ts.map +1 -0
  175. package/dist/theme/DocsShell.svelte +91 -0
  176. package/dist/theme/DocsShell.svelte.d.ts +34 -0
  177. package/dist/theme/DocsShell.svelte.d.ts.map +1 -0
  178. package/dist/theme/ErrorPage.svelte +113 -0
  179. package/dist/theme/ErrorPage.svelte.d.ts +34 -0
  180. package/dist/theme/ErrorPage.svelte.d.ts.map +1 -0
  181. package/dist/theme/FloatingToolbar.svelte +8 -0
  182. package/dist/theme/FloatingToolbar.svelte.d.ts +22 -0
  183. package/dist/theme/FloatingToolbar.svelte.d.ts.map +1 -0
  184. package/dist/theme/Footer.svelte +18 -0
  185. package/dist/theme/Footer.svelte.d.ts +24 -0
  186. package/dist/theme/Footer.svelte.d.ts.map +1 -0
  187. package/dist/theme/FooterLinks.svelte +37 -0
  188. package/dist/theme/FooterLinks.svelte.d.ts +22 -0
  189. package/dist/theme/FooterLinks.svelte.d.ts.map +1 -0
  190. package/dist/theme/FormField.svelte +41 -0
  191. package/dist/theme/FormField.svelte.d.ts +35 -0
  192. package/dist/theme/FormField.svelte.d.ts.map +1 -0
  193. package/dist/theme/HomePage.svelte +284 -0
  194. package/dist/theme/HomePage.svelte.d.ts +65 -0
  195. package/dist/theme/HomePage.svelte.d.ts.map +1 -0
  196. package/dist/theme/Input.svelte +36 -0
  197. package/dist/theme/Input.svelte.d.ts +34 -0
  198. package/dist/theme/Input.svelte.d.ts.map +1 -0
  199. package/dist/theme/LayoutShell.svelte +120 -0
  200. package/dist/theme/LayoutShell.svelte.d.ts +43 -0
  201. package/dist/theme/LayoutShell.svelte.d.ts.map +1 -0
  202. package/dist/theme/LocalizedLink.svelte +21 -0
  203. package/dist/theme/LocalizedLink.svelte.d.ts +38 -0
  204. package/dist/theme/LocalizedLink.svelte.d.ts.map +1 -0
  205. package/dist/theme/MobileNav.svelte +20 -0
  206. package/dist/theme/MobileNav.svelte.d.ts +26 -0
  207. package/dist/theme/MobileNav.svelte.d.ts.map +1 -0
  208. package/dist/theme/Navbar.svelte +69 -0
  209. package/dist/theme/Navbar.svelte.d.ts +29 -0
  210. package/dist/theme/Navbar.svelte.d.ts.map +1 -0
  211. package/dist/theme/PageLayout.svelte +49 -0
  212. package/dist/theme/PageLayout.svelte.d.ts +34 -0
  213. package/dist/theme/PageLayout.svelte.d.ts.map +1 -0
  214. package/dist/theme/PageShell.svelte +98 -0
  215. package/dist/theme/PageShell.svelte.d.ts +35 -0
  216. package/dist/theme/PageShell.svelte.d.ts.map +1 -0
  217. package/dist/theme/PageTools.svelte +87 -0
  218. package/dist/theme/PageTools.svelte.d.ts +25 -0
  219. package/dist/theme/PageTools.svelte.d.ts.map +1 -0
  220. package/dist/theme/RenderError.svelte +62 -0
  221. package/dist/theme/RenderError.svelte.d.ts +31 -0
  222. package/dist/theme/RenderError.svelte.d.ts.map +1 -0
  223. package/dist/theme/RootLayout.svelte +274 -0
  224. package/dist/theme/RootLayout.svelte.d.ts +47 -0
  225. package/dist/theme/RootLayout.svelte.d.ts.map +1 -0
  226. package/dist/theme/RouteRenderError.svelte +53 -0
  227. package/dist/theme/RouteRenderError.svelte.d.ts +34 -0
  228. package/dist/theme/RouteRenderError.svelte.d.ts.map +1 -0
  229. package/dist/theme/SafeRenderError.svelte +49 -0
  230. package/dist/theme/SafeRenderError.svelte.d.ts +33 -0
  231. package/dist/theme/SafeRenderError.svelte.d.ts.map +1 -0
  232. package/dist/theme/ScopeSwitcher.svelte +107 -0
  233. package/dist/theme/ScopeSwitcher.svelte.d.ts +26 -0
  234. package/dist/theme/ScopeSwitcher.svelte.d.ts.map +1 -0
  235. package/dist/theme/SearchDialog.svelte +200 -0
  236. package/dist/theme/SearchDialog.svelte.d.ts +31 -0
  237. package/dist/theme/SearchDialog.svelte.d.ts.map +1 -0
  238. package/dist/theme/Select.svelte +37 -0
  239. package/dist/theme/Select.svelte.d.ts +42 -0
  240. package/dist/theme/Select.svelte.d.ts.map +1 -0
  241. package/dist/theme/SidebarTree.svelte +148 -0
  242. package/dist/theme/SidebarTree.svelte.d.ts +24 -0
  243. package/dist/theme/SidebarTree.svelte.d.ts.map +1 -0
  244. package/dist/theme/SocialNav.svelte +17 -0
  245. package/dist/theme/SocialNav.svelte.d.ts +22 -0
  246. package/dist/theme/SocialNav.svelte.d.ts.map +1 -0
  247. package/dist/theme/TableOfContents.svelte +73 -0
  248. package/dist/theme/TableOfContents.svelte.d.ts +25 -0
  249. package/dist/theme/TableOfContents.svelte.d.ts.map +1 -0
  250. package/dist/theme/Textarea.svelte +33 -0
  251. package/dist/theme/Textarea.svelte.d.ts +33 -0
  252. package/dist/theme/Textarea.svelte.d.ts.map +1 -0
  253. package/dist/theme/ThemeInit.svelte +16 -0
  254. package/dist/theme/ThemeInit.svelte.d.ts +23 -0
  255. package/dist/theme/ThemeInit.svelte.d.ts.map +1 -0
  256. package/dist/theme/ThemeToggle.svelte +37 -0
  257. package/dist/theme/ThemeToggle.svelte.d.ts +23 -0
  258. package/dist/theme/ThemeToggle.svelte.d.ts.map +1 -0
  259. package/dist/theme/TopNav.svelte +23 -0
  260. package/dist/theme/TopNav.svelte.d.ts +22 -0
  261. package/dist/theme/TopNav.svelte.d.ts.map +1 -0
  262. package/dist/theme/base.css +240 -0
  263. package/dist/theme/defaults.d.ts +4 -0
  264. package/dist/theme/defaults.d.ts.map +1 -0
  265. package/dist/theme/defaults.js +55 -0
  266. package/dist/theme/forms.d.ts +4 -0
  267. package/dist/theme/forms.d.ts.map +1 -0
  268. package/dist/theme/forms.js +3 -0
  269. package/dist/theme/headless.d.ts +62 -0
  270. package/dist/theme/headless.d.ts.map +1 -0
  271. package/dist/theme/headless.js +1064 -0
  272. package/dist/theme/index.d.ts +41 -0
  273. package/dist/theme/index.d.ts.map +1 -0
  274. package/dist/theme/index.js +38 -0
  275. package/dist/theme/portal.d.ts +6 -0
  276. package/dist/theme/portal.d.ts.map +1 -0
  277. package/dist/theme/portal.js +59 -0
  278. package/dist/theme/styles.css +4232 -0
  279. package/dist/theme/types.d.ts +363 -0
  280. package/dist/theme/types.d.ts.map +1 -0
  281. package/dist/theme/types.js +1 -0
  282. package/dist/virtual.d.ts +65 -0
  283. package/dist/vite.d.ts +16 -0
  284. package/dist/vite.d.ts.map +1 -0
  285. package/dist/vite.js +386 -0
  286. package/package.json +132 -0
@@ -0,0 +1,1064 @@
1
+ import { tick } from 'svelte';
2
+ import { derived, get, writable } from 'svelte/store';
3
+ import { defaultSvedocsMessages } from '../core/config.js';
4
+ import { resolveSvedocsHref } from '../core/routes.js';
5
+ import { filterSearchRecords, searchRecords } from '../search/local.js';
6
+ const accentPalette = {
7
+ emerald: '#007f68',
8
+ teal: '#087f8c',
9
+ sky: '#0969da',
10
+ indigo: '#4f46e5',
11
+ rose: '#c83e4d',
12
+ amber: '#b36b00'
13
+ };
14
+ const copyIconSvg = '<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 5h9v14H9zM6 8v12h10"/></svg>';
15
+ const checkIconSvg = '<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l4 4 10-10"/></svg>';
16
+ export function createThemeContext(input) {
17
+ const page = input.page;
18
+ const localeCode = input.localeCode ?? page?.locale ?? input.config.i18n.defaultLocale ?? 'en';
19
+ const locale = input.config.i18n.locales.find((candidate) => candidate.code === localeCode);
20
+ const languageTag = locale?.hreflang ?? localeCode;
21
+ const messages = resolveMessages(input.config, localeCode);
22
+ const t = createTranslate(messages);
23
+ const activeNavHref = resolveLocalizedActiveNavHref(input.config.theme.nav, page?.routePath ?? '/', input.config, input.pages ?? [], localeCode);
24
+ return {
25
+ config: input.config,
26
+ ...(page ? { page } : {}),
27
+ pages: input.pages ?? [],
28
+ tree: input.tree ?? [],
29
+ search: input.search ?? [],
30
+ ...(input.loadSearch ? { loadSearch: input.loadSearch } : {}),
31
+ searchScope: createRuntimeScope(input.config.search.scope, page),
32
+ aiScope: createRuntimeScope(input.config.ai.scope, page),
33
+ surface: page?.frontmatter.layout === 'home' || page?.scopePath === '/' ? 'home' : 'reading',
34
+ isDocsPage: page?.kind === 'doc',
35
+ activeNavHref,
36
+ ...(locale ? { locale } : {}),
37
+ localeCode,
38
+ languageTag,
39
+ messages,
40
+ t
41
+ };
42
+ }
43
+ export function resolveMessages(config, localeCode) {
44
+ return config.i18n.messages[localeCode ?? config.i18n.defaultLocale ?? 'en']
45
+ ?? config.i18n.messages[config.i18n.defaultLocale ?? 'en']
46
+ ?? config.i18n.messages.en
47
+ ?? defaultSvedocsMessages;
48
+ }
49
+ export function createTranslate(messages) {
50
+ return (key, values) => formatMessage(messages[key] ?? defaultSvedocsMessages[key] ?? key, values);
51
+ }
52
+ export function formatMessage(message, values) {
53
+ if (!values)
54
+ return message;
55
+ return message.replace(/\{([A-Za-z0-9_.-]+)\}/g, (match, key) => {
56
+ const value = values[key];
57
+ return value === undefined ? match : String(value);
58
+ });
59
+ }
60
+ export function fallbackTranslate(key, values) {
61
+ return formatMessage(defaultSvedocsMessages[key] ?? key, values);
62
+ }
63
+ export function createRuntimeScope(mode, page) {
64
+ if (mode === 'all' || !page)
65
+ return {};
66
+ return {
67
+ ...(page.locale ? { locale: page.locale } : {})
68
+ };
69
+ }
70
+ export function createThemeStyle(config) {
71
+ const accent = resolveColor(config.theme.palette.accent, '#007f68');
72
+ const modeTokens = config.theme.defaultMode === 'dark'
73
+ ? [
74
+ '--sd-bg:#11130f',
75
+ '--sd-ink:#f4f1e8',
76
+ '--sd-muted:#aaa698',
77
+ '--sd-line:#2f332d',
78
+ '--sd-grid-line:color-mix(in srgb, var(--sd-line) 45%, transparent)',
79
+ '--sd-panel:#181b16',
80
+ '--sd-accent:#50d6b3',
81
+ '--sd-accent-2:#ff8a66',
82
+ '--sd-scrollbar-thumb:color-mix(in srgb, var(--sd-ink) 34%, transparent)',
83
+ '--sd-scrollbar-thumb-hover:color-mix(in srgb, var(--sd-accent) 70%, var(--sd-ink))'
84
+ ]
85
+ : config.theme.defaultMode === 'light'
86
+ ? [
87
+ '--sd-bg:#f8f7f2',
88
+ '--sd-ink:#161612',
89
+ '--sd-muted:#68675f',
90
+ '--sd-line:#dedbd0',
91
+ '--sd-grid-line:color-mix(in srgb, var(--sd-line) 42%, transparent)',
92
+ '--sd-panel:#fffdf7',
93
+ '--sd-accent:#007f68',
94
+ '--sd-accent-2:#d64735',
95
+ '--sd-scrollbar-thumb:color-mix(in srgb, var(--sd-ink) 30%, transparent)',
96
+ '--sd-scrollbar-thumb-hover:color-mix(in srgb, var(--sd-accent) 62%, var(--sd-ink))'
97
+ ]
98
+ : [];
99
+ return [
100
+ ...modeTokens,
101
+ `--font-sans:${config.theme.fonts.sans}`,
102
+ `--font-mono:${config.theme.fonts.mono}`,
103
+ `--sd-font-display:${config.theme.fonts.display}`,
104
+ `--sd-radius:${config.theme.radius}`,
105
+ `--sd-accent:${accent}`
106
+ ].join(';');
107
+ }
108
+ export function createThemeInitScript(defaultMode, languageTag = 'en', dir = 'ltr') {
109
+ if (defaultMode !== 'system')
110
+ return '';
111
+ return `<script>(function(){try{var d=${serializeInlineScriptValue(defaultMode)};var l=${serializeInlineScriptValue(languageTag)};var r=${serializeInlineScriptValue(dir)};var s;try{s=localStorage.getItem('svedocs-theme')}catch(e){}var p=s==='dark'||s==='light'||s==='system'?s:d;var m=typeof matchMedia==='function'&&matchMedia('(prefers-color-scheme: dark)').matches;var t=p==='system'?(m?'dark':'light'):p;var h=document.documentElement;h.dataset.theme=t;h.style.colorScheme=t;h.lang=l;h.dir=r;}catch(e){}})();<\/script>`;
112
+ }
113
+ function serializeInlineScriptValue(value) {
114
+ return JSON.stringify(value)
115
+ .replace(/</g, '\\u003c')
116
+ .replace(/\u2028/g, '\\u2028')
117
+ .replace(/\u2029/g, '\\u2029');
118
+ }
119
+ export function resolveColor(value, fallback) {
120
+ if (/^(#|rgb|hsl|oklch|color-mix)/.test(value))
121
+ return value;
122
+ return accentPalette[value] ?? fallback;
123
+ }
124
+ export function linkRel(item) {
125
+ return item.external ? 'noreferrer' : item.rel;
126
+ }
127
+ export function resolveLocalizedNavItem(item, context) {
128
+ const label = resolveLocalizedText(item.label, item.labelKey, context);
129
+ if (item.external)
130
+ return { ...item, label };
131
+ return {
132
+ ...item,
133
+ label,
134
+ href: resolveLocalizedHref(item.href, context)
135
+ };
136
+ }
137
+ export function resolveLocalizedText(fallback, key, context) {
138
+ return key && context.messages[key] !== undefined ? context.t(key) : fallback;
139
+ }
140
+ export function resolveLocalizedHref(href, context) {
141
+ return resolveLocalizedHrefForLocale(href, context.config, context.pages, context.localeCode);
142
+ }
143
+ export function resolveLocaleCodeFromPath(path, config) {
144
+ const segments = normalizePath(path).split('/').filter(Boolean);
145
+ const localePath = segments[0] === 'docs' ? segments[1] : segments[0];
146
+ const locale = config.i18n.locales.find((candidate) => candidate.path === decodePathSegment(localePath));
147
+ return locale?.code ?? config.i18n.defaultLocale ?? 'en';
148
+ }
149
+ function resolveLocalizedActiveNavHref(nav, currentPath, config, pages, localeCode) {
150
+ const localizedNav = nav.map((item) => (item.external
151
+ ? item
152
+ : {
153
+ ...item,
154
+ href: resolveLocalizedHrefForLocale(item.href, config, pages, localeCode)
155
+ }));
156
+ return resolveActiveNavHref(localizedNav, currentPath);
157
+ }
158
+ function resolveLocalizedHrefForLocale(href, config, pages, localeCode) {
159
+ return resolveSvedocsHref({ href, pages, config, localeCode }).href;
160
+ }
161
+ export function isActiveNavItem(item, activeNavHref) {
162
+ return !item.external && normalizePath(item.href) === activeNavHref;
163
+ }
164
+ export function handleSamePathNavClick(event, href, external) {
165
+ if (external)
166
+ return;
167
+ if (event.button !== 0)
168
+ return;
169
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
170
+ return;
171
+ if (typeof window === 'undefined')
172
+ return;
173
+ const target = createComparableUrl(href, window.location.href);
174
+ if (!target)
175
+ return;
176
+ const current = createComparableUrl(window.location.href, window.location.href);
177
+ if (target === current)
178
+ event.preventDefault();
179
+ }
180
+ export function createDomId(value) {
181
+ return value.toLowerCase().replace(/[^a-z0-9_-]+/g, '-').replace(/^-|-$/g, '') || 'site';
182
+ }
183
+ export function resolveActiveNavHref(nav, currentPath) {
184
+ const normalizedCurrentPath = normalizePath(currentPath);
185
+ let activeHref = '';
186
+ for (const item of nav) {
187
+ if (item.external)
188
+ continue;
189
+ const normalizedHref = normalizePath(item.href);
190
+ if (!isPathMatch(normalizedCurrentPath, normalizedHref))
191
+ continue;
192
+ if (normalizedHref.length > activeHref.length)
193
+ activeHref = normalizedHref;
194
+ }
195
+ return activeHref;
196
+ }
197
+ export function normalizePath(path) {
198
+ const pathname = getPathname(path);
199
+ const withoutTrailingSlash = pathname.replace(/\/+$/, '');
200
+ return withoutTrailingSlash ? withoutTrailingSlash : '/';
201
+ }
202
+ export function createSearchController(initial = {}) {
203
+ const options = writable(normalizeSearchOptions(initial));
204
+ const open = writable(false);
205
+ const query = writable('');
206
+ const activeIndex = writable(0);
207
+ const remoteResults = writable([]);
208
+ const remoteStatus = writable('idle');
209
+ const remoteError = writable('');
210
+ const loadedRecords = writable(initial.records ?? []);
211
+ const recordsStatus = writable((initial.records?.length ?? 0) > 0 ? 'ready' : 'idle');
212
+ let recordsRequest;
213
+ let recordsSource = initial.records;
214
+ let loadRecordsSource = initial.loadRecords;
215
+ let recordsVersion = 0;
216
+ let remoteKey = '';
217
+ let remoteRequestId = 0;
218
+ const usesRemoteSearch = derived(options, ($options) => isRemoteSearch($options));
219
+ const localResults = derived([query, loadedRecords, options], ([$query, $records, $options]) => ($query.trim()
220
+ ? searchRecords($records, { query: $query, limit: 8, ...$options.scope })
221
+ : createDefaultSearchResults($records, $options.scope)));
222
+ const results = derived([usesRemoteSearch, query, remoteStatus, remoteResults, localResults], ([$usesRemoteSearch, $query, $remoteStatus, $remoteResults, $localResults]) => ($usesRemoteSearch && $query.trim()
223
+ ? $remoteStatus === 'error' ? $localResults : $remoteResults
224
+ : $localResults));
225
+ results.subscribe((nextResults) => {
226
+ activeIndex.update((value) => Math.min(value, Math.max(nextResults.length - 1, 0)));
227
+ });
228
+ function setOptions(nextOptions) {
229
+ const recordsChanged = Object.hasOwn(nextOptions, 'records') && nextOptions.records !== recordsSource;
230
+ const loadRecordsChanged = Object.hasOwn(nextOptions, 'loadRecords') && nextOptions.loadRecords !== loadRecordsSource;
231
+ options.update((current) => normalizeSearchOptions({ ...current, ...nextOptions }));
232
+ if (loadRecordsChanged) {
233
+ loadRecordsSource = nextOptions.loadRecords;
234
+ recordsRequest = undefined;
235
+ recordsVersion += 1;
236
+ if (!recordsChanged) {
237
+ const nextRecords = recordsSource ?? [];
238
+ loadedRecords.set(nextRecords);
239
+ recordsStatus.set(nextRecords.length > 0 ? 'ready' : 'idle');
240
+ }
241
+ }
242
+ if (recordsChanged) {
243
+ recordsSource = nextOptions.records;
244
+ recordsRequest = undefined;
245
+ recordsVersion += 1;
246
+ const nextRecords = nextOptions.records ?? [];
247
+ loadedRecords.set(nextRecords);
248
+ recordsStatus.set(nextRecords.length > 0 ? 'ready' : 'idle');
249
+ }
250
+ syncRemoteResults();
251
+ }
252
+ function show() {
253
+ open.set(true);
254
+ void ensureRecords();
255
+ syncRemoteResults();
256
+ }
257
+ function hide() {
258
+ open.set(false);
259
+ setQuery('');
260
+ activeIndex.set(0);
261
+ }
262
+ function setQuery(value) {
263
+ query.set(value);
264
+ activeIndex.set(0);
265
+ if (!value.trim()) {
266
+ remoteResults.set([]);
267
+ remoteStatus.set('idle');
268
+ remoteError.set('');
269
+ remoteKey = '';
270
+ return;
271
+ }
272
+ syncRemoteResults();
273
+ }
274
+ function moveActive(delta) {
275
+ const nextResults = get(results);
276
+ activeIndex.update((value) => Math.min(Math.max(value + delta, 0), Math.max(nextResults.length - 1, 0)));
277
+ }
278
+ function activate(index) {
279
+ activeIndex.set(index);
280
+ }
281
+ function select(index = get(activeIndex)) {
282
+ const result = get(results)[index];
283
+ if (result)
284
+ hide();
285
+ return result;
286
+ }
287
+ async function ensureRecords() {
288
+ const currentRecords = get(loadedRecords);
289
+ const currentOptions = get(options);
290
+ if (currentRecords.length > 0 || !currentOptions.loadRecords)
291
+ return currentRecords;
292
+ if (!recordsRequest) {
293
+ const requestVersion = recordsVersion;
294
+ recordsStatus.set('loading');
295
+ recordsRequest = currentOptions.loadRecords()
296
+ .then((nextRecords) => {
297
+ if (requestVersion !== recordsVersion)
298
+ return get(loadedRecords);
299
+ loadedRecords.set(nextRecords);
300
+ recordsStatus.set('ready');
301
+ return nextRecords;
302
+ })
303
+ .catch((error) => {
304
+ if (requestVersion === recordsVersion) {
305
+ recordsStatus.set('error');
306
+ recordsRequest = undefined;
307
+ }
308
+ throw error;
309
+ });
310
+ }
311
+ try {
312
+ return await recordsRequest;
313
+ }
314
+ catch {
315
+ return get(loadedRecords);
316
+ }
317
+ }
318
+ async function loadRemoteResults(currentQuery, scope, requestId) {
319
+ const currentOptions = get(options);
320
+ remoteResults.set([]);
321
+ remoteStatus.set('loading');
322
+ remoteError.set('');
323
+ try {
324
+ const fetcher = currentOptions.fetcher ?? globalThis.fetch;
325
+ if (!fetcher)
326
+ throw new Error(currentOptions.t('search.fetchUnavailable'));
327
+ const response = await fetcher(createSearchUrl(currentOptions.endpoint, currentQuery, scope, currentOptions.provider, currentOptions.origin));
328
+ if (!response.ok)
329
+ throw new Error(currentOptions.t('search.requestError', { status: response.status }));
330
+ const payload = await response.json();
331
+ if (requestId !== remoteRequestId)
332
+ return;
333
+ remoteResults.set(payload.results ?? []);
334
+ remoteStatus.set('ready');
335
+ }
336
+ catch (error) {
337
+ if (requestId !== remoteRequestId)
338
+ return;
339
+ remoteResults.set([]);
340
+ remoteError.set(error instanceof Error ? error.message : currentOptions.t('search.failed'));
341
+ remoteStatus.set('error');
342
+ }
343
+ }
344
+ function syncRemoteResults() {
345
+ const currentOptions = get(options);
346
+ const currentQuery = get(query);
347
+ if (!get(open) || !currentQuery.trim() || !isRemoteSearch(currentOptions))
348
+ return;
349
+ const nextKey = createRemoteKey(currentOptions.provider, currentOptions.endpoint, currentQuery, currentOptions.scope);
350
+ if (nextKey === remoteKey)
351
+ return;
352
+ remoteKey = nextKey;
353
+ void loadRemoteResults(currentQuery, currentOptions.scope, ++remoteRequestId);
354
+ }
355
+ return {
356
+ open,
357
+ query,
358
+ activeIndex,
359
+ results,
360
+ remoteStatus,
361
+ remoteError,
362
+ recordsStatus,
363
+ setOptions,
364
+ show,
365
+ hide,
366
+ setQuery,
367
+ moveActive,
368
+ activate,
369
+ select,
370
+ ensureRecords
371
+ };
372
+ }
373
+ export function createAskAiController(initial) {
374
+ const options = writable(normalizeAskOptions(initial));
375
+ const open = writable(false);
376
+ const input = writable('');
377
+ const messages = writable([]);
378
+ const loading = writable(false);
379
+ const loadedRecords = writable(initial.records ?? []);
380
+ let recordsRequest;
381
+ let recordsSource = initial.records;
382
+ let loadRecordsSource = initial.loadRecords;
383
+ let recordsVersion = 0;
384
+ let messageId = 0;
385
+ function setOptions(nextOptions) {
386
+ const recordsChanged = Object.hasOwn(nextOptions, 'records') && nextOptions.records !== recordsSource;
387
+ const loadRecordsChanged = Object.hasOwn(nextOptions, 'loadRecords') && nextOptions.loadRecords !== loadRecordsSource;
388
+ options.update((current) => normalizeAskOptions({ ...current, ...nextOptions }));
389
+ if (loadRecordsChanged) {
390
+ loadRecordsSource = nextOptions.loadRecords;
391
+ recordsRequest = undefined;
392
+ recordsVersion += 1;
393
+ if (!recordsChanged)
394
+ loadedRecords.set(recordsSource ?? []);
395
+ }
396
+ if (recordsChanged) {
397
+ recordsSource = nextOptions.records;
398
+ recordsRequest = undefined;
399
+ recordsVersion += 1;
400
+ loadedRecords.set(nextOptions.records ?? []);
401
+ }
402
+ }
403
+ function nextId() {
404
+ return ++messageId;
405
+ }
406
+ function show() {
407
+ const currentOptions = get(options);
408
+ if (!currentOptions.config.ai.enabled || get(open))
409
+ return;
410
+ open.set(true);
411
+ const welcomeMessage = currentOptions.welcomeMessage ?? currentOptions.config.ai.welcomeMessage;
412
+ if (get(messages).length === 0 && welcomeMessage) {
413
+ messages.set([{ id: nextId(), role: 'assistant', content: welcomeMessage, welcome: true }]);
414
+ }
415
+ }
416
+ function hide() {
417
+ open.set(false);
418
+ }
419
+ function reset() {
420
+ const currentOptions = get(options);
421
+ const welcomeMessage = currentOptions.welcomeMessage ?? currentOptions.config.ai.welcomeMessage;
422
+ messages.set(welcomeMessage ? [{ id: nextId(), role: 'assistant', content: welcomeMessage, welcome: true }] : []);
423
+ input.set('');
424
+ }
425
+ function setInput(value) {
426
+ input.set(value);
427
+ }
428
+ async function send(text = get(input)) {
429
+ const value = text.trim();
430
+ if (!value || get(loading))
431
+ return;
432
+ const currentOptions = get(options);
433
+ input.set('');
434
+ const userMsg = { id: nextId(), role: 'user', content: value };
435
+ const assistantMsg = { id: nextId(), role: 'assistant', content: '', citations: [] };
436
+ messages.update((current) => [...current, userMsg, assistantMsg]);
437
+ loading.set(true);
438
+ const transcript = get(messages)
439
+ .filter((message) => !message.welcome && !(message.role === 'assistant' && message.id === assistantMsg.id))
440
+ .map((message) => ({ role: message.role, content: message.content }));
441
+ if (currentOptions.buildMode !== 'edge') {
442
+ const sourceRecords = await ensureRecords();
443
+ updateAssistant(assistantMsg.id, createLocalAskDraft(value, sourceRecords, currentOptions.scope, currentOptions.t));
444
+ loading.set(false);
445
+ return;
446
+ }
447
+ try {
448
+ const fetcher = currentOptions.fetcher ?? globalThis.fetch;
449
+ if (!fetcher)
450
+ throw new Error(currentOptions.t('ask.fetchUnavailable'));
451
+ const response = await fetcher(currentOptions.endpoint, {
452
+ method: 'POST',
453
+ headers: {
454
+ 'content-type': 'application/json',
455
+ accept: 'text/event-stream'
456
+ },
457
+ body: JSON.stringify({
458
+ question: value,
459
+ messages: transcript,
460
+ ...currentOptions.scope
461
+ })
462
+ });
463
+ if (response.headers.get('content-type')?.includes('text/event-stream')) {
464
+ await readAskStream(response, assistantMsg.id, currentOptions.t);
465
+ const streamedMessage = findMessage(assistantMsg.id);
466
+ if (!response.ok) {
467
+ throw new Error(streamedMessage?.error ?? currentOptions.t('ask.requestError', { status: response.status }));
468
+ }
469
+ if (streamedMessage?.error && !streamedMessage.content.trim())
470
+ throw new Error(streamedMessage.error);
471
+ if (!streamedMessage?.content.trim())
472
+ throw new Error(currentOptions.t('ask.failed'));
473
+ return;
474
+ }
475
+ if (!response.ok) {
476
+ const failure = await response.json().catch(() => ({}));
477
+ throw new Error(failure.error ?? currentOptions.t('ask.requestError', { status: response.status }));
478
+ }
479
+ const result = await response.json();
480
+ updateAssistant(assistantMsg.id, {
481
+ content: result.answer ?? '',
482
+ citations: result.citations ?? []
483
+ });
484
+ }
485
+ catch (requestError) {
486
+ const sourceRecords = await ensureRecords();
487
+ const fallbackCitations = rankRecords(sourceRecords, value.toLowerCase(), currentOptions.scope).slice(0, 3).map((result) => ({
488
+ title: result.title,
489
+ url: result.url,
490
+ ...(result.section ? { section: result.section } : {})
491
+ }));
492
+ const fallback = fallbackCitations.length > 0
493
+ ? currentOptions.t(fallbackCitations.length === 1 ? 'ask.fallbackSource' : 'ask.fallbackSources', {
494
+ count: fallbackCitations.length,
495
+ provider: currentOptions.config.ai.provider
496
+ })
497
+ : currentOptions.t('ask.fallbackReady', { provider: currentOptions.config.ai.provider });
498
+ updateAssistant(assistantMsg.id, {
499
+ content: fallback,
500
+ citations: fallbackCitations,
501
+ error: requestError instanceof Error ? requestError.message : currentOptions.t('ask.failed')
502
+ });
503
+ }
504
+ finally {
505
+ loading.set(false);
506
+ }
507
+ }
508
+ async function ensureRecords() {
509
+ const currentRecords = get(loadedRecords);
510
+ const currentOptions = get(options);
511
+ if (currentRecords.length > 0 || !currentOptions.loadRecords)
512
+ return currentRecords;
513
+ if (!recordsRequest) {
514
+ const requestVersion = recordsVersion;
515
+ recordsRequest = currentOptions.loadRecords()
516
+ .then((nextRecords) => {
517
+ if (requestVersion !== recordsVersion)
518
+ return get(loadedRecords);
519
+ loadedRecords.set(nextRecords);
520
+ return nextRecords;
521
+ })
522
+ .catch((error) => {
523
+ if (requestVersion === recordsVersion)
524
+ recordsRequest = undefined;
525
+ throw error;
526
+ });
527
+ }
528
+ try {
529
+ return await recordsRequest;
530
+ }
531
+ catch {
532
+ return get(loadedRecords);
533
+ }
534
+ }
535
+ function createLocalAskDraft(question, records, scope, t) {
536
+ const citations = rankRecords(records, question.toLowerCase(), scope).slice(0, 3).map((result) => ({
537
+ title: result.title,
538
+ url: result.url,
539
+ ...(result.section ? { section: result.section } : {})
540
+ }));
541
+ return {
542
+ content: citations.length > 0
543
+ ? t(citations.length === 1 ? 'ask.localSource' : 'ask.localSources', { count: citations.length })
544
+ : t('ask.localEmpty'),
545
+ citations
546
+ };
547
+ }
548
+ function findMessage(id) {
549
+ return get(messages).find((message) => message.id === id);
550
+ }
551
+ function updateAssistant(id, patch) {
552
+ messages.update((current) => current.map((message) => (message.id === id ? { ...message, ...patch } : message)));
553
+ }
554
+ function appendDelta(id, delta) {
555
+ if (!delta)
556
+ return;
557
+ messages.update((current) => current.map((message) => (message.id === id ? { ...message, content: message.content + delta } : message)));
558
+ }
559
+ function setCitations(id, citations) {
560
+ messages.update((current) => current.map((message) => (message.id === id ? { ...message, citations } : message)));
561
+ }
562
+ function setError(id, error) {
563
+ messages.update((current) => current.map((message) => (message.id === id ? { ...message, error } : message)));
564
+ }
565
+ function rankRecords(records, query, scope) {
566
+ return searchRecords(records, { query, limit: 5, ...scope });
567
+ }
568
+ async function readAskStream(response, assistantId, t) {
569
+ const reader = response.body?.getReader();
570
+ if (!reader)
571
+ return;
572
+ const decoder = new TextDecoder();
573
+ let buffer = '';
574
+ while (true) {
575
+ const { done, value } = await reader.read();
576
+ if (done)
577
+ break;
578
+ buffer += decoder.decode(value, { stream: true });
579
+ let boundary = findAskEventBoundary(buffer);
580
+ while (boundary) {
581
+ readAskEvent(buffer.slice(0, boundary.index), assistantId, t);
582
+ buffer = buffer.slice(boundary.index + boundary.length);
583
+ boundary = findAskEventBoundary(buffer);
584
+ }
585
+ }
586
+ buffer += decoder.decode();
587
+ if (buffer.trim())
588
+ readAskEvent(buffer, assistantId, t);
589
+ }
590
+ function findAskEventBoundary(value) {
591
+ const match = /\r\n\r\n|\n\n|\r\r/.exec(value);
592
+ return match ? { index: match.index, length: match[0].length } : undefined;
593
+ }
594
+ function readAskEvent(block, assistantId, t) {
595
+ const normalized = block.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
596
+ const event = /^event:\s*(.+)$/m.exec(normalized)?.[1] ?? 'message';
597
+ const data = normalized.split('\n').filter((line) => line.startsWith('data:')).map((line) => line.slice(5).trim()).join('\n');
598
+ if (!data || data === '[DONE]')
599
+ return;
600
+ try {
601
+ const payload = JSON.parse(data);
602
+ if (event === 'answer')
603
+ updateAssistant(assistantId, { content: payload.answer ?? '' });
604
+ if (event === 'delta')
605
+ appendDelta(assistantId, payload.delta ?? payload.content ?? '');
606
+ if (event === 'message') {
607
+ appendDelta(assistantId, payload.choices?.[0]?.delta?.content
608
+ ?? payload.choices?.[0]?.message?.content
609
+ ?? payload.choices?.[0]?.text
610
+ ?? payload.content
611
+ ?? '');
612
+ }
613
+ if (event === 'chunks')
614
+ setCitations(assistantId, normalizeChunkCitations(payload.chunks ?? [], t));
615
+ if (event === 'citations')
616
+ setCitations(assistantId, payload.citations ?? []);
617
+ if (event === 'error')
618
+ setError(assistantId, payload.error ?? t('ask.failed'));
619
+ }
620
+ catch {
621
+ setError(assistantId, t('ask.streamUnreadable'));
622
+ }
623
+ }
624
+ return {
625
+ open,
626
+ input,
627
+ messages,
628
+ loading,
629
+ setOptions,
630
+ show,
631
+ hide,
632
+ reset,
633
+ setInput,
634
+ send,
635
+ ensureRecords
636
+ };
637
+ }
638
+ export function createTocController(initial) {
639
+ const activeHeading = writable(initial.page.headings[0]?.id ?? '');
640
+ const indicatorTop = writable(0);
641
+ const indicatorHeight = writable(0);
642
+ const indicatorReady = writable(false);
643
+ let page = initial.page;
644
+ let tocEl = null;
645
+ let headingFrame;
646
+ let stopHeadingTracking;
647
+ let headingTrackingVersion = 0;
648
+ let mounted = false;
649
+ function setPage(nextPage) {
650
+ if (page === nextPage)
651
+ return;
652
+ page = nextPage;
653
+ activeHeading.set(page.headings[0]?.id ?? '');
654
+ indicatorReady.set(false);
655
+ if (mounted)
656
+ void attachHeadingTracker();
657
+ }
658
+ function setContainer(element) {
659
+ tocEl = element;
660
+ const id = get(activeHeading);
661
+ if (id)
662
+ void updateIndicator(id);
663
+ }
664
+ function activate(id) {
665
+ activeHeading.set(id);
666
+ void updateIndicator(id);
667
+ }
668
+ function mount() {
669
+ mounted = true;
670
+ void attachHeadingTracker();
671
+ return destroy;
672
+ }
673
+ function destroy() {
674
+ mounted = false;
675
+ headingTrackingVersion += 1;
676
+ stopHeadingTracking?.();
677
+ stopHeadingTracking = undefined;
678
+ cancelHeadingFrame();
679
+ }
680
+ async function attachHeadingTracker() {
681
+ if (typeof document === 'undefined' || typeof window === 'undefined')
682
+ return;
683
+ const version = headingTrackingVersion + 1;
684
+ headingTrackingVersion = version;
685
+ stopHeadingTracking?.();
686
+ stopHeadingTracking = undefined;
687
+ cancelHeadingFrame();
688
+ indicatorReady.set(false);
689
+ activeHeading.set(page.headings[0]?.id ?? '');
690
+ await tick();
691
+ if (version !== headingTrackingVersion)
692
+ return;
693
+ const headings = page.headings
694
+ .map((heading) => document.getElementById(heading.id))
695
+ .filter((element) => Boolean(element));
696
+ if (headings.length === 0)
697
+ return;
698
+ const syncActiveHeading = () => {
699
+ headingFrame = undefined;
700
+ const nextHeading = getActiveHeading(headings);
701
+ if (nextHeading) {
702
+ activeHeading.set(nextHeading.id);
703
+ void updateIndicator(nextHeading.id);
704
+ }
705
+ };
706
+ const scheduleActiveHeadingSync = () => {
707
+ if (headingFrame !== undefined)
708
+ return;
709
+ headingFrame = requestAnimationFrame(syncActiveHeading);
710
+ };
711
+ window.addEventListener('scroll', scheduleActiveHeadingSync, { passive: true });
712
+ window.addEventListener('resize', scheduleActiveHeadingSync);
713
+ stopHeadingTracking = () => {
714
+ window.removeEventListener('scroll', scheduleActiveHeadingSync);
715
+ window.removeEventListener('resize', scheduleActiveHeadingSync);
716
+ };
717
+ scheduleActiveHeadingSync();
718
+ }
719
+ async function updateIndicator(id) {
720
+ await tick();
721
+ if (!tocEl)
722
+ return;
723
+ const link = tocEl.querySelector(`a.sd-toc-link[href="#${cssEscape(id)}"]`);
724
+ if (!link)
725
+ return;
726
+ const markerHeight = Math.min(24, Math.max(16, link.offsetHeight - 12));
727
+ const atBottom = (window.innerHeight + window.scrollY) >= document.documentElement.scrollHeight - 2;
728
+ const isLastLink = !link.nextElementSibling || !link.nextElementSibling.matches('a.sd-toc-link');
729
+ if (atBottom && isLastLink) {
730
+ const paddingBottom = parseFloat(getComputedStyle(tocEl).paddingBottom) || 0;
731
+ indicatorTop.set(tocEl.scrollHeight - paddingBottom - markerHeight);
732
+ }
733
+ else {
734
+ indicatorTop.set(link.offsetTop + (link.offsetHeight - markerHeight) / 2);
735
+ }
736
+ indicatorHeight.set(markerHeight);
737
+ indicatorReady.set(true);
738
+ }
739
+ function cancelHeadingFrame() {
740
+ if (headingFrame === undefined)
741
+ return;
742
+ cancelAnimationFrame(headingFrame);
743
+ headingFrame = undefined;
744
+ }
745
+ return {
746
+ activeHeading,
747
+ indicatorTop,
748
+ indicatorHeight,
749
+ indicatorReady,
750
+ setPage,
751
+ setContainer,
752
+ activate,
753
+ mount,
754
+ destroy
755
+ };
756
+ }
757
+ export function createThemeModeController(defaultMode = 'system') {
758
+ const fixedMode = defaultMode === 'system' ? undefined : defaultMode;
759
+ const mode = writable(fixedMode ?? 'light');
760
+ const preference = writable(defaultMode);
761
+ let media;
762
+ function resolveMode(nextPreference) {
763
+ if (nextPreference !== 'system')
764
+ return nextPreference;
765
+ try {
766
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function')
767
+ return 'light';
768
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
769
+ }
770
+ catch {
771
+ return 'light';
772
+ }
773
+ }
774
+ function applyResolvedMode(nextMode) {
775
+ mode.set(nextMode);
776
+ if (typeof document !== 'undefined') {
777
+ document.documentElement.dataset.theme = nextMode;
778
+ document.documentElement.style.colorScheme = nextMode;
779
+ }
780
+ }
781
+ function setPreference(nextPreference) {
782
+ if (fixedMode) {
783
+ preference.set(fixedMode);
784
+ return;
785
+ }
786
+ preference.set(nextPreference);
787
+ writeStoredThemePreference(nextPreference);
788
+ applyResolvedMode(resolveMode(nextPreference));
789
+ }
790
+ function apply(nextMode) {
791
+ if (fixedMode)
792
+ return;
793
+ setPreference(nextMode);
794
+ }
795
+ function toggle() {
796
+ if (fixedMode)
797
+ return;
798
+ apply(get(mode) === 'dark' ? 'light' : 'dark');
799
+ }
800
+ function syncFromSystem() {
801
+ if (fixedMode)
802
+ return;
803
+ const stored = readStoredThemePreference();
804
+ const nextPreference = stored === 'dark' || stored === 'light' || stored === 'system' ? stored : defaultMode;
805
+ preference.set(nextPreference);
806
+ applyResolvedMode(resolveMode(nextPreference));
807
+ }
808
+ function syncFromMedia() {
809
+ if (get(preference) === 'system')
810
+ applyResolvedMode(resolveMode('system'));
811
+ }
812
+ function mount() {
813
+ if (fixedMode)
814
+ return () => undefined;
815
+ const current = typeof document !== 'undefined' ? document.documentElement.dataset.theme : undefined;
816
+ mode.set(current === 'dark' ? 'dark' : 'light');
817
+ try {
818
+ if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
819
+ media = window.matchMedia('(prefers-color-scheme: dark)');
820
+ media.addEventListener('change', syncFromMedia);
821
+ }
822
+ }
823
+ catch {
824
+ media = undefined;
825
+ }
826
+ syncFromSystem();
827
+ return () => media?.removeEventListener('change', syncFromMedia);
828
+ }
829
+ return {
830
+ mode,
831
+ preference,
832
+ apply,
833
+ setPreference,
834
+ toggle,
835
+ mount
836
+ };
837
+ }
838
+ function readStoredThemePreference() {
839
+ try {
840
+ return typeof localStorage === 'undefined' ? undefined : localStorage.getItem('svedocs-theme') ?? undefined;
841
+ }
842
+ catch {
843
+ return undefined;
844
+ }
845
+ }
846
+ function writeStoredThemePreference(value) {
847
+ try {
848
+ if (typeof localStorage !== 'undefined')
849
+ localStorage.setItem('svedocs-theme', value);
850
+ }
851
+ catch {
852
+ // Theme switching remains functional when storage is blocked.
853
+ }
854
+ }
855
+ export function createMobileNavController() {
856
+ const open = writable(false);
857
+ function toggle() {
858
+ open.update((value) => !value);
859
+ }
860
+ function close() {
861
+ open.set(false);
862
+ }
863
+ function handleWindowKeydown(event) {
864
+ if (event.key === 'Escape')
865
+ close();
866
+ }
867
+ return {
868
+ open,
869
+ toggle,
870
+ close,
871
+ handleWindowKeydown
872
+ };
873
+ }
874
+ export function createPageToolsController(config) {
875
+ const scrolled = writable(false);
876
+ const aiEnabled = config.ai.enabled;
877
+ const visible = derived(scrolled, ($scrolled) => aiEnabled || $scrolled);
878
+ const mode = derived(scrolled, () => (aiEnabled ? 'pill' : 'solo'));
879
+ const aiCollapsed = derived(scrolled, ($scrolled) => aiEnabled && $scrolled);
880
+ function openAskAi() {
881
+ if (!aiEnabled || typeof window === 'undefined')
882
+ return;
883
+ window.dispatchEvent(new CustomEvent('svedocs:open-ai'));
884
+ }
885
+ function backToTop() {
886
+ const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
887
+ window.scrollTo({ top: 0, behavior: reduceMotion ? 'auto' : 'smooth' });
888
+ }
889
+ function mount() {
890
+ if (typeof window === 'undefined')
891
+ return () => undefined;
892
+ function updateScrolled() {
893
+ scrolled.set(window.scrollY > 240);
894
+ }
895
+ updateScrolled();
896
+ window.addEventListener('scroll', updateScrolled, { passive: true });
897
+ return () => window.removeEventListener('scroll', updateScrolled);
898
+ }
899
+ return {
900
+ scrolled,
901
+ visible,
902
+ mode,
903
+ aiCollapsed,
904
+ openAskAi,
905
+ backToTop,
906
+ mount
907
+ };
908
+ }
909
+ export async function copyTextToClipboard(source) {
910
+ await navigator.clipboard.writeText(source.trim());
911
+ }
912
+ export async function copyCodeToClipboard(button, source, copiedLabel = 'Copied', idleLabel = 'Copy code') {
913
+ try {
914
+ await copyTextToClipboard(source);
915
+ button.dataset.state = 'copied';
916
+ button.innerHTML = checkIconSvg;
917
+ button.setAttribute('aria-label', copiedLabel);
918
+ window.setTimeout(() => {
919
+ button.dataset.state = 'idle';
920
+ button.innerHTML = copyIconSvg;
921
+ button.setAttribute('aria-label', idleLabel);
922
+ }, 1600);
923
+ }
924
+ catch {
925
+ button.dataset.state = 'error';
926
+ }
927
+ }
928
+ export function createDefaultSearchResults(records, scope) {
929
+ return filterSearchRecords(records, scope).slice(0, 6).map((record) => ({
930
+ id: record.id,
931
+ title: record.title,
932
+ url: record.url,
933
+ ...(record.section ? { section: record.section } : {}),
934
+ excerpt: record.content.slice(0, 150),
935
+ score: 0.1,
936
+ metadata: record.metadata
937
+ }));
938
+ }
939
+ export function createSearchUrl(endpoint, query, scope, provider, origin) {
940
+ const base = origin ?? (typeof window !== 'undefined' ? window.location.origin : 'http://localhost');
941
+ const url = new URL(endpoint, base);
942
+ url.searchParams.set('q', query);
943
+ url.searchParams.set('limit', '8');
944
+ url.searchParams.set('provider', provider);
945
+ if (scope.locale)
946
+ url.searchParams.set('locale', scope.locale);
947
+ if (scope.kind)
948
+ url.searchParams.set('kind', scope.kind);
949
+ return url.toString();
950
+ }
951
+ function isRemoteSearch(options) {
952
+ return options.buildMode === 'edge' && options.provider !== 'local' && options.provider !== 'local-json';
953
+ }
954
+ function normalizeSearchOptions(options) {
955
+ return {
956
+ records: options.records ?? [],
957
+ scope: options.scope ?? {},
958
+ provider: options.provider ?? 'local',
959
+ endpoint: options.endpoint ?? '/api/search',
960
+ buildMode: options.buildMode ?? 'edge',
961
+ t: options.t ?? fallbackTranslate,
962
+ ...(options.loadRecords ? { loadRecords: options.loadRecords } : {}),
963
+ ...(options.fetcher ? { fetcher: options.fetcher } : {}),
964
+ ...(options.origin ? { origin: options.origin } : {})
965
+ };
966
+ }
967
+ function normalizeAskOptions(options) {
968
+ return {
969
+ config: options.config,
970
+ records: options.records ?? [],
971
+ scope: options.scope ?? {},
972
+ endpoint: options.endpoint ?? '/api/ask',
973
+ buildMode: options.buildMode ?? 'edge',
974
+ t: options.t ?? fallbackTranslate,
975
+ ...(options.loadRecords ? { loadRecords: options.loadRecords } : {}),
976
+ ...(Object.hasOwn(options, 'welcomeMessage') && options.welcomeMessage !== undefined ? { welcomeMessage: options.welcomeMessage } : {}),
977
+ ...(options.fetcher ? { fetcher: options.fetcher } : {})
978
+ };
979
+ }
980
+ function createRemoteKey(provider, endpoint, query, scope) {
981
+ return JSON.stringify([provider, endpoint, query.trim(), scope.locale, scope.kind]);
982
+ }
983
+ function normalizeChunkCitations(chunks, t) {
984
+ return chunks.slice(0, 5).map((chunk, index) => {
985
+ const metadata = normalizeSvedocsMetadata({ ...(chunk.item?.metadata ?? {}), ...(chunk.metadata ?? {}) });
986
+ const section = stringValue(metadata.section);
987
+ return {
988
+ title: stringValue(chunk.title) ?? stringValue(chunk.item?.title) ?? stringValue(metadata.title) ?? t('ask.sourceTitle', { index: index + 1 }),
989
+ url: stringValue(chunk.url) ?? stringValue(metadata.url) ?? stringValue(metadata.source_url) ?? '#',
990
+ ...(section ? { section } : {})
991
+ };
992
+ });
993
+ }
994
+ function normalizeSvedocsMetadata(metadata) {
995
+ const embedded = stringValue(metadata.svedocs);
996
+ if (!embedded)
997
+ return metadata;
998
+ try {
999
+ const parsed = JSON.parse(embedded);
1000
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
1001
+ return metadata;
1002
+ return { ...metadata, ...parsed };
1003
+ }
1004
+ catch {
1005
+ return metadata;
1006
+ }
1007
+ }
1008
+ function stringValue(value) {
1009
+ return typeof value === 'string' && value.trim() ? value : undefined;
1010
+ }
1011
+ function cssEscape(value) {
1012
+ if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function')
1013
+ return CSS.escape(value);
1014
+ return value.replace(/([^a-zA-Z0-9_-])/g, '\\$1');
1015
+ }
1016
+ function getActiveHeading(headings) {
1017
+ const viewportTop = 80;
1018
+ const viewportBottom = window.innerHeight || document.documentElement.clientHeight;
1019
+ const snapshots = headings.map((heading) => ({
1020
+ element: heading,
1021
+ top: heading.getBoundingClientRect().top
1022
+ }));
1023
+ const visible = snapshots.filter((heading) => heading.top >= viewportTop && heading.top <= viewportBottom);
1024
+ if (visible.length > 0) {
1025
+ return visible.reduce((lowest, heading) => heading.top > lowest.top ? heading : lowest).element;
1026
+ }
1027
+ return snapshots.filter((heading) => heading.top < viewportTop).at(-1)?.element ?? snapshots[0]?.element;
1028
+ }
1029
+ function isPathMatch(currentPath, hrefPath) {
1030
+ if (hrefPath === '/')
1031
+ return currentPath === '/';
1032
+ return currentPath === hrefPath || currentPath.startsWith(`${hrefPath}/`);
1033
+ }
1034
+ function getPathname(path) {
1035
+ if (path.startsWith('http://') || path.startsWith('https://')) {
1036
+ try {
1037
+ return new URL(path).pathname;
1038
+ }
1039
+ catch {
1040
+ return path;
1041
+ }
1042
+ }
1043
+ return path.split(/[?#]/, 1)[0] || '/';
1044
+ }
1045
+ function decodePathSegment(segment) {
1046
+ if (!segment)
1047
+ return undefined;
1048
+ try {
1049
+ return decodeURIComponent(segment);
1050
+ }
1051
+ catch {
1052
+ return segment;
1053
+ }
1054
+ }
1055
+ function createComparableUrl(value, base) {
1056
+ try {
1057
+ const url = new URL(value, base);
1058
+ url.pathname = normalizePath(url.pathname);
1059
+ return url.href;
1060
+ }
1061
+ catch {
1062
+ return undefined;
1063
+ }
1064
+ }