@farming-labs/svelte-theme 0.0.1
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/package.json +57 -0
- package/src/components/AskAIDialog.svelte +342 -0
- package/src/components/Breadcrumb.svelte +47 -0
- package/src/components/Callout.svelte +28 -0
- package/src/components/DocsContent.svelte +32 -0
- package/src/components/DocsLayout.svelte +258 -0
- package/src/components/DocsPage.svelte +145 -0
- package/src/components/DocsSidebar.svelte +63 -0
- package/src/components/FloatingAIChat.svelte +474 -0
- package/src/components/MobileNav.svelte +14 -0
- package/src/components/SearchDialog.svelte +91 -0
- package/src/components/TableOfContents.svelte +69 -0
- package/src/components/ThemeToggle.svelte +40 -0
- package/src/index.d.ts +23 -0
- package/src/index.js +23 -0
- package/src/lib/renderMarkdown.js +110 -0
- package/src/themes/darksharp.js +42 -0
- package/src/themes/default.js +42 -0
- package/src/themes/pixel-border.js +38 -0
- package/styles/docs.css +2124 -0
- package/styles/pixel-border-bundle.css +6 -0
- package/styles/pixel-border.css +601 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import ThemeToggle from "./ThemeToggle.svelte";
|
|
3
|
+
import AskAIDialog from "./AskAIDialog.svelte";
|
|
4
|
+
import FloatingAIChat from "./FloatingAIChat.svelte";
|
|
5
|
+
import { page } from "$app/stores";
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
tree,
|
|
9
|
+
config = null,
|
|
10
|
+
title = undefined,
|
|
11
|
+
titleUrl = undefined,
|
|
12
|
+
themeToggle = true,
|
|
13
|
+
children,
|
|
14
|
+
} = $props();
|
|
15
|
+
|
|
16
|
+
let resolvedTitle = $derived(title ?? config?.nav?.title ?? "Docs");
|
|
17
|
+
let resolvedTitleUrl = $derived(titleUrl ?? config?.nav?.url ?? "/docs");
|
|
18
|
+
|
|
19
|
+
let sidebarOpen = $state(false);
|
|
20
|
+
let searchOpen = $state(false);
|
|
21
|
+
|
|
22
|
+
function toggleSidebar() {
|
|
23
|
+
sidebarOpen = !sidebarOpen;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function closeSidebar() {
|
|
27
|
+
sidebarOpen = false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function openSearch() {
|
|
31
|
+
searchOpen = true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function closeSearch() {
|
|
35
|
+
searchOpen = false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function handleKeydown(e) {
|
|
39
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
searchOpen = !searchOpen;
|
|
42
|
+
}
|
|
43
|
+
if (e.key === "Escape") {
|
|
44
|
+
searchOpen = false;
|
|
45
|
+
sidebarOpen = false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isActive(url) {
|
|
50
|
+
const current = $page.url.pathname;
|
|
51
|
+
const normalised = url.replace(/\/$/, '') || '/';
|
|
52
|
+
const currentNorm = current.replace(/\/$/, '') || '/';
|
|
53
|
+
return normalised === currentNorm;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const ICON_MAP = {
|
|
57
|
+
book: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>`,
|
|
58
|
+
terminal: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>`,
|
|
59
|
+
rocket: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/></svg>`,
|
|
60
|
+
settings: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>`,
|
|
61
|
+
shield: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/></svg>`,
|
|
62
|
+
puzzle: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19.439 7.85c-.049.322.059.648.289.878l1.568 1.568c.47.47.706 1.087.706 1.704s-.235 1.233-.706 1.704l-1.611 1.611a.98.98 0 0 1-.837.276c-.47-.07-.802-.48-.968-.925a2.501 2.501 0 1 0-3.214 3.214c.446.166.855.497.925.968a.979.979 0 0 1-.276.837l-1.61 1.61a2.404 2.404 0 0 1-1.705.707 2.402 2.402 0 0 1-1.704-.706l-1.568-1.568a1.026 1.026 0 0 0-.877-.29c-.493.074-.84.504-1.02.968a2.5 2.5 0 1 1-3.237-3.237c.464-.18.894-.527.967-1.02a1.026 1.026 0 0 0-.289-.877l-1.568-1.568A2.402 2.402 0 0 1 1.998 12c0-.617.236-1.234.706-1.704L4.23 8.77c.24-.24.581-.353.917-.303.515.076.84.523 1.02.968a2.501 2.501 0 1 0 3.237-3.237c-.464-.18-.894-.527-.968-1.02a1.025 1.025 0 0 1 .303-.917l1.525-1.525A2.402 2.402 0 0 1 12 1.998c.617 0 1.234.236 1.704.706l1.568 1.568c.23.23.556.338.877.29.493-.074.84-.504 1.02-.969a2.5 2.5 0 1 1 3.237 3.237c-.464.18-.894.527-.967 1.02Z"/></svg>`,
|
|
63
|
+
zap: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/></svg>`,
|
|
64
|
+
database: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5V19A9 3 0 0 0 21 19V5"/><path d="M3 12A9 3 0 0 0 21 12"/></svg>`,
|
|
65
|
+
key: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4"/><path d="m21 2-9.6 9.6"/><circle cx="7.5" cy="15.5" r="5.5"/></svg>`,
|
|
66
|
+
mail: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>`,
|
|
67
|
+
file: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>`,
|
|
68
|
+
folder: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>`,
|
|
69
|
+
link: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>`,
|
|
70
|
+
lightbulb: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/></svg>`,
|
|
71
|
+
code: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>`,
|
|
72
|
+
users: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>`,
|
|
73
|
+
globe: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg>`,
|
|
74
|
+
lock: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>`,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
function getIcon(iconKey) {
|
|
78
|
+
if (!iconKey) return null;
|
|
79
|
+
return ICON_MAP[iconKey] || null;
|
|
80
|
+
}
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<svelte:window onkeydown={handleKeydown} />
|
|
84
|
+
|
|
85
|
+
<div class="fd-layout">
|
|
86
|
+
<!-- Mobile header -->
|
|
87
|
+
<header class="fd-header">
|
|
88
|
+
<button class="fd-menu-btn" onclick={toggleSidebar} aria-label="Toggle sidebar">
|
|
89
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
90
|
+
<line x1="3" y1="6" x2="21" y2="6" />
|
|
91
|
+
<line x1="3" y1="12" x2="21" y2="12" />
|
|
92
|
+
<line x1="3" y1="18" x2="21" y2="18" />
|
|
93
|
+
</svg>
|
|
94
|
+
</button>
|
|
95
|
+
<a href={resolvedTitleUrl} class="fd-header-title">{resolvedTitle}</a>
|
|
96
|
+
<button class="fd-search-trigger-mobile" onclick={openSearch} aria-label="Search">
|
|
97
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
98
|
+
<circle cx="11" cy="11" r="8" />
|
|
99
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
|
100
|
+
</svg>
|
|
101
|
+
</button>
|
|
102
|
+
</header>
|
|
103
|
+
|
|
104
|
+
{#if sidebarOpen}
|
|
105
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
106
|
+
<div class="fd-sidebar-overlay" onclick={closeSidebar} role="presentation"></div>
|
|
107
|
+
{/if}
|
|
108
|
+
|
|
109
|
+
<aside class="fd-sidebar" class:fd-sidebar-open={sidebarOpen}>
|
|
110
|
+
<div class="fd-sidebar-header">
|
|
111
|
+
<a href={resolvedTitleUrl} class="fd-sidebar-title" onclick={closeSidebar}>
|
|
112
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
113
|
+
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
|
|
114
|
+
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
|
|
115
|
+
</svg>
|
|
116
|
+
{resolvedTitle}
|
|
117
|
+
</a>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div class="fd-sidebar-search">
|
|
121
|
+
<button class="fd-sidebar-search-btn" onclick={openSearch}>
|
|
122
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
123
|
+
<circle cx="11" cy="11" r="8" />
|
|
124
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
|
125
|
+
</svg>
|
|
126
|
+
<span>Search</span>
|
|
127
|
+
<kbd>⌘</kbd><kbd>K</kbd>
|
|
128
|
+
</button>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<nav class="fd-sidebar-nav">
|
|
132
|
+
{#if tree?.children}
|
|
133
|
+
{#each tree.children as node, i}
|
|
134
|
+
{#if node.type === "page"}
|
|
135
|
+
<a
|
|
136
|
+
href={node.url}
|
|
137
|
+
class="fd-sidebar-link fd-sidebar-top-link"
|
|
138
|
+
class:fd-sidebar-link-active={isActive(node.url)}
|
|
139
|
+
class:fd-sidebar-first-item={i === 0}
|
|
140
|
+
data-active={isActive(node.url)}
|
|
141
|
+
onclick={closeSidebar}
|
|
142
|
+
>
|
|
143
|
+
{#if getIcon(node.icon)}
|
|
144
|
+
<span class="fd-sidebar-icon">{@html getIcon(node.icon)}</span>
|
|
145
|
+
{/if}
|
|
146
|
+
{node.name}
|
|
147
|
+
</a>
|
|
148
|
+
{:else if node.type === "folder"}
|
|
149
|
+
<details class="fd-sidebar-folder" class:fd-sidebar-first-item={i === 0} open>
|
|
150
|
+
<summary class="fd-sidebar-folder-trigger">
|
|
151
|
+
<span class="fd-sidebar-folder-label">
|
|
152
|
+
{#if getIcon(node.icon)}
|
|
153
|
+
<span class="fd-sidebar-icon">{@html getIcon(node.icon)}</span>
|
|
154
|
+
{/if}
|
|
155
|
+
{node.name}
|
|
156
|
+
</span>
|
|
157
|
+
<svg class="fd-sidebar-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
158
|
+
<polyline points="6 9 12 15 18 9" />
|
|
159
|
+
</svg>
|
|
160
|
+
</summary>
|
|
161
|
+
<div class="fd-sidebar-folder-content">
|
|
162
|
+
{#if node.index}
|
|
163
|
+
<a
|
|
164
|
+
href={node.index.url}
|
|
165
|
+
class="fd-sidebar-link fd-sidebar-child-link"
|
|
166
|
+
class:fd-sidebar-link-active={isActive(node.index.url)}
|
|
167
|
+
data-active={isActive(node.index.url)}
|
|
168
|
+
onclick={closeSidebar}
|
|
169
|
+
>
|
|
170
|
+
{node.index.name}
|
|
171
|
+
</a>
|
|
172
|
+
{/if}
|
|
173
|
+
{#each node.children as child}
|
|
174
|
+
{#if child.type === "page"}
|
|
175
|
+
<a
|
|
176
|
+
href={child.url}
|
|
177
|
+
class="fd-sidebar-link fd-sidebar-child-link"
|
|
178
|
+
class:fd-sidebar-link-active={isActive(child.url)}
|
|
179
|
+
data-active={isActive(child.url)}
|
|
180
|
+
onclick={closeSidebar}
|
|
181
|
+
>
|
|
182
|
+
{child.name}
|
|
183
|
+
</a>
|
|
184
|
+
{:else if child.type === "folder"}
|
|
185
|
+
<details class="fd-sidebar-folder fd-sidebar-nested-folder" open>
|
|
186
|
+
<summary class="fd-sidebar-folder-trigger">
|
|
187
|
+
<span class="fd-sidebar-folder-label">
|
|
188
|
+
{child.name}
|
|
189
|
+
</span>
|
|
190
|
+
<svg class="fd-sidebar-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
191
|
+
<polyline points="6 9 12 15 18 9" />
|
|
192
|
+
</svg>
|
|
193
|
+
</summary>
|
|
194
|
+
<div class="fd-sidebar-folder-content">
|
|
195
|
+
{#if child.index}
|
|
196
|
+
<a
|
|
197
|
+
href={child.index.url}
|
|
198
|
+
class="fd-sidebar-link fd-sidebar-child-link"
|
|
199
|
+
class:fd-sidebar-link-active={isActive(child.index.url)}
|
|
200
|
+
data-active={isActive(child.index.url)}
|
|
201
|
+
onclick={closeSidebar}
|
|
202
|
+
>
|
|
203
|
+
{child.index.name}
|
|
204
|
+
</a>
|
|
205
|
+
{/if}
|
|
206
|
+
{#each child.children as grandchild}
|
|
207
|
+
{#if grandchild.type === "page"}
|
|
208
|
+
<a
|
|
209
|
+
href={grandchild.url}
|
|
210
|
+
class="fd-sidebar-link fd-sidebar-child-link"
|
|
211
|
+
class:fd-sidebar-link-active={isActive(grandchild.url)}
|
|
212
|
+
data-active={isActive(grandchild.url)}
|
|
213
|
+
onclick={closeSidebar}
|
|
214
|
+
>
|
|
215
|
+
{grandchild.name}
|
|
216
|
+
</a>
|
|
217
|
+
{/if}
|
|
218
|
+
{/each}
|
|
219
|
+
</div>
|
|
220
|
+
</details>
|
|
221
|
+
{/if}
|
|
222
|
+
{/each}
|
|
223
|
+
</div>
|
|
224
|
+
</details>
|
|
225
|
+
{/if}
|
|
226
|
+
{/each}
|
|
227
|
+
{/if}
|
|
228
|
+
</nav>
|
|
229
|
+
|
|
230
|
+
{#if themeToggle}
|
|
231
|
+
<div class="fd-sidebar-footer">
|
|
232
|
+
<ThemeToggle />
|
|
233
|
+
</div>
|
|
234
|
+
{/if}
|
|
235
|
+
</aside>
|
|
236
|
+
|
|
237
|
+
<main class="fd-main">
|
|
238
|
+
{@render children()}
|
|
239
|
+
</main>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
{#if config?.ai?.mode === "floating" && config?.ai?.enabled}
|
|
243
|
+
<FloatingAIChat
|
|
244
|
+
suggestedQuestions={config.ai.suggestedQuestions ?? []}
|
|
245
|
+
aiLabel={config.ai.aiLabel ?? "AI"}
|
|
246
|
+
position={config.ai.position ?? "bottom-right"}
|
|
247
|
+
floatingStyle={config.ai.floatingStyle ?? "panel"}
|
|
248
|
+
/>
|
|
249
|
+
{/if}
|
|
250
|
+
|
|
251
|
+
{#if searchOpen}
|
|
252
|
+
<AskAIDialog
|
|
253
|
+
onclose={closeSearch}
|
|
254
|
+
suggestedQuestions={config?.ai?.suggestedQuestions ?? []}
|
|
255
|
+
aiLabel={config?.ai?.aiLabel ?? "AI"}
|
|
256
|
+
hideAITab={config?.ai?.mode === "floating"}
|
|
257
|
+
/>
|
|
258
|
+
{/if}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Breadcrumb from "./Breadcrumb.svelte";
|
|
3
|
+
import TableOfContents from "./TableOfContents.svelte";
|
|
4
|
+
import { page } from "$app/stores";
|
|
5
|
+
import { onMount } from "svelte";
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
tocEnabled = true,
|
|
9
|
+
breadcrumbEnabled = true,
|
|
10
|
+
entry = "docs",
|
|
11
|
+
previousPage = null,
|
|
12
|
+
nextPage = null,
|
|
13
|
+
editOnGithub = null,
|
|
14
|
+
lastModified = null,
|
|
15
|
+
children,
|
|
16
|
+
} = $props();
|
|
17
|
+
|
|
18
|
+
let tocItems = $state([]);
|
|
19
|
+
|
|
20
|
+
onMount(() => {
|
|
21
|
+
scanHeadings();
|
|
22
|
+
wireInteractive();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
$effect(() => {
|
|
26
|
+
void $page.url.pathname;
|
|
27
|
+
scanHeadings();
|
|
28
|
+
wireInteractive();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
function scanHeadings() {
|
|
32
|
+
requestAnimationFrame(() => {
|
|
33
|
+
const container = document.querySelector(".fd-page-body");
|
|
34
|
+
if (!container) return;
|
|
35
|
+
|
|
36
|
+
const headings = container.querySelectorAll("h2[id], h3[id], h4[id]");
|
|
37
|
+
tocItems = Array.from(headings).map((el) => ({
|
|
38
|
+
title: el.textContent?.replace(/^#\s*/, "") || "",
|
|
39
|
+
url: `#${el.id}`,
|
|
40
|
+
depth: parseInt(el.tagName[1], 10),
|
|
41
|
+
}));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function wireInteractive() {
|
|
46
|
+
requestAnimationFrame(() => {
|
|
47
|
+
// Copy buttons
|
|
48
|
+
document.querySelectorAll(".fd-copy-btn").forEach((btn) => {
|
|
49
|
+
btn.onclick = () => {
|
|
50
|
+
const code = btn.getAttribute("data-code")
|
|
51
|
+
?.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"');
|
|
52
|
+
if (!code) return;
|
|
53
|
+
navigator.clipboard.writeText(code).then(() => {
|
|
54
|
+
btn.classList.add("fd-copy-btn-copied");
|
|
55
|
+
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
|
|
56
|
+
setTimeout(() => {
|
|
57
|
+
btn.classList.remove("fd-copy-btn-copied");
|
|
58
|
+
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>';
|
|
59
|
+
}, 2000);
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Tabs
|
|
65
|
+
document.querySelectorAll("[data-tabs]").forEach((tabs) => {
|
|
66
|
+
tabs.querySelectorAll(".fd-tab-trigger").forEach((trigger) => {
|
|
67
|
+
trigger.onclick = () => {
|
|
68
|
+
const val = trigger.getAttribute("data-tab-value");
|
|
69
|
+
tabs.querySelectorAll(".fd-tab-trigger").forEach((t) => {
|
|
70
|
+
t.classList.toggle("fd-tab-active", t.getAttribute("data-tab-value") === val);
|
|
71
|
+
t.setAttribute("aria-selected", String(t.getAttribute("data-tab-value") === val));
|
|
72
|
+
});
|
|
73
|
+
tabs.querySelectorAll(".fd-tab-panel").forEach((p) => {
|
|
74
|
+
p.classList.toggle("fd-tab-panel-active", p.getAttribute("data-tab-panel") === val);
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<div class="fd-page">
|
|
84
|
+
<article class="fd-page-article" id="nd-page">
|
|
85
|
+
{#if breadcrumbEnabled}
|
|
86
|
+
<Breadcrumb pathname={$page.url.pathname} {entry} />
|
|
87
|
+
{/if}
|
|
88
|
+
|
|
89
|
+
<div class="fd-page-body">
|
|
90
|
+
{@render children()}
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
{#if editOnGithub}
|
|
94
|
+
<div class="fd-edit-on-github">
|
|
95
|
+
<a href={editOnGithub} target="_blank" rel="noopener noreferrer">
|
|
96
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
97
|
+
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7" />
|
|
98
|
+
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z" />
|
|
99
|
+
</svg>
|
|
100
|
+
Edit on GitHub
|
|
101
|
+
</a>
|
|
102
|
+
{#if lastModified}
|
|
103
|
+
<span class="fd-last-modified">Last updated: {lastModified}</span>
|
|
104
|
+
{/if}
|
|
105
|
+
</div>
|
|
106
|
+
{/if}
|
|
107
|
+
|
|
108
|
+
{#if previousPage || nextPage}
|
|
109
|
+
<nav class="fd-page-nav" aria-label="Page navigation">
|
|
110
|
+
{#if previousPage}
|
|
111
|
+
<a href={previousPage.url} class="fd-page-nav-card fd-page-nav-prev">
|
|
112
|
+
<span class="fd-page-nav-label">
|
|
113
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
114
|
+
<polyline points="15 18 9 12 15 6" />
|
|
115
|
+
</svg>
|
|
116
|
+
Previous
|
|
117
|
+
</span>
|
|
118
|
+
<span class="fd-page-nav-title">{previousPage.name}</span>
|
|
119
|
+
</a>
|
|
120
|
+
{:else}
|
|
121
|
+
<div></div>
|
|
122
|
+
{/if}
|
|
123
|
+
{#if nextPage}
|
|
124
|
+
<a href={nextPage.url} class="fd-page-nav-card fd-page-nav-next">
|
|
125
|
+
<span class="fd-page-nav-label">
|
|
126
|
+
Next
|
|
127
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
128
|
+
<polyline points="9 18 15 12 9 6" />
|
|
129
|
+
</svg>
|
|
130
|
+
</span>
|
|
131
|
+
<span class="fd-page-nav-title">{nextPage.name}</span>
|
|
132
|
+
</a>
|
|
133
|
+
{:else}
|
|
134
|
+
<div></div>
|
|
135
|
+
{/if}
|
|
136
|
+
</nav>
|
|
137
|
+
{/if}
|
|
138
|
+
</article>
|
|
139
|
+
|
|
140
|
+
{#if tocEnabled}
|
|
141
|
+
<aside class="fd-toc">
|
|
142
|
+
<TableOfContents items={tocItems} />
|
|
143
|
+
</aside>
|
|
144
|
+
{/if}
|
|
145
|
+
</div>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* DocsSidebar — Standalone sidebar navigation component.
|
|
4
|
+
* Use this if you want more control over the layout.
|
|
5
|
+
*/
|
|
6
|
+
import { page } from "$app/stores";
|
|
7
|
+
|
|
8
|
+
let { tree, onnavigate } = $props();
|
|
9
|
+
|
|
10
|
+
$effect(() => {
|
|
11
|
+
// Auto-highlight current page in sidebar
|
|
12
|
+
});
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<nav class="fd-sidebar-nav">
|
|
16
|
+
{#if tree?.children}
|
|
17
|
+
{#each tree.children as node}
|
|
18
|
+
{#if node.type === "page"}
|
|
19
|
+
<a
|
|
20
|
+
href={node.url}
|
|
21
|
+
class="fd-sidebar-link"
|
|
22
|
+
class:fd-sidebar-link-active={$page.url.pathname === node.url}
|
|
23
|
+
onclick={onnavigate}
|
|
24
|
+
>
|
|
25
|
+
{node.name}
|
|
26
|
+
</a>
|
|
27
|
+
{:else if node.type === "folder"}
|
|
28
|
+
<details class="fd-sidebar-folder" open>
|
|
29
|
+
<summary class="fd-sidebar-folder-trigger">
|
|
30
|
+
{node.name}
|
|
31
|
+
<svg class="fd-sidebar-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
32
|
+
<polyline points="6 9 12 15 18 9" />
|
|
33
|
+
</svg>
|
|
34
|
+
</summary>
|
|
35
|
+
<div class="fd-sidebar-folder-content">
|
|
36
|
+
{#if node.index}
|
|
37
|
+
<a
|
|
38
|
+
href={node.index.url}
|
|
39
|
+
class="fd-sidebar-link"
|
|
40
|
+
class:fd-sidebar-link-active={$page.url.pathname === node.index.url}
|
|
41
|
+
onclick={onnavigate}
|
|
42
|
+
>
|
|
43
|
+
{node.index.name}
|
|
44
|
+
</a>
|
|
45
|
+
{/if}
|
|
46
|
+
{#each node.children as child}
|
|
47
|
+
{#if child.type === "page"}
|
|
48
|
+
<a
|
|
49
|
+
href={child.url}
|
|
50
|
+
class="fd-sidebar-link"
|
|
51
|
+
class:fd-sidebar-link-active={$page.url.pathname === child.url}
|
|
52
|
+
onclick={onnavigate}
|
|
53
|
+
>
|
|
54
|
+
{child.name}
|
|
55
|
+
</a>
|
|
56
|
+
{/if}
|
|
57
|
+
{/each}
|
|
58
|
+
</div>
|
|
59
|
+
</details>
|
|
60
|
+
{/if}
|
|
61
|
+
{/each}
|
|
62
|
+
{/if}
|
|
63
|
+
</nav>
|