@farming-labs/svelte 0.1.58 → 0.1.59

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.
@@ -9,14 +9,17 @@
9
9
  * - Tables, lists, inline formatting, headings with anchor IDs
10
10
  */
11
11
  import { type DocsTheme } from "@farming-labs/docs";
12
+ import { type SerializedOpenDocsProvider } from "@farming-labs/docs/server";
12
13
  interface RenderMarkdownOptions {
13
14
  theme?: DocsTheme;
15
+ icons?: Record<string, string>;
16
+ openDocsProviders?: SerializedOpenDocsProvider[];
14
17
  }
15
18
  /**
16
19
  * Render a markdown string to HTML with full syntax highlighting,
17
20
  * callouts, tables, tabs, and copy-to-clipboard support.
18
21
  *
19
- * Designed for server-side use in SvelteKit `+page.server` loaders.
22
+ * Designed for server-side use in SvelteKit page loaders.
20
23
  */
21
24
  export declare function renderMarkdown(content: string, options?: RenderMarkdownOptions): Promise<string>;
22
25
  export {};
package/dist/markdown.js CHANGED
@@ -9,8 +9,9 @@
9
9
  * - Tables, lists, inline formatting, headings with anchor IDs
10
10
  */
11
11
  import { resolveDocsAgentMdxContent } from "@farming-labs/docs";
12
+ import { parsePromptStringArray, resolvePromptProviderChoices, sanitizePromptText, } from "@farming-labs/docs/server";
12
13
  import { createHighlighter } from "shiki";
