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,93 @@
1
+ export function createExcerpt(content, query) {
2
+ const terms = tokenizeSearchQuery(query);
3
+ const haystack = content.toLowerCase();
4
+ const index = terms.reduce((best, term) => {
5
+ const next = haystack.indexOf(term);
6
+ if (next < 0)
7
+ return best;
8
+ return best < 0 ? next : Math.min(best, next);
9
+ }, -1);
10
+ if (index < 0)
11
+ return content.slice(0, 160);
12
+ const start = Math.max(0, index - 60);
13
+ return content.slice(start, start + 180).trim();
14
+ }
15
+ export function normalizeSearchText(value) {
16
+ return value
17
+ .toLowerCase()
18
+ .normalize('NFKD')
19
+ .replace(/[\u0300-\u036f]/g, '')
20
+ .replace(/[^\p{L}\p{N}]+/gu, ' ')
21
+ .trim();
22
+ }
23
+ export function tokenizeSearchText(value) {
24
+ const normalized = normalizeSearchText(value);
25
+ if (!normalized)
26
+ return [];
27
+ return normalized.split(/\s+/).flatMap(tokenizeSearchSegment);
28
+ }
29
+ export function tokenizeSearchQuery(query) {
30
+ return normalizeSearchText(query).split(/\s+/).filter(Boolean);
31
+ }
32
+ export function stringMetadata(value) {
33
+ return typeof value === 'string' && value.trim() ? value : undefined;
34
+ }
35
+ export function stringifyMetadata(metadata) {
36
+ return Object.fromEntries(Object.entries(metadata)
37
+ .filter(([, value]) => value !== undefined && value !== null)
38
+ .map(([key, value]) => [key, Array.isArray(value) ? value.join(',') : String(value)]));
39
+ }
40
+ export function uniqueStrings(values) {
41
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
42
+ }
43
+ export function jsonResponse(value, status = 200) {
44
+ return new Response(JSON.stringify(value), {
45
+ status,
46
+ headers: {
47
+ 'content-type': 'application/json; charset=utf-8'
48
+ }
49
+ });
50
+ }
51
+ export function chunkArray(items, size) {
52
+ const safeSize = Math.max(1, Math.floor(size));
53
+ const chunks = [];
54
+ for (let index = 0; index < items.length; index += safeSize) {
55
+ chunks.push(items.slice(index, index + safeSize));
56
+ }
57
+ return chunks;
58
+ }
59
+ export function wait(ms) {
60
+ return new Promise((resolve) => setTimeout(resolve, ms));
61
+ }
62
+ function tokenizeSearchSegment(segment) {
63
+ const tokens = [];
64
+ let run = '';
65
+ let runIsCjk;
66
+ for (const char of Array.from(segment)) {
67
+ const charIsCjk = isCjkSearchChar(char);
68
+ if (run && charIsCjk !== runIsCjk) {
69
+ tokens.push(...tokenizeSearchRun(run, runIsCjk === true));
70
+ run = '';
71
+ }
72
+ run += char;
73
+ runIsCjk = charIsCjk;
74
+ }
75
+ if (run)
76
+ tokens.push(...tokenizeSearchRun(run, runIsCjk === true));
77
+ return tokens;
78
+ }
79
+ function tokenizeSearchRun(run, cjk) {
80
+ if (!cjk)
81
+ return [run];
82
+ const chars = Array.from(run);
83
+ const tokens = [...chars];
84
+ for (let size = 2; size <= Math.min(3, chars.length); size += 1) {
85
+ for (let index = 0; index <= chars.length - size; index += 1) {
86
+ tokens.push(chars.slice(index, index + size).join(''));
87
+ }
88
+ }
89
+ return tokens;
90
+ }
91
+ function isCjkSearchChar(value) {
92
+ return /[\u3040-\u30ff\u3400-\u9fff\uf900-\ufaff\uac00-\ud7af]/u.test(value);
93
+ }
@@ -0,0 +1,8 @@
1
+ export * from './search/types.js';
2
+ export * from './search/local.js';
3
+ export * from './search/algolia.js';
4
+ export * from './search/typesense.js';
5
+ export * from './search/cloudflare.js';
6
+ export * from './search/cloudflare-sync.js';
7
+ export * from './search/runtime.js';
8
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC"}
package/dist/search.js ADDED
@@ -0,0 +1,7 @@
1
+ export * from './search/types.js';
2
+ export * from './search/local.js';
3
+ export * from './search/algolia.js';
4
+ export * from './search/typesense.js';
5
+ export * from './search/cloudflare.js';
6
+ export * from './search/cloudflare-sync.js';
7
+ export * from './search/runtime.js';
@@ -0,0 +1,27 @@
1
+ import type { MdsvexOptions } from 'mdsvex';
2
+ export declare const svedocsContentExtensions: readonly [".md", ".mdx", ".svx"];
3
+ export declare const svedocsSvelteExtensions: readonly [".svelte", ".md", ".mdx", ".svx"];
4
+ export interface SvedocsPreprocessor {
5
+ name: string;
6
+ markup(input: {
7
+ content: string;
8
+ filename?: string;
9
+ }): Promise<{
10
+ code: string;
11
+ data?: Record<string, unknown>;
12
+ map?: string;
13
+ } | undefined>;
14
+ }
15
+ export declare function svedocsPreprocess(options?: MdsvexOptions): SvedocsPreprocessor;
16
+ export declare function createSvedocsMdsvexOptions(source: string, options?: MdsvexOptions, svedocsOptions?: {
17
+ codeTheme?: string;
18
+ codeThemes?: {
19
+ light?: string;
20
+ dark?: string;
21
+ };
22
+ shikiTransformers?: unknown[];
23
+ codeCopyButton?: boolean;
24
+ headingAnchorLabel?: string;
25
+ resolveHref?: (href: string) => string;
26
+ }): MdsvexOptions;
27
+ //# sourceMappingURL=svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svelte.d.ts","sourceRoot":"","sources":["../src/svelte.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAS5C,eAAO,MAAM,wBAAwB,kCAAmC,CAAC;AACzE,eAAO,MAAM,uBAAuB,6CAAoD,CAAC;AAEzF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,KAAK,EAAE;QACZ,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC;QACV,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,GAAG,SAAS,CAAC,CAAC;CAChB;AAED,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,aAAkB,GAAG,mBAAmB,CAQlF;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,aAAkB,EAC3B,cAAc,GAAE;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,iBAAiB,CAAC,EAAE,OAAO,EAAE,CAAC;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACnC,GACL,aAAa,CAyBf"}
package/dist/svelte.js ADDED
@@ -0,0 +1,45 @@
1
+ import { mdsvex } from 'mdsvex';
2
+ import rehypeKatex from 'rehype-katex';
3
+ import rehypeSlug from 'rehype-slug';
4
+ import remarkGfm from 'remark-gfm';
5
+ import remarkMath from 'remark-math';
6
+ import { extractCodeBlocks, rehypeCodeBlocks, remarkSvedocsCodeBlocks } from './mdx/code.js';
7
+ import { rehypeSvedocsHeadingAnchors } from './mdx/headings.js';
8
+ import { rehypeSvedocsLinks } from './mdx/links.js';
9
+ export const svedocsContentExtensions = ['.md', '.mdx', '.svx'];
10
+ export const svedocsSvelteExtensions = ['.svelte', ...svedocsContentExtensions];
11
+ export function svedocsPreprocess(options = {}) {
12
+ return {
13
+ name: 'svedocs-mdsvex',
14
+ async markup(input) {
15
+ const processor = mdsvex(createSvedocsMdsvexOptions(input.content, options));
16
+ return processor.markup(input);
17
+ }
18
+ };
19
+ }
20
+ export function createSvedocsMdsvexOptions(source, options = {}, svedocsOptions = {}) {
21
+ const codeBlocks = extractCodeBlocks(source);
22
+ return {
23
+ extensions: [...svedocsContentExtensions],
24
+ ...options,
25
+ remarkPlugins: [
26
+ remarkGfm,
27
+ remarkMath,
28
+ [remarkSvedocsCodeBlocks, codeBlocks, {
29
+ theme: svedocsOptions.codeTheme,
30
+ themes: svedocsOptions.codeThemes,
31
+ transformers: svedocsOptions.shikiTransformers,
32
+ ...(typeof svedocsOptions.codeCopyButton === 'boolean' ? { copyButton: svedocsOptions.codeCopyButton } : {})
33
+ }],
34
+ ...(options.remarkPlugins ?? [])
35
+ ],
36
+ rehypePlugins: [
37
+ rehypeSlug,
38
+ [rehypeSvedocsHeadingAnchors, { label: svedocsOptions.headingAnchorLabel }],
39
+ rehypeKatex,
40
+ [rehypeSvedocsLinks, { resolveHref: svedocsOptions.resolveHref }],
41
+ [rehypeCodeBlocks, codeBlocks],
42
+ ...(options.rehypePlugins ?? [])
43
+ ]
44
+ };
45
+ }
@@ -0,0 +1,3 @@
1
+ import type { SvedocsPage } from './core.js';
2
+ export declare function createFixturePage(overrides?: Partial<SvedocsPage>): SvedocsPage;
3
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAsBnF"}
@@ -0,0 +1,23 @@
1
+ export function createFixturePage(overrides = {}) {
2
+ const id = overrides.id ?? 'index';
3
+ const routePath = overrides.routePath ?? '/';
4
+ return {
5
+ id,
6
+ sourcePath: overrides.sourcePath ?? 'content/index.md',
7
+ routePath,
8
+ scopePath: overrides.scopePath ?? routePath,
9
+ slug: overrides.slug ?? [],
10
+ kind: overrides.kind ?? 'doc',
11
+ title: overrides.title ?? 'Introduction',
12
+ html: overrides.html ?? '<h1>Introduction</h1>',
13
+ plainText: overrides.plainText ?? 'Introduction',
14
+ headings: overrides.headings ?? [],
15
+ links: overrides.links ?? [],
16
+ codeBlocks: overrides.codeBlocks ?? [],
17
+ frontmatter: overrides.frontmatter ?? {},
18
+ seo: overrides.seo ?? { title: overrides.title ?? 'Introduction' },
19
+ search: overrides.search ?? [],
20
+ lastUpdated: overrides.lastUpdated ?? '2026-05-18T00:00:00.000Z',
21
+ ...overrides
22
+ };
23
+ }
@@ -0,0 +1,145 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import type { Component } from 'svelte';
4
+ import type { SvedocsPage } from '../core/types.js';
5
+ import { copyCodeToClipboard, fallbackTranslate } from './headless.js';
6
+ import SafeRenderError from './SafeRenderError.svelte';
7
+ import type { SvedocsThemeComponentMap, SvedocsThemeContext } from './types.js';
8
+
9
+ export let page: SvedocsPage;
10
+ export let content: Component | undefined = undefined;
11
+ export let context: SvedocsThemeContext | undefined = undefined;
12
+ export let hasDocHeaderSlot: boolean | undefined = undefined;
13
+ export let themeComponents: Partial<SvedocsThemeComponentMap> = {};
14
+
15
+ $: ErrorComponent = SafeRenderError;
16
+ $: t = context?.t ?? fallbackTranslate;
17
+ $: breadcrumbs = createBreadcrumbs(page, context);
18
+ $: kind = page.kind === 'doc' ? t('article.kind.doc') : t('article.kind.page');
19
+ $: eyebrow = breadcrumbs.length > 0 ? breadcrumbs : [{ label: kind, path: page.kind === 'doc' ? findDocsRoot(page, context) : '/' }];
20
+ $: showDocHeaderSlot = hasDocHeaderSlot ?? Boolean($$slots['doc-header']);
21
+
22
+ onMount(() => {
23
+ const root = document.querySelector<HTMLElement>('.sd-prose');
24
+ if (!root) return;
25
+ function handleClick(event: MouseEvent) {
26
+ const target = event.target as HTMLElement | null;
27
+ const button = target?.closest<HTMLButtonElement>('button.sd-code-copy');
28
+ if (!button) return;
29
+ event.preventDefault();
30
+ const block = button.closest<HTMLElement>('.sd-code');
31
+ if (!block) return;
32
+ const source = block.dataset.copy
33
+ ?? block.querySelector<HTMLElement>('code[data-copy]')?.dataset.copy
34
+ ?? block.querySelector('code')?.textContent
35
+ ?? block.textContent
36
+ ?? '';
37
+ void copyCodeToClipboard(button, source, t('code.copied'), button.getAttribute('aria-label') ?? t('code.copy'));
38
+ }
39
+ root.addEventListener('click', handleClick);
40
+ return () => root.removeEventListener('click', handleClick);
41
+ });
42
+
43
+ function createBreadcrumbs(page: SvedocsPage, context: SvedocsThemeContext | undefined) {
44
+ if (page.kind !== 'doc') return [];
45
+ const docsRoot = findDocsRoot(page, context);
46
+ if (page.routePath === docsRoot) return [];
47
+ const segments = page.routePath.split('/').filter(Boolean);
48
+ const rootLength = docsRoot.split('/').filter(Boolean).length;
49
+ const breadcrumbs = [{ label: t('nav.docs'), path: docsRoot }];
50
+ for (let length = rootLength + 1; length < segments.length; length += 1) {
51
+ const path = `/${segments.slice(0, length).join('/')}`;
52
+ const target = context?.pages.find((candidate) => candidate.routePath === path);
53
+ breadcrumbs.push({ label: target?.navTitle ?? target?.title ?? titleFromSegment(segments[length - 1] ?? ''), path });
54
+ }
55
+ return breadcrumbs;
56
+ }
57
+
58
+ function titleFromSegment(segment: string): string {
59
+ return segment
60
+ .replace(/[-_]+/g, ' ')
61
+ .replace(/\b\w/g, (letter) => letter.toUpperCase());
62
+ }
63
+
64
+ function findDocsRoot(page: SvedocsPage, context: SvedocsThemeContext | undefined): string {
65
+ const translatedRoot = context?.pages.find((candidate) => (
66
+ candidate.kind === 'doc'
67
+ && candidate.scopePath === '/docs'
68
+ && candidate.locale === page.locale
69
+ ));
70
+ if (translatedRoot) return translatedRoot.routePath;
71
+ const suffix = page.scopePath === '/docs' ? '' : page.scopePath.slice('/docs'.length);
72
+ if (suffix && page.routePath.endsWith(suffix)) {
73
+ return page.routePath.slice(0, -suffix.length) || '/docs';
74
+ }
75
+ return page.scopePath === '/docs' ? page.routePath : '/docs';
76
+ }
77
+ </script>
78
+
79
+ <article class="sd-doc" data-theme-component="article" data-site={context?.config.site.name}>
80
+ <span class="sd-doc-corner" data-corner="tl" aria-hidden="true"></span>
81
+ <span class="sd-doc-corner" data-corner="tr" aria-hidden="true"></span>
82
+ {#if showDocHeaderSlot}
83
+ <slot name="doc-header" {page} breadcrumbs={eyebrow} />
84
+ {:else}
85
+ <header class="sd-doc-header">
86
+ <nav class="sd-doc-eyebrow" aria-label={t('article.breadcrumb')}>
87
+ <span class="sd-doc-eyebrow-mark" aria-hidden="true"></span>
88
+ {#each eyebrow as item, i}
89
+ {#if i > 0}<span class="sd-doc-eyebrow-sep" aria-hidden="true">/</span>{/if}
90
+ <a href={item.path}>{item.label}</a>
91
+ {/each}
92
+ </nav>
93
+ <h1>{page.title}</h1>
94
+ {#if page.description}
95
+ <p class="sd-doc-lede">{page.description}</p>
96
+ {/if}
97
+ </header>
98
+ {/if}
99
+ <div class="sd-prose">
100
+ <svelte:boundary>
101
+ {#if content}
102
+ <svelte:component this={content} />
103
+ {:else}
104
+ {@html page.html}
105
+ {/if}
106
+ {#snippet failed(error, reset)}
107
+ <svelte:component
108
+ this={ErrorComponent} component={themeComponents.RenderError}
109
+ {error}
110
+ {reset}
111
+ {page}
112
+ {context}
113
+ variant="content"
114
+ label={t('render.article.label')}
115
+ title={t('render.article.title')}
116
+ message={t('render.article.message')}
117
+ />
118
+ {/snippet}
119
+ </svelte:boundary>
120
+ </div>
121
+ <footer class="sd-doc-footer">
122
+ <div class="sd-doc-meta">
123
+ {#if page.lastUpdated}
124
+ <span>{t('article.updated', { date: new Date(page.lastUpdated).toLocaleDateString(context?.languageTag) })}</span>
125
+ {/if}
126
+ {#if page.editUrl}
127
+ <a href={page.editUrl}>{t('article.edit')}</a>
128
+ {/if}
129
+ </div>
130
+ {#if page.prev}
131
+ <a class="sd-prev-next" data-direction="prev" href={page.prev.path}>
132
+ <span><i aria-hidden="true"></i>{t('article.previous')}</span>
133
+ <strong>{page.prev.title}</strong>
134
+ <em class="sd-prev-next-arrow" aria-hidden="true">←</em>
135
+ </a>
136
+ {/if}
137
+ {#if page.next}
138
+ <a class="sd-prev-next" data-direction="next" href={page.next.path}>
139
+ <span>{t('article.next')}<i aria-hidden="true"></i></span>
140
+ <strong>{page.next.title}</strong>
141
+ <em class="sd-prev-next-arrow" aria-hidden="true">→</em>
142
+ </a>
143
+ {/if}
144
+ </footer>
145
+ </article>
@@ -0,0 +1,36 @@
1
+ import type { Component } from 'svelte';
2
+ import type { SvedocsPage } from '../core/types.js';
3
+ import type { SvedocsThemeComponentMap, SvedocsThemeContext } from './types.js';
4
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
5
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
6
+ $$bindings?: Bindings;
7
+ } & Exports;
8
+ (internal: unknown, props: Props & {
9
+ $$events?: Events;
10
+ $$slots?: Slots;
11
+ }): Exports & {
12
+ $set?: any;
13
+ $on?: any;
14
+ };
15
+ z_$$bindings?: Bindings;
16
+ }
17
+ declare const Article: $$__sveltets_2_IsomorphicComponent<{
18
+ page: SvedocsPage;
19
+ content?: Component | undefined;
20
+ context?: SvedocsThemeContext | undefined;
21
+ hasDocHeaderSlot?: boolean | undefined;
22
+ themeComponents?: Partial<SvedocsThemeComponentMap>;
23
+ }, {
24
+ [evt: string]: CustomEvent<any>;
25
+ }, {
26
+ 'doc-header': {
27
+ page: SvedocsPage;
28
+ breadcrumbs: {
29
+ label: string;
30
+ path: string;
31
+ }[];
32
+ };
33
+ }, {}, string>;
34
+ type Article = InstanceType<typeof Article>;
35
+ export default Article;
36
+ //# sourceMappingURL=Article.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Article.svelte.d.ts","sourceRoot":"","sources":["../../src/theme/Article.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,KAAK,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AA2IhF,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IAC9G,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAUD,QAAA,MAAM,OAAO;UAfwI,WAAW;cAAY,SAAS,GAAG,SAAS;cAAY,mBAAmB,GAAG,SAAS;uBAAqB,OAAO,GAAG,SAAS;sBAAoB,OAAO,CAAC,wBAAwB,CAAC;;;;;;;;;;;cAevO,CAAC;AACjF,KAAK,OAAO,GAAG,YAAY,CAAC,OAAO,OAAO,CAAC,CAAC;AAC9C,eAAe,OAAO,CAAC"}
@@ -0,0 +1,241 @@
1
+ <script lang="ts">
2
+ import { onDestroy, onMount, tick } from 'svelte';
3
+ import type { SvedocsResolvedConfig, SvedocsSearchRecord } from '../core/types.js';
4
+ import type { SearchScope } from '../search/types.js';
5
+ import { createAskAiController, fallbackTranslate } from './headless.js';
6
+ import { portal } from './portal.js';
7
+ import type { SvedocsAskAiController, SvedocsAskAiMessage, SvedocsThemeContext } from './types.js';
8
+
9
+ export let config: SvedocsResolvedConfig;
10
+ export let records: SvedocsSearchRecord[] = [];
11
+ export let loadRecords: (() => Promise<SvedocsSearchRecord[]>) | undefined = undefined;
12
+ export let scope: SearchScope = {};
13
+ export let buildMode: SvedocsResolvedConfig['build']['mode'] = 'edge';
14
+ export let endpoint = '/api/ask';
15
+ export let controller: SvedocsAskAiController | undefined = undefined;
16
+ export let context: SvedocsThemeContext | undefined = undefined;
17
+
18
+ const internalController = createAskAiController({ config, records, loadRecords, scope, buildMode, endpoint });
19
+ let activeController: SvedocsAskAiController = internalController;
20
+ let open = false;
21
+ let input = '';
22
+ let messages: SvedocsAskAiMessage[] = [];
23
+ let loading = false;
24
+ let panel: HTMLDivElement | undefined;
25
+ let textarea: HTMLTextAreaElement | undefined;
26
+ let scrollEl: HTMLDivElement | undefined;
27
+ let previousFocus: HTMLElement | undefined;
28
+ let boundController: SvedocsAskAiController | undefined;
29
+ let unsubscribeController: (() => void) | undefined;
30
+
31
+ $: t = context?.t ?? fallbackTranslate;
32
+ $: activeController = controller ?? internalController;
33
+ $: activeController.setOptions({ config, records, loadRecords, scope, buildMode, endpoint, welcomeMessage: t('ask.welcome'), t });
34
+ $: enabled = config.ai.enabled;
35
+ $: label = t('ask.label');
36
+ $: placeholder = t('ask.placeholder');
37
+ $: suggestions = [
38
+ t('ask.suggestion.1'),
39
+ t('ask.suggestion.2'),
40
+ t('ask.suggestion.3')
41
+ ].filter(Boolean);
42
+ $: showSuggestions = suggestions.length > 0
43
+ && !loading
44
+ && messages.filter((message) => !message.welcome).length === 0;
45
+ $: bindController(activeController);
46
+
47
+ function show() {
48
+ if (!enabled) return;
49
+ previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : undefined;
50
+ activeController.show();
51
+ tick().then(() => {
52
+ textarea?.focus();
53
+ scrollToBottom();
54
+ });
55
+ }
56
+
57
+ function hide() {
58
+ activeController.hide();
59
+ tick().then(() => previousFocus?.focus());
60
+ }
61
+
62
+ function reset() {
63
+ activeController.reset();
64
+ tick().then(scrollToBottom);
65
+ }
66
+
67
+ function scrollToBottom() {
68
+ if (!scrollEl) return;
69
+ scrollEl.scrollTop = scrollEl.scrollHeight;
70
+ }
71
+
72
+ async function send(text = input) {
73
+ await activeController.send(text);
74
+ await tick();
75
+ scrollToBottom();
76
+ }
77
+
78
+ function handleInput(event: Event) {
79
+ activeController.setInput((event.currentTarget as HTMLTextAreaElement).value);
80
+ }
81
+
82
+ function handleKeydown(event: KeyboardEvent) {
83
+ if (event.key === 'Escape') {
84
+ event.preventDefault();
85
+ hide();
86
+ }
87
+ if (event.key === 'Tab') trapFocus(event, panel);
88
+ }
89
+
90
+ function handleComposerKeydown(event: KeyboardEvent) {
91
+ if (event.key === 'Enter' && !event.shiftKey) {
92
+ event.preventDefault();
93
+ void send(input);
94
+ }
95
+ }
96
+
97
+ function bindController(nextController: SvedocsAskAiController): void {
98
+ if (boundController === nextController) return;
99
+ unsubscribeController?.();
100
+ boundController = nextController;
101
+ const unsubscribers = [
102
+ nextController.open.subscribe((value) => (open = value)),
103
+ nextController.input.subscribe((value) => (input = value)),
104
+ nextController.loading.subscribe((value) => (loading = value)),
105
+ nextController.messages.subscribe((value) => {
106
+ messages = value;
107
+ if (open) tick().then(scrollToBottom);
108
+ })
109
+ ];
110
+ unsubscribeController = () => {
111
+ for (const unsubscribe of unsubscribers) unsubscribe();
112
+ };
113
+ }
114
+
115
+ function trapFocus(event: KeyboardEvent, root: HTMLElement | undefined) {
116
+ if (!root) return;
117
+ const focusable = Array.from(
118
+ root.querySelectorAll<HTMLElement>('a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])')
119
+ ).filter((element) => !element.hasAttribute('disabled') && element.offsetParent !== null);
120
+ const first = focusable[0];
121
+ const last = focusable.at(-1);
122
+ if (!first || !last) return;
123
+ if (event.shiftKey && document.activeElement === first) {
124
+ event.preventDefault();
125
+ last.focus();
126
+ } else if (!event.shiftKey && document.activeElement === last) {
127
+ event.preventDefault();
128
+ first.focus();
129
+ }
130
+ }
131
+
132
+ onMount(() => {
133
+ window.addEventListener('svedocs:open-ai', show);
134
+ return () => window.removeEventListener('svedocs:open-ai', show);
135
+ });
136
+
137
+ onDestroy(() => {
138
+ unsubscribeController?.();
139
+ });
140
+ </script>
141
+
142
+ {#if open}
143
+ <div class="sd-chat-portal" use:portal>
144
+ <div
145
+ bind:this={panel}
146
+ class="sd-chat-panel"
147
+ role="dialog"
148
+ aria-modal="true"
149
+ aria-label={label}
150
+ tabindex="-1"
151
+ on:keydown={handleKeydown}
152
+ data-theme-component="ask-ai"
153
+ >
154
+ <header class="sd-chat-header">
155
+ <div class="sd-chat-title">
156
+ <span class="sd-chat-mark" aria-hidden="true"></span>
157
+ <strong>{label}</strong>
158
+ </div>
159
+ <div class="sd-chat-actions">
160
+ {#if messages.filter((message) => !message.welcome).length > 0}
161
+ <button type="button" class="sd-chat-icon-button" aria-label={t('ask.newChat')} title={t('ask.newChat')} on:click={reset}>
162
+ <svg aria-hidden="true" viewBox="0 0 24 24">
163
+ <path d="M4 12h8m4 0h4M12 4v8m0 4v4" />
164
+ </svg>
165
+ </button>
166
+ {/if}
167
+ <button type="button" class="sd-chat-icon-button" aria-label={t('ask.close')} title={t('ask.close')} on:click={hide}>
168
+ <svg aria-hidden="true" viewBox="0 0 24 24">
169
+ <path d="M6 6l12 12M18 6L6 18" />
170
+ </svg>
171
+ </button>
172
+ </div>
173
+ </header>
174
+
175
+ <div bind:this={scrollEl} class="sd-chat-messages" role="log" aria-live="polite">
176
+ {#if messages.length === 0}
177
+ <div class="sd-chat-empty">
178
+ <p>{t('ask.empty')}</p>
179
+ </div>
180
+ {/if}
181
+ {#each messages as message (message.id)}
182
+ <div class="sd-chat-bubble" data-role={message.role}>
183
+ {#if message.content || (message.role === 'assistant' && loading && message.id === messages[messages.length - 1]?.id)}
184
+ <div class="sd-chat-bubble-body">
185
+ {#if !message.content}
186
+ <span class="sd-chat-typing" aria-label={t('ask.thinking')}>
187
+ <span></span><span></span><span></span>
188
+ </span>
189
+ {:else}
190
+ {message.content}
191
+ {/if}
192
+ </div>
193
+ {/if}
194
+ {#if message.citations && message.citations.length > 0}
195
+ <div class="sd-chat-citations">
196
+ {#each message.citations as citation}
197
+ <a href={citation.url} on:click={hide}>
198
+ {citation.section ?? citation.title}
199
+ </a>
200
+ {/each}
201
+ </div>
202
+ {/if}
203
+ {#if message.error}
204
+ <p class="sd-chat-error">{message.error}</p>
205
+ {/if}
206
+ </div>
207
+ {/each}
208
+ </div>
209
+
210
+ {#if showSuggestions}
211
+ <div class="sd-chat-suggestions">
212
+ {#each suggestions as suggestion}
213
+ <button type="button" on:click={() => send(suggestion)}>{suggestion}</button>
214
+ {/each}
215
+ </div>
216
+ {/if}
217
+
218
+ <form class="sd-chat-composer" on:submit|preventDefault={() => send(input)}>
219
+ <textarea
220
+ bind:this={textarea}
221
+ value={input}
222
+ rows="1"
223
+ placeholder={placeholder}
224
+ on:input={handleInput}
225
+ on:keydown={handleComposerKeydown}
226
+ ></textarea>
227
+ <button
228
+ class="sd-chat-send"
229
+ type="submit"
230
+ aria-label={t('ask.send')}
231
+ title={t('ask.send')}
232
+ disabled={!input.trim() || loading}
233
+ >
234
+ <svg aria-hidden="true" viewBox="0 0 24 24">
235
+ <path d="M4 12l16-8-6 16-2-7-8-1Z" />
236
+ </svg>
237
+ </button>
238
+ </form>
239
+ </div>
240
+ </div>
241
+ {/if}