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.
- package/LICENSE +5 -0
- package/README.md +85 -0
- package/dist/ai/openai-compatible.d.ts +13 -0
- package/dist/ai/openai-compatible.d.ts.map +1 -0
- package/dist/ai/openai-compatible.js +92 -0
- package/dist/ai/providers.d.ts +20 -0
- package/dist/ai/providers.d.ts.map +1 -0
- package/dist/ai/providers.js +185 -0
- package/dist/ai/rate-limit.d.ts +18 -0
- package/dist/ai/rate-limit.d.ts.map +1 -0
- package/dist/ai/rate-limit.js +52 -0
- package/dist/ai/response.d.ts +4 -0
- package/dist/ai/response.d.ts.map +1 -0
- package/dist/ai/response.js +151 -0
- package/dist/ai/runtime.d.ts +32 -0
- package/dist/ai/runtime.d.ts.map +1 -0
- package/dist/ai/runtime.js +111 -0
- package/dist/ai/types.d.ts +93 -0
- package/dist/ai/types.d.ts.map +1 -0
- package/dist/ai/types.js +1 -0
- package/dist/ai.d.ts +7 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +6 -0
- package/dist/ambient.d.ts +5 -0
- package/dist/cloudflare.d.ts +29 -0
- package/dist/cloudflare.d.ts.map +1 -0
- package/dist/cloudflare.js +149 -0
- package/dist/config.d.ts +373 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +299 -0
- package/dist/core/checks.d.ts +4 -0
- package/dist/core/checks.d.ts.map +1 -0
- package/dist/core/checks.js +267 -0
- package/dist/core/config.d.ts +6 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +178 -0
- package/dist/core/content.d.ts +7 -0
- package/dist/core/content.d.ts.map +1 -0
- package/dist/core/content.js +340 -0
- package/dist/core/i18n.d.ts +5 -0
- package/dist/core/i18n.d.ts.map +1 -0
- package/dist/core/i18n.js +117 -0
- package/dist/core/links.d.ts +5 -0
- package/dist/core/links.d.ts.map +1 -0
- package/dist/core/links.js +60 -0
- package/dist/core/messages.d.ts +149 -0
- package/dist/core/messages.d.ts.map +1 -0
- package/dist/core/messages.js +163 -0
- package/dist/core/navigation.d.ts +5 -0
- package/dist/core/navigation.d.ts.map +1 -0
- package/dist/core/navigation.js +144 -0
- package/dist/core/routes.d.ts +36 -0
- package/dist/core/routes.d.ts.map +1 -0
- package/dist/core/routes.js +152 -0
- package/dist/core/search.d.ts +5 -0
- package/dist/core/search.d.ts.map +1 -0
- package/dist/core/search.js +41 -0
- package/dist/core/types.d.ts +468 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +4 -0
- package/dist/core/utils.d.ts +13 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +60 -0
- package/dist/core.d.ts +9 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +8 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/mdx/ast.d.ts +32 -0
- package/dist/mdx/ast.d.ts.map +1 -0
- package/dist/mdx/ast.js +148 -0
- package/dist/mdx/code.d.ts +17 -0
- package/dist/mdx/code.d.ts.map +1 -0
- package/dist/mdx/code.js +327 -0
- package/dist/mdx/compile.d.ts +29 -0
- package/dist/mdx/compile.d.ts.map +1 -0
- package/dist/mdx/compile.js +59 -0
- package/dist/mdx/diff.d.ts +9 -0
- package/dist/mdx/diff.d.ts.map +1 -0
- package/dist/mdx/diff.js +171 -0
- package/dist/mdx/headings.d.ts +4 -0
- package/dist/mdx/headings.d.ts.map +1 -0
- package/dist/mdx/headings.js +42 -0
- package/dist/mdx/links.d.ts +4 -0
- package/dist/mdx/links.d.ts.map +1 -0
- package/dist/mdx/links.js +144 -0
- package/dist/mdx/utils.d.ts +10 -0
- package/dist/mdx/utils.d.ts.map +1 -0
- package/dist/mdx/utils.js +64 -0
- package/dist/og/image.d.ts +25 -0
- package/dist/og/image.d.ts.map +1 -0
- package/dist/og/image.js +224 -0
- package/dist/og/metadata.d.ts +7 -0
- package/dist/og/metadata.d.ts.map +1 -0
- package/dist/og/metadata.js +198 -0
- package/dist/og/response.d.ts +4 -0
- package/dist/og/response.d.ts.map +1 -0
- package/dist/og/response.js +49 -0
- package/dist/og/rss.d.ts +4 -0
- package/dist/og/rss.d.ts.map +1 -0
- package/dist/og/rss.js +96 -0
- package/dist/og/sitemap.d.ts +7 -0
- package/dist/og/sitemap.d.ts.map +1 -0
- package/dist/og/sitemap.js +90 -0
- package/dist/og/types.d.ts +65 -0
- package/dist/og/types.d.ts.map +1 -0
- package/dist/og/types.js +1 -0
- package/dist/og.d.ts +6 -0
- package/dist/og.d.ts.map +1 -0
- package/dist/og.js +5 -0
- package/dist/routes.d.ts +2 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +1 -0
- package/dist/search/algolia.d.ts +10 -0
- package/dist/search/algolia.d.ts.map +1 -0
- package/dist/search/algolia.js +82 -0
- package/dist/search/cloudflare-sync.d.ts +5 -0
- package/dist/search/cloudflare-sync.d.ts.map +1 -0
- package/dist/search/cloudflare-sync.js +169 -0
- package/dist/search/cloudflare.d.ts +11 -0
- package/dist/search/cloudflare.d.ts.map +1 -0
- package/dist/search/cloudflare.js +126 -0
- package/dist/search/local.d.ts +9 -0
- package/dist/search/local.d.ts.map +1 -0
- package/dist/search/local.js +165 -0
- package/dist/search/runtime.d.ts +41 -0
- package/dist/search/runtime.d.ts.map +1 -0
- package/dist/search/runtime.js +152 -0
- package/dist/search/types.d.ts +172 -0
- package/dist/search/types.d.ts.map +1 -0
- package/dist/search/types.js +1 -0
- package/dist/search/typesense.d.ts +11 -0
- package/dist/search/typesense.d.ts.map +1 -0
- package/dist/search/typesense.js +91 -0
- package/dist/search/utils.d.ts +11 -0
- package/dist/search/utils.d.ts.map +1 -0
- package/dist/search/utils.js +93 -0
- package/dist/search.d.ts +8 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +7 -0
- package/dist/svelte.d.ts +27 -0
- package/dist/svelte.d.ts.map +1 -0
- package/dist/svelte.js +45 -0
- package/dist/testing.d.ts +3 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +23 -0
- package/dist/theme/Article.svelte +145 -0
- package/dist/theme/Article.svelte.d.ts +36 -0
- package/dist/theme/Article.svelte.d.ts.map +1 -0
- package/dist/theme/AskAiPanel.svelte +241 -0
- package/dist/theme/AskAiPanel.svelte.d.ts +31 -0
- package/dist/theme/AskAiPanel.svelte.d.ts.map +1 -0
- package/dist/theme/Brand.svelte +19 -0
- package/dist/theme/Brand.svelte.d.ts +22 -0
- package/dist/theme/Brand.svelte.d.ts.map +1 -0
- package/dist/theme/Button.svelte +34 -0
- package/dist/theme/Button.svelte.d.ts +41 -0
- package/dist/theme/Button.svelte.d.ts.map +1 -0
- package/dist/theme/Checkbox.svelte +63 -0
- package/dist/theme/Checkbox.svelte.d.ts +45 -0
- package/dist/theme/Checkbox.svelte.d.ts.map +1 -0
- package/dist/theme/CodeBlock.svelte +6 -0
- package/dist/theme/CodeBlock.svelte.d.ts +22 -0
- package/dist/theme/CodeBlock.svelte.d.ts.map +1 -0
- package/dist/theme/DocPage.svelte +15 -0
- package/dist/theme/DocPage.svelte.d.ts +33 -0
- package/dist/theme/DocPage.svelte.d.ts.map +1 -0
- package/dist/theme/DocsApp.svelte +194 -0
- package/dist/theme/DocsApp.svelte.d.ts +60 -0
- package/dist/theme/DocsApp.svelte.d.ts.map +1 -0
- package/dist/theme/DocsLayout.svelte +99 -0
- package/dist/theme/DocsLayout.svelte.d.ts +39 -0
- package/dist/theme/DocsLayout.svelte.d.ts.map +1 -0
- package/dist/theme/DocsShell.svelte +91 -0
- package/dist/theme/DocsShell.svelte.d.ts +34 -0
- package/dist/theme/DocsShell.svelte.d.ts.map +1 -0
- package/dist/theme/ErrorPage.svelte +113 -0
- package/dist/theme/ErrorPage.svelte.d.ts +34 -0
- package/dist/theme/ErrorPage.svelte.d.ts.map +1 -0
- package/dist/theme/FloatingToolbar.svelte +8 -0
- package/dist/theme/FloatingToolbar.svelte.d.ts +22 -0
- package/dist/theme/FloatingToolbar.svelte.d.ts.map +1 -0
- package/dist/theme/Footer.svelte +18 -0
- package/dist/theme/Footer.svelte.d.ts +24 -0
- package/dist/theme/Footer.svelte.d.ts.map +1 -0
- package/dist/theme/FooterLinks.svelte +37 -0
- package/dist/theme/FooterLinks.svelte.d.ts +22 -0
- package/dist/theme/FooterLinks.svelte.d.ts.map +1 -0
- package/dist/theme/FormField.svelte +41 -0
- package/dist/theme/FormField.svelte.d.ts +35 -0
- package/dist/theme/FormField.svelte.d.ts.map +1 -0
- package/dist/theme/HomePage.svelte +284 -0
- package/dist/theme/HomePage.svelte.d.ts +65 -0
- package/dist/theme/HomePage.svelte.d.ts.map +1 -0
- package/dist/theme/Input.svelte +36 -0
- package/dist/theme/Input.svelte.d.ts +34 -0
- package/dist/theme/Input.svelte.d.ts.map +1 -0
- package/dist/theme/LayoutShell.svelte +120 -0
- package/dist/theme/LayoutShell.svelte.d.ts +43 -0
- package/dist/theme/LayoutShell.svelte.d.ts.map +1 -0
- package/dist/theme/LocalizedLink.svelte +21 -0
- package/dist/theme/LocalizedLink.svelte.d.ts +38 -0
- package/dist/theme/LocalizedLink.svelte.d.ts.map +1 -0
- package/dist/theme/MobileNav.svelte +20 -0
- package/dist/theme/MobileNav.svelte.d.ts +26 -0
- package/dist/theme/MobileNav.svelte.d.ts.map +1 -0
- package/dist/theme/Navbar.svelte +69 -0
- package/dist/theme/Navbar.svelte.d.ts +29 -0
- package/dist/theme/Navbar.svelte.d.ts.map +1 -0
- package/dist/theme/PageLayout.svelte +49 -0
- package/dist/theme/PageLayout.svelte.d.ts +34 -0
- package/dist/theme/PageLayout.svelte.d.ts.map +1 -0
- package/dist/theme/PageShell.svelte +98 -0
- package/dist/theme/PageShell.svelte.d.ts +35 -0
- package/dist/theme/PageShell.svelte.d.ts.map +1 -0
- package/dist/theme/PageTools.svelte +87 -0
- package/dist/theme/PageTools.svelte.d.ts +25 -0
- package/dist/theme/PageTools.svelte.d.ts.map +1 -0
- package/dist/theme/RenderError.svelte +62 -0
- package/dist/theme/RenderError.svelte.d.ts +31 -0
- package/dist/theme/RenderError.svelte.d.ts.map +1 -0
- package/dist/theme/RootLayout.svelte +274 -0
- package/dist/theme/RootLayout.svelte.d.ts +47 -0
- package/dist/theme/RootLayout.svelte.d.ts.map +1 -0
- package/dist/theme/RouteRenderError.svelte +53 -0
- package/dist/theme/RouteRenderError.svelte.d.ts +34 -0
- package/dist/theme/RouteRenderError.svelte.d.ts.map +1 -0
- package/dist/theme/SafeRenderError.svelte +49 -0
- package/dist/theme/SafeRenderError.svelte.d.ts +33 -0
- package/dist/theme/SafeRenderError.svelte.d.ts.map +1 -0
- package/dist/theme/ScopeSwitcher.svelte +107 -0
- package/dist/theme/ScopeSwitcher.svelte.d.ts +26 -0
- package/dist/theme/ScopeSwitcher.svelte.d.ts.map +1 -0
- package/dist/theme/SearchDialog.svelte +200 -0
- package/dist/theme/SearchDialog.svelte.d.ts +31 -0
- package/dist/theme/SearchDialog.svelte.d.ts.map +1 -0
- package/dist/theme/Select.svelte +37 -0
- package/dist/theme/Select.svelte.d.ts +42 -0
- package/dist/theme/Select.svelte.d.ts.map +1 -0
- package/dist/theme/SidebarTree.svelte +148 -0
- package/dist/theme/SidebarTree.svelte.d.ts +24 -0
- package/dist/theme/SidebarTree.svelte.d.ts.map +1 -0
- package/dist/theme/SocialNav.svelte +17 -0
- package/dist/theme/SocialNav.svelte.d.ts +22 -0
- package/dist/theme/SocialNav.svelte.d.ts.map +1 -0
- package/dist/theme/TableOfContents.svelte +73 -0
- package/dist/theme/TableOfContents.svelte.d.ts +25 -0
- package/dist/theme/TableOfContents.svelte.d.ts.map +1 -0
- package/dist/theme/Textarea.svelte +33 -0
- package/dist/theme/Textarea.svelte.d.ts +33 -0
- package/dist/theme/Textarea.svelte.d.ts.map +1 -0
- package/dist/theme/ThemeInit.svelte +16 -0
- package/dist/theme/ThemeInit.svelte.d.ts +23 -0
- package/dist/theme/ThemeInit.svelte.d.ts.map +1 -0
- package/dist/theme/ThemeToggle.svelte +37 -0
- package/dist/theme/ThemeToggle.svelte.d.ts +23 -0
- package/dist/theme/ThemeToggle.svelte.d.ts.map +1 -0
- package/dist/theme/TopNav.svelte +23 -0
- package/dist/theme/TopNav.svelte.d.ts +22 -0
- package/dist/theme/TopNav.svelte.d.ts.map +1 -0
- package/dist/theme/base.css +240 -0
- package/dist/theme/defaults.d.ts +4 -0
- package/dist/theme/defaults.d.ts.map +1 -0
- package/dist/theme/defaults.js +55 -0
- package/dist/theme/forms.d.ts +4 -0
- package/dist/theme/forms.d.ts.map +1 -0
- package/dist/theme/forms.js +3 -0
- package/dist/theme/headless.d.ts +62 -0
- package/dist/theme/headless.d.ts.map +1 -0
- package/dist/theme/headless.js +1064 -0
- package/dist/theme/index.d.ts +41 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +38 -0
- package/dist/theme/portal.d.ts +6 -0
- package/dist/theme/portal.d.ts.map +1 -0
- package/dist/theme/portal.js +59 -0
- package/dist/theme/styles.css +4232 -0
- package/dist/theme/types.d.ts +363 -0
- package/dist/theme/types.d.ts.map +1 -0
- package/dist/theme/types.js +1 -0
- package/dist/virtual.d.ts +65 -0
- package/dist/vite.d.ts +16 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +386 -0
- 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
|
+
}
|
package/dist/search.d.ts
ADDED
|
@@ -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';
|
package/dist/svelte.d.ts
ADDED
|
@@ -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 @@
|
|
|
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"}
|
package/dist/testing.js
ADDED
|
@@ -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}
|