@farming-labs/svelte-theme 0.0.3-beta.2 → 0.0.3-beta.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/svelte-theme",
3
- "version": "0.0.3-beta.2",
3
+ "version": "0.0.3-beta.3",
4
4
  "description": "Svelte UI components for @farming-labs/docs — layout, sidebar, TOC, search, and theme toggle",
5
5
  "keywords": [
6
6
  "docs",
@@ -82,8 +82,8 @@
82
82
  "dependencies": {
83
83
  "gray-matter": "^4.0.3",
84
84
  "sugar-high": "^0.9.5",
85
- "@farming-labs/docs": "0.0.3-beta.2",
86
- "@farming-labs/svelte": "0.0.3-beta.2"
85
+ "@farming-labs/docs": "0.0.3-beta.3",
86
+ "@farming-labs/svelte": "0.0.3-beta.3"
87
87
  },
88
88
  "peerDependencies": {
89
89
  "svelte": ">=5.0.0"
@@ -1,8 +1,18 @@
1
1
  <script>
2
2
  import DocsPage from "./DocsPage.svelte";
3
+ import { onMount, onDestroy } from "svelte";
4
+
5
+ const DEFAULT_OPEN_PROVIDERS = [
6
+ { name: "ChatGPT", urlTemplate: "https://chatgpt.com/?hints=search&q=Read+{mdxUrl},+I+want+to+ask+questions+about+it." },
7
+ { name: "Claude", urlTemplate: "https://claude.ai/new?q=Read+{mdxUrl},+I+want+to+ask+questions+about+it." },
8
+ ];
3
9
 
4
10
  let { data, config = null } = $props();
5
11
 
12
+ let openDropdownMenu = $state(false);
13
+ let copyLabel = $state("Copy page");
14
+ let copied = $state(false);
15
+
6
16
  let titleSuffix = $derived(
7
17
  config?.metadata?.titleTemplate
8
18
  ? config.metadata.titleTemplate.replace("%s", "")
@@ -14,7 +24,7 @@
14
24
  );
15
25
 
16
26
  let tocStyle = $derived(
17
- config?.theme?.ui?.layout?.toc?.style ?? "default"
27
+ (config?.theme?.ui?.layout?.toc?.style === "directional") ? "directional" : "default"
18
28
  );
19
29
 
20
30
  let breadcrumbEnabled = $derived.by(() => {
@@ -37,6 +47,146 @@
37
47
  if (typeof cfg === "object" && cfg !== null) return cfg.enabled !== false;
38
48
  return false;
39
49
  });
50
+
51
+ let copyMarkdownEnabled = $derived.by(() => {
52
+ const pa = config?.pageActions;
53
+ if (!pa) return false;
54
+ const cm = pa.copyMarkdown;
55
+ if (cm === true) return true;
56
+ if (typeof cm === "object" && cm !== null) return cm.enabled !== false;
57
+ return false;
58
+ });
59
+
60
+ let openDocsEnabled = $derived.by(() => {
61
+ const pa = config?.pageActions;
62
+ if (!pa) return false;
63
+ const od = pa.openDocs;
64
+ if (od === true) return true;
65
+ if (typeof od === "object" && od !== null) return od.enabled !== false;
66
+ return false;
67
+ });
68
+
69
+ let openDocsProviders = $derived.by(() => {
70
+ const pa = config?.pageActions;
71
+ const od = pa && typeof pa === "object" && pa.openDocs != null ? pa.openDocs : null;
72
+ const list = od && typeof od === "object" && "providers" in od ? od.providers : undefined;
73
+ if (Array.isArray(list) && list.length > 0) {
74
+ const mapped = list
75
+ .map((p) => ({
76
+ name: typeof p?.name === "string" ? p.name : "Open",
77
+ urlTemplate: typeof p?.urlTemplate === "string" ? p.urlTemplate : "",
78
+ }))
79
+ .filter((p) => p.urlTemplate.length > 0);
80
+ if (mapped.length > 0) return mapped;
81
+ }
82
+ return DEFAULT_OPEN_PROVIDERS;
83
+ });
84
+
85
+ let pageActionsPosition = $derived(
86
+ typeof config?.pageActions === "object" && config?.pageActions !== null && config?.pageActions.position
87
+ ? config.pageActions.position
88
+ : "below-title"
89
+ );
90
+
91
+ let pageActionsAlignment = $derived(
92
+ typeof config?.pageActions === "object" && config?.pageActions !== null && config?.pageActions.alignment
93
+ ? config.pageActions.alignment
94
+ : "left"
95
+ );
96
+
97
+ let lastUpdatedConfig = $derived.by(() => {
98
+ const lu = config?.lastUpdated;
99
+ if (lu === false) return { enabled: false, position: "footer" };
100
+ if (lu === true || lu === undefined) return { enabled: true, position: "footer" };
101
+ const o = lu;
102
+ return {
103
+ enabled: o.enabled !== false,
104
+ position: o.position ?? "footer",
105
+ };
106
+ });
107
+
108
+ let showLastUpdatedInFooter = $derived(
109
+ !!data.lastModified && lastUpdatedConfig.enabled && lastUpdatedConfig.position === "footer"
110
+ );
111
+ let showLastUpdatedBelowTitle = $derived(
112
+ !!data.lastModified && lastUpdatedConfig.enabled && lastUpdatedConfig.position === "below-title"
113
+ );
114
+
115
+ let htmlWithoutFirstH1 = $derived(
116
+ (data.html || "").replace(/<h1[^>]*>[\s\S]*?<\/h1>\s*/i, "")
117
+ );
118
+
119
+ let showPageActions = $derived(
120
+ (copyMarkdownEnabled || openDocsEnabled) && openDocsProviders.length >= 0
121
+ );
122
+ let showActionsAbove = $derived(pageActionsPosition === "above-title" && showPageActions);
123
+ let showActionsBelow = $derived(pageActionsPosition === "below-title" && showPageActions);
124
+
125
+ function handleCopyPage() {
126
+ let text = "";
127
+ if (data.rawMarkdown && typeof data.rawMarkdown === "string" && data.rawMarkdown.length > 0) {
128
+ text = data.rawMarkdown;
129
+ } else {
130
+ const article = document.querySelector("#nd-page");
131
+ if (article) text = article.innerText || "";
132
+ }
133
+ if (!text) return;
134
+ navigator.clipboard.writeText(text).then(
135
+ () => {
136
+ copyLabel = "Copied!";
137
+ copied = true;
138
+ setTimeout(() => {
139
+ copyLabel = "Copy page";
140
+ copied = false;
141
+ }, 2000);
142
+ },
143
+ () => {
144
+ copyLabel = "Copy failed";
145
+ setTimeout(() => { copyLabel = "Copy page"; }, 2000);
146
+ }
147
+ );
148
+ }
149
+
150
+ function toggleDropdown() {
151
+ openDropdownMenu = !openDropdownMenu;
152
+ }
153
+
154
+ function closeDropdown() {
155
+ openDropdownMenu = false;
156
+ }
157
+
158
+ function openInProvider(provider) {
159
+ const pathname = typeof window !== "undefined" ? window.location.pathname : "";
160
+ const pageUrl = typeof window !== "undefined" ? window.location.href : "";
161
+ const mdxUrl = typeof window !== "undefined"
162
+ ? window.location.origin + pathname + (pathname.endsWith("/") ? "page.mdx" : ".mdx")
163
+ : "";
164
+ const githubUrl = data.editOnGithub || "";
165
+ const url = provider.urlTemplate
166
+ .replace(/\{url\}/g, encodeURIComponent(pageUrl))
167
+ .replace(/\{mdxUrl\}/g, encodeURIComponent(mdxUrl))
168
+ .replace(/\{githubUrl\}/g, githubUrl);
169
+ if (typeof window !== "undefined") window.open(url, "_blank", "noopener,noreferrer");
170
+ closeDropdown();
171
+ }
172
+
173
+ function handleClickOutside(e) {
174
+ const target = e.target;
175
+ if (openDropdownMenu && !target?.closest?.(".fd-page-action-dropdown")) {
176
+ closeDropdown();
177
+ }
178
+ }
179
+
180
+ onMount(() => {
181
+ if (typeof document !== "undefined") {
182
+ document.addEventListener("click", handleClickOutside);
183
+ }
184
+ });
185
+ onDestroy(() => {
186
+ if (typeof document !== "undefined") {
187
+ document.removeEventListener("click", handleClickOutside);
188
+ }
189
+ });
40
190
  </script>
41
191
 
42
192
  <svelte:head>
@@ -54,13 +204,145 @@
54
204
  previousPage={data.previousPage}
55
205
  nextPage={data.nextPage}
56
206
  editOnGithub={showEditOnGithub ? data.editOnGithub : null}
57
- lastModified={showLastModified ? data.lastModified : null}
207
+ lastModified={showLastUpdatedInFooter ? data.lastModified : null}
58
208
  {llmsTxtEnabled}
59
209
  >
60
210
  {#snippet children()}
61
- {#if data.description}
62
- <p class="fd-page-description">{data.description}</p>
63
- {/if}
64
- {@html data.html}
211
+ <div class="fd-docs-content">
212
+ {#if showActionsAbove}
213
+ <div
214
+ class="fd-page-actions"
215
+ data-page-actions
216
+ data-actions-alignment={pageActionsAlignment}
217
+ >
218
+ {#if copyMarkdownEnabled}
219
+ <button
220
+ type="button"
221
+ class="fd-page-action-btn"
222
+ aria-label="Copy page content"
223
+ data-copied={copied}
224
+ onclick={handleCopyPage}
225
+ >
226
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
227
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
228
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
229
+ </svg>
230
+ <span>{copyLabel}</span>
231
+ </button>
232
+ {/if}
233
+ {#if openDocsEnabled && openDocsProviders.length > 0}
234
+ <div class="fd-page-action-dropdown">
235
+ <button
236
+ type="button"
237
+ class="fd-page-action-btn"
238
+ aria-expanded={openDropdownMenu}
239
+ aria-haspopup="true"
240
+ onclick={toggleDropdown}
241
+ >
242
+ <span>Open in</span>
243
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
244
+ <polyline points="6 9 12 15 18 9" />
245
+ </svg>
246
+ </button>
247
+ <div
248
+ class="fd-page-action-menu"
249
+ role="menu"
250
+ hidden={!openDropdownMenu}
251
+ >
252
+ {#each openDocsProviders as provider}
253
+ <button
254
+ type="button"
255
+ role="menuitem"
256
+ class="fd-page-action-menu-item"
257
+ onclick={() => openInProvider(provider)}
258
+ >
259
+ <span class="fd-page-action-menu-label">Open in {provider.name}</span>
260
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
261
+ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
262
+ <polyline points="15 3 21 3 21 9" />
263
+ <line x1="10" y1="14" x2="21" y2="3" />
264
+ </svg>
265
+ </button>
266
+ {/each}
267
+ </div>
268
+ </div>
269
+ {/if}
270
+ </div>
271
+ {/if}
272
+
273
+ <h1 class="fd-page-title">{data.title}</h1>
274
+ {#if data.description}
275
+ <p class="fd-page-description">{data.description}</p>
276
+ {/if}
277
+ {#if showLastUpdatedBelowTitle && data.lastModified}
278
+ <p class="fd-last-modified fd-last-modified-below-title">
279
+ Last updated: {data.lastModified}
280
+ </p>
281
+ {/if}
282
+
283
+ {#if showActionsBelow}
284
+ <hr class="fd-page-actions-divider" aria-hidden="true" />
285
+ <div
286
+ class="fd-page-actions"
287
+ data-page-actions
288
+ data-actions-alignment={pageActionsAlignment}
289
+ >
290
+ {#if copyMarkdownEnabled}
291
+ <button
292
+ type="button"
293
+ class="fd-page-action-btn"
294
+ aria-label="Copy page content"
295
+ data-copied={copied}
296
+ onclick={handleCopyPage}
297
+ >
298
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
299
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
300
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
301
+ </svg>
302
+ <span>{copyLabel}</span>
303
+ </button>
304
+ {/if}
305
+ {#if openDocsEnabled && openDocsProviders.length > 0}
306
+ <div class="fd-page-action-dropdown">
307
+ <button
308
+ type="button"
309
+ class="fd-page-action-btn"
310
+ aria-expanded={openDropdownMenu}
311
+ aria-haspopup="true"
312
+ onclick={toggleDropdown}
313
+ >
314
+ <span>Open in</span>
315
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
316
+ <polyline points="6 9 12 15 18 9" />
317
+ </svg>
318
+ </button>
319
+ <div
320
+ class="fd-page-action-menu"
321
+ role="menu"
322
+ hidden={!openDropdownMenu}
323
+ >
324
+ {#each openDocsProviders as provider}
325
+ <button
326
+ type="button"
327
+ role="menuitem"
328
+ class="fd-page-action-menu-item"
329
+ onclick={() => openInProvider(provider)}
330
+ >
331
+ <span class="fd-page-action-menu-label">Open in {provider.name}</span>
332
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
333
+ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
334
+ <polyline points="15 3 21 3 21 9" />
335
+ <line x1="10" y1="14" x2="21" y2="3" />
336
+ </svg>
337
+ </button>
338
+ {/each}
339
+ </div>
340
+ </div>
341
+ {/if}
342
+ </div>
343
+ {/if}
344
+
345
+ {@html htmlWithoutFirstH1}
346
+ </div>
65
347
  {/snippet}
66
348
  </DocsPage>
@@ -1,5 +1,6 @@
1
1
  <script>
2
2
  import ThemeToggle from "./ThemeToggle.svelte";
3
+ import SearchDialog from "./SearchDialog.svelte";
3
4
  import AskAIDialog from "./AskAIDialog.svelte";
4
5
  import FloatingAIChat from "./FloatingAIChat.svelte";
5
6
  import { page } from "$app/stores";
@@ -396,10 +397,5 @@
396
397
  {/if}
397
398
 
398
399
  {#if searchOpen}
399
- <AskAIDialog
400
- onclose={closeSearch}
401
- suggestedQuestions={config?.ai?.suggestedQuestions ?? []}
402
- aiLabel={config?.ai?.aiLabel ?? "AI"}
403
- hideAITab={config?.ai?.mode === "floating"}
404
- />
400
+ <SearchDialog onclose={closeSearch} />
405
401
  {/if}
@@ -268,7 +268,7 @@
268
268
  class="fd-ai-floating-trigger"
269
269
  style={btnStyle}
270
270
  >
271
- <svelte:component this={triggerComponent} />
271
+ <svelte:component this={triggerComponent} aiLabel={label} />
272
272
  </div>
273
273
  {:else}
274
274
  <button
@@ -486,7 +486,7 @@
486
486
  class="fd-ai-floating-trigger"
487
487
  style={btnStyle}
488
488
  >
489
- <svelte:component this={triggerComponent} />
489
+ <svelte:component this={triggerComponent} aiLabel={label} />
490
490
  </div>
491
491
  {:else}
492
492
  <button