13
- let highlighterPromise;
14
+ let highlighterPromise = null;
14
15
  function getHighlighter() {
15
16
  if (!highlighterPromise) {
16
17
  highlighterPromise = createHighlighter({
@@ -51,6 +52,21 @@ const hoverLinkDefaults = {
51
52
  side: "bottom",
52
53
  sideOffset: 12,
53
54
  };
55
+ const promptDefaults = {
56
+ actions: ["copy"],
57
+ copyLabel: "Copy prompt",
58
+ copiedLabel: "Copied",
59
+ openLabel: "Open in",
60
+ copyIcon: "copy",
61
+ copiedIcon: "check",
62
+ openIcon: "arrowUpRight",
63
+ };
64
+ const promptActionIcons = {
65
+ copy: '<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 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" /></svg>',
66
+ check: '<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>',
67
+ arrowUpRight: '<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="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /><polyline points="15 3 21 3 21 9" /><line x1="10" y1="14" x2="21" y2="3" /></svg>',
68
+ chevronDown: '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9" /></svg>',
69
+ };
54
70
  function escapeHtml(value) {
55
71
  return value
56
72
  .replace(/&/g, "&amp;")
@@ -117,6 +133,21 @@ function resolveHoverLinkOptions(theme) {
117
133
  }
118
134
  return base;
119
135
  }
136
+ function resolvePromptOptions(theme) {
137
+ const configured = theme?.ui?.components?.Prompt;
138
+ const base = { ...promptDefaults };
139
+ if (typeof configured === "function") {
140
+ const resolved = configured(base);
141
+ if (resolved && typeof resolved === "object") {
142
+ return { ...base, ...resolved };
143
+ }
144
+ return base;
145
+ }
146
+ if (configured && typeof configured === "object") {
147
+ return { ...base, ...configured };
148
+ }
149
+ return base;
150
+ }
120
151
  function renderHoverLink(attrSource, children, theme) {
121
152
  const attrs = parseJsxAttributes(attrSource);
122
153
  const defaults = resolveHoverLinkOptions(theme);
@@ -164,6 +195,120 @@ function renderHoverLink(attrSource, children, theme) {
164
195
  `</span>` +
165
196
  `</span>`);
166
197
  }
198
+ function resolvePromptIconName(value) {
199
+ if (value === false)
200
+ return false;
201
+ if (typeof value !== "string")
202
+ return undefined;
203
+ const trimmed = value.trim();
204
+ if (!trimmed)
205
+ return undefined;
206
+ if (trimmed === "false")
207
+ return false;
208
+ return trimmed;
209
+ }
210
+ function renderPromptIconHtml(name, iconRegistry) {
211
+ if (!name)
212
+ return "";
213
+ const registryMatch = iconRegistry?.[name];
214
+ if (registryMatch)
215
+ return registryMatch;
216
+ return promptActionIcons[name] ?? "";
217
+ }
218
+ function renderPrompt(attrSource, children, options) {
219
+ const attrs = parseJsxAttributes(attrSource);
220
+ const defaults = resolvePromptOptions(options.theme);
221
+ const title = toStringValue(attrs.title) ?? toStringValue(defaults.title);
222
+ const description = toStringValue(attrs.description) ?? toStringValue(defaults.description);
223
+ const iconName = toStringValue(attrs.icon) ?? toStringValue(defaults.icon);
224
+ const showTitle = attrs.showTitle !== undefined
225
+ ? toBoolean(attrs.showTitle, true)
226
+ : toBoolean(defaults.showTitle, true);
227
+ const showDescription = attrs.showDescription !== undefined
228
+ ? toBoolean(attrs.showDescription, true)
229
+ : toBoolean(defaults.showDescription, true);
230
+ const showPrompt = attrs.showPrompt !== undefined
231
+ ? toBoolean(attrs.showPrompt, false)
232
+ : toBoolean(defaults.showPrompt, false);
233
+ const actions = parsePromptStringArray(attrs.actions ?? defaults.actions) ?? ["copy"];
234
+ const providers = resolvePromptProviderChoices(options.openDocsProviders, parsePromptStringArray(attrs.providers ?? defaults.providers)) ?? [];
235
+ const copyLabel = toStringValue(attrs.copyLabel) ?? toStringValue(defaults.copyLabel) ?? promptDefaults.copyLabel;
236
+ const copiedLabel = toStringValue(attrs.copiedLabel) ??
237
+ toStringValue(defaults.copiedLabel) ??
238
+ promptDefaults.copiedLabel;
239
+ const openLabel = toStringValue(attrs.openLabel) ?? toStringValue(defaults.openLabel) ?? promptDefaults.openLabel;
240
+ const copyIcon = resolvePromptIconName(attrs.copyIcon ?? defaults.copyIcon);
241
+ const copiedIcon = resolvePromptIconName(attrs.copiedIcon ?? defaults.copiedIcon);
242
+ const openIcon = resolvePromptIconName(attrs.openIcon ?? defaults.openIcon);
243
+ const promptText = sanitizePromptText(dedentCode(children.trim()));
244
+ if (!promptText)
245
+ return "";
246
+ const cardIconHtml = iconName && options.icons?.[iconName] ? options.icons[iconName] : "";
247
+ const copyIconHtml = renderPromptIconHtml(copyIcon, options.icons);
248
+ const copiedIconHtml = renderPromptIconHtml(copiedIcon, options.icons);
249
+ const openIconHtml = renderPromptIconHtml(openIcon, options.icons);
250
+ const showCopy = actions.includes("copy");
251
+ const showOpen = actions.includes("open") && providers.length > 0;
252
+ const escapedPrompt = escapeHtml(promptText);
253
+ const singleProvider = showOpen && providers.length === 1 ? providers[0] : null;
254
+ let actionsHtml = "";
255
+ if (showCopy || showOpen) {
256
+ actionsHtml += '<div class="fd-prompt-actions">';
257
+ if (showCopy) {
258
+ actionsHtml +=
259
+ `<button type="button" class="fd-prompt-action-btn" data-prompt-copy>` +
260
+ `<span class="fd-prompt-action-icon">${copyIconHtml}</span>` +
261
+ `<span data-prompt-copy-label="${escapeHtml(copiedLabel)}" data-prompt-default-label="${escapeHtml(copyLabel)}">${escapeHtml(copyLabel)}</span>` +
262
+ `<span class="fd-prompt-action-icon fd-prompt-action-icon-copied" hidden>${copiedIconHtml}</span>` +
263
+ `</button>`;
264
+ }
265
+ if (singleProvider) {
266
+ actionsHtml +=
267
+ `<button type="button" class="fd-prompt-action-btn" data-prompt-open-direct data-url-template="${escapeHtml(singleProvider.urlTemplate)}">` +
268
+ `<span class="fd-prompt-action-icon">${openIconHtml}</span>` +
269
+ `<span>${escapeHtml(openLabel)} ${escapeHtml(singleProvider.name)}</span>` +
270
+ `</button>`;
271
+ }
272
+ else if (showOpen) {
273
+ actionsHtml +=
274
+ `<div class="fd-prompt-dropdown" data-prompt-dropdown>` +
275
+ `<button type="button" class="fd-prompt-action-btn" aria-expanded="false" data-prompt-trigger>` +
276
+ `<span class="fd-prompt-action-icon">${openIconHtml}</span>` +
277
+ `<span>${escapeHtml(openLabel)}</span>` +
278
+ `<span class="fd-prompt-action-chevron">${promptActionIcons.chevronDown}</span>` +
279
+ `</button>` +
280
+ `<div class="fd-prompt-menu" role="menu" hidden data-prompt-menu>`;
281
+ for (const provider of providers) {
282
+ actionsHtml +=
283
+ `<button type="button" role="menuitem" class="fd-prompt-menu-item" data-prompt-open-provider data-url-template="${escapeHtml(provider.urlTemplate)}">` +
284
+ (provider.iconHtml
285
+ ? `<span class="fd-prompt-menu-icon">${provider.iconHtml}</span>`
286
+ : "") +
287
+ `<span class="fd-prompt-menu-label">${escapeHtml(openLabel)} ${escapeHtml(provider.name)}</span>` +
288
+ `</button>`;
289
+ }
290
+ actionsHtml += `</div></div>`;
291
+ }
292
+ actionsHtml += `</div>`;
293
+ }
294
+ return (`<div class="fd-prompt" data-prompt-card>` +
295
+ (cardIconHtml || (showTitle && title) || (showDescription && description)
296
+ ? `<div class="fd-prompt-header">` +
297
+ (cardIconHtml ? `<span class="fd-prompt-icon">${cardIconHtml}</span>` : "") +
298
+ `<div class="fd-prompt-copy">` +
299
+ (showTitle && title ? `<p class="fd-prompt-title">${escapeHtml(title)}</p>` : "") +
300
+ (showDescription && description
301
+ ? `<p class="fd-prompt-description">${escapeHtml(description)}</p>`
302
+ : "") +
303
+ `</div></div>`
304
+ : "") +
305
+ `<div data-prompt-text hidden aria-hidden="true">${escapedPrompt}</div>` +
306
+ (showPrompt
307
+ ? `<div class="fd-prompt-body"><pre class="fd-prompt-code">${escapedPrompt}</pre></div>`
308
+ : "") +
309
+ actionsHtml +
310
+ `</div>`);
311
+ }
167
312
  const calloutIcons = {
168
313
  note: '<svg width="16" height="16" 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"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>',
169
314
  warning: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
@@ -237,7 +382,7 @@ function dedentCode(raw) {
237
382
  * Render a markdown string to HTML with full syntax highlighting,
238
383
  * callouts, tables, tabs, and copy-to-clipboard support.
239
384
  *
240
- * Designed for server-side use in SvelteKit `+page.server` loaders.
385
+ * Designed for server-side use in SvelteKit page loaders.
241
386
  */
242
387
  export async function renderMarkdown(content, options = {}) {
243
388
  if (!content)
@@ -294,6 +439,12 @@ export async function renderMarkdown(content, options = {}) {
294
439
  hoverLinkBlocks.push(renderHoverLink(attrSource, children, options.theme));
295
440
  return placeholder;
296
441
  });
442
+ const promptBlocks = [];
443
+ result = result.replace(/<Prompt(?:\s+([^>]*?))?>([\s\S]*?)<\/Prompt>/g, (_, attrSource, children) => {
444
+ const placeholder = `%%PROMPT_${promptBlocks.length}%%`;
445
+ promptBlocks.push(renderPrompt(attrSource ?? "", children, options));
446
+ return placeholder;
447
+ });
297
448
  // Inline code
298
449
  result = result.replace(/`([^`]+)`/g, "<code>$1</code>");
299
450
  // Headings (h4 → h1 order to avoid prefix collisions)
@@ -334,6 +485,8 @@ export async function renderMarkdown(content, options = {}) {
334
485
  result = result.replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>");
335
486
  result = result.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
336
487
  result = result.replace(/\*(.+?)\*/g, "<em>$1</em>");
488
+ // Images — before links so ![alt](url) is not captured as a link
489
+ result = result.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, src) => `<img src="${src}" alt="${alt.replace(/"/g, "&quot;")}" class="fd-docs-content-img" loading="lazy" decoding="async" />`);
337
490
  // Links
338
491
  result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
339
492
  // Horizontal rules
@@ -384,7 +537,7 @@ export async function renderMarkdown(content, options = {}) {
384
537
  return "";
385
538
  if (/^<(h[1-6]|pre|ul|ol|blockquote|hr|table|div)/.test(block))
386
539
  return block;
387
- if (/^%%(CODEBLOCK|CALLOUT|TABS)_\d+%%$/.test(block))
540
+ if (/^%%(CODEBLOCK|CALLOUT|TABS|PROMPT|HOVERLINK)_\d+%%$/.test(block))
388
541
  return block;
389
542
  return `<p>${block}</p>`;
390
543
  })
@@ -402,5 +555,8 @@ export async function renderMarkdown(content, options = {}) {
402
555
  for (let i = 0; i < hoverLinkBlocks.length; i++) {
403
556
  result = result.replace(`%%HOVERLINK_${i}%%`, hoverLinkBlocks[i]);
404
557
  }
558
+ for (let i = 0; i < promptBlocks.length; i++) {
559
+ result = result.replace(`%%PROMPT_${i}%%`, promptBlocks[i]);
560
+ }
405
561
  return result;
406
562
  }
package/dist/server.js CHANGED
@@ -31,7 +31,7 @@ import fs from "node:fs";
31
31
  import path from "node:path";
32
32
  import matter from "gray-matter";
33
33
  import { applySidebarFolderIndexBehavior, buildDocsAgentDiscoverySpec, findDocsMarkdownPage, isDocsAgentDiscoveryRequest, isDocsSkillRequest, normalizeDocsRelated, performDocsSearch, renderDocsMarkdownDocument, renderDocsSkillDocument, stripGeneratedAgentProvenance, resolveDocsAgentMdxContent, resolvePageSidebarFolderIndexBehavior, resolveSearchRequestConfig, resolveDocsI18n, resolveDocsLlmsTxtFormat, resolveDocsLocale, resolveDocsMarkdownRequest, resolveDocsPath, resolvePageReadingTime, resolveReadingTimeOptions, resolveDocsSkillFormat, } from "@farming-labs/docs";
34
- import { createDocsMcpHttpHandler, resolveDocsMcpConfig } from "@farming-labs/docs/server";
34
+ import { createDocsMcpHttpHandler, resolveDocsMcpConfig, serializeDocsIconRegistry, serializeOpenDocsProviders, } from "@farming-labs/docs/server";
35
35
  import { loadDocsNavTree, loadDocsContent, flattenNavTree } from "./content.js";
36
36
  import { renderMarkdown } from "./markdown.js";
37
37
  export { createSvelteApiReference } from "./api-reference.js";
@@ -489,7 +489,13 @@ export function createDocsServer(config = {}) {
489
489
  enabledByDefault: readingTimeOptions.enabled,
490
490
  wordsPerMinute: readingTimeOptions.wordsPerMinute,
491
491
  });
492
- const html = await renderMarkdown(humanRawContent, { theme: config.theme });
492
+ const html = await renderMarkdown(humanRawContent, {
493
+ theme: config.theme,
494
+ icons: serializeDocsIconRegistry(config.icons),
495
+ openDocsProviders: serializeOpenDocsProviders(config.pageActions?.openDocs && typeof config.pageActions.openDocs === "object"
496
+ ? config.pageActions.openDocs.providers
497
+ : undefined),
498
+ });
493
499
  const currentUrl = isIndex ? `/${entry}` : `/${entry}/${slug}`;
494
500
  const currentIndex = flatPages.findIndex((p) => p.url === currentUrl);
495
501
  const previousPage = currentIndex > 0 ? flatPages[currentIndex - 1] : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/svelte",
3
- "version": "0.1.58",
3
+ "version": "0.1.59",
4
4
  "description": "SvelteKit adapter for @farming-labs/docs — content loading and navigation utilities",
5
5
  "keywords": [
6
6
  "docs",
@@ -56,7 +56,7 @@
56
56
  "devDependencies": {
57
57
  "@types/node": "^22.10.0",
58
58
  "typescript": "^5.9.3",
59
- "@farming-labs/docs": "0.1.58"
59
+ "@farming-labs/docs": "0.1.59"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "@farming-labs/docs": "*"