@farming-labs/svelte 0.0.50 → 0.0.51

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.
@@ -8,10 +8,15 @@
8
8
  * - Callouts / admonitions (GitHub `[!NOTE]` and `**Note:**` styles)
9
9
  * - Tables, lists, inline formatting, headings with anchor IDs
10
10
  */
11
+ import type { DocsTheme } from "@farming-labs/docs";
12
+ interface RenderMarkdownOptions {
13
+ theme?: DocsTheme;
14
+ }
11
15
  /**
12
16
  * Render a markdown string to HTML with full syntax highlighting,
13
17
  * callouts, tables, tabs, and copy-to-clipboard support.
14
18
  *
15
19
  * Designed for server-side use in SvelteKit `+page.server` loaders.
16
20
  */
17
- export declare function renderMarkdown(content: string): Promise<string>;
21
+ export declare function renderMarkdown(content: string, options?: RenderMarkdownOptions): Promise<string>;
22
+ export {};
package/dist/markdown.js CHANGED
@@ -43,6 +43,126 @@ function slugify(text) {
43
43
  .replace(/-+/g, "-")
44
44
  .trim();
45
45
  }
46
+ const hoverLinkDefaults = {
47
+ linkLabel: "Open page",
48
+ showIndicator: false,
49
+ align: "center",
50
+ side: "bottom",
51
+ sideOffset: 12,
52
+ };
53
+ function escapeHtml(value) {
54
+ return value
55
+ .replace(/&/g, "&amp;")
56
+ .replace(/</g, "&lt;")
57
+ .replace(/>/g, "&gt;")
58
+ .replace(/"/g, "&quot;");
59
+ }
60
+ function parseJsxAttributes(source) {
61
+ const attrs = {};
62
+ const pattern = /([A-Za-z_:][-.\w:]*)(?:=(?:"([^"]*)"|'([^']*)'|\{([^}]*)\}))?/g;
63
+ let match;
64
+ while ((match = pattern.exec(source)) !== null) {
65
+ const [, name, doubleQuoted, singleQuoted, braced] = match;
66
+ const rawValue = doubleQuoted ?? singleQuoted ?? braced;
67
+ attrs[name] = rawValue === undefined ? true : rawValue.trim();
68
+ }
69
+ return attrs;
70
+ }
71
+ function toBoolean(value, fallback) {
72
+ if (typeof value === "boolean")
73
+ return value;
74
+ if (typeof value === "string") {
75
+ if (value === "true")
76
+ return true;
77
+ if (value === "false")
78
+ return false;
79
+ return fallback;
80
+ }
81
+ return fallback;
82
+ }
83
+ function toStringValue(value) {
84
+ return typeof value === "string" ? value : undefined;
85
+ }
86
+ function toNumberValue(value) {
87
+ if (typeof value === "number")
88
+ return Number.isFinite(value) ? value : undefined;
89
+ if (typeof value !== "string")
90
+ return undefined;
91
+ const parsed = Number(value);
92
+ return Number.isFinite(parsed) ? parsed : undefined;
93
+ }
94
+ function normalizeHoverAlign(value) {
95
+ if (value === "start" || value === "end")
96
+ return value;
97
+ return "center";
98
+ }
99
+ function normalizeHoverSide(value) {
100
+ if (value === "top" || value === "right" || value === "left")
101
+ return value;
102
+ return "bottom";
103
+ }
104
+ function resolveHoverLinkOptions(theme) {
105
+ const configured = theme?.ui?.components?.HoverLink;
106
+ const base = { ...hoverLinkDefaults };
107
+ if (typeof configured === "function") {
108
+ const resolved = configured(base);
109
+ if (resolved && typeof resolved === "object") {
110
+ return { ...base, ...resolved };
111
+ }
112
+ return base;
113
+ }
114
+ if (configured && typeof configured === "object") {
115
+ return { ...base, ...configured };
116
+ }
117
+ return base;
118
+ }
119
+ function renderHoverLink(attrSource, children, theme) {
120
+ const attrs = parseJsxAttributes(attrSource);
121
+ const defaults = resolveHoverLinkOptions(theme);
122
+ const href = toStringValue(attrs.href);
123
+ const title = toStringValue(attrs.title);
124
+ const description = toStringValue(attrs.description);
125
+ if (!href || !title || !description)
126
+ return children;
127
+ const linkLabel = toStringValue(attrs.linkLabel) ??
128
+ toStringValue(defaults.linkLabel) ??
129
+ hoverLinkDefaults.linkLabel;
130
+ const previewLabel = toStringValue(attrs.previewLabel) ?? toStringValue(defaults.previewLabel);
131
+ const showIndicator = attrs.showIndicator !== undefined
132
+ ? toBoolean(attrs.showIndicator, hoverLinkDefaults.showIndicator)
133
+ : toBoolean(defaults.showIndicator, hoverLinkDefaults.showIndicator);
134
+ const external = attrs.external !== undefined
135
+ ? toBoolean(attrs.external, false)
136
+ : toBoolean(defaults.external, false);
137
+ const align = normalizeHoverAlign(toStringValue(attrs.align) ?? toStringValue(defaults.align));
138
+ const side = normalizeHoverSide(toStringValue(attrs.side) ?? toStringValue(defaults.side));
139
+ const sideOffset = toNumberValue(attrs.sideOffset) ??
140
+ toNumberValue(defaults.sideOffset) ??
141
+ hoverLinkDefaults.sideOffset;
142
+ const targetAttrs = external ? ' target="_blank" rel="noopener noreferrer"' : "";
143
+ const triggerHtml = escapeHtml(children.trim()) || escapeHtml(title);
144
+ const indicatorHtml = showIndicator
145
+ ? '<span class="fd-hover-link-indicator" aria-hidden="true">+</span>'
146
+ : "";
147
+ const previewHtml = previewLabel
148
+ ? `<span class="fd-hover-link-preview-label">${escapeHtml(previewLabel)}</span>`
149
+ : "";
150
+ return (`<span class="fd-hover-link" data-hover-link data-align="${align}" data-side="${side}" style="--fd-hover-link-side-offset:${sideOffset}px">` +
151
+ `<button type="button" class="fd-hover-link-trigger" aria-haspopup="dialog" aria-expanded="false">${triggerHtml}${indicatorHtml}</button>` +
152
+ `<span class="fd-hover-link-popover" role="dialog" aria-hidden="true">` +
153
+ `<span class="fd-hover-link-card">` +
154
+ `<span class="fd-hover-link-body">` +
155
+ previewHtml +
156
+ `<a href="${escapeHtml(href)}" class="fd-hover-link-title"${targetAttrs}>${escapeHtml(title)}</a>` +
157
+ `<span class="fd-hover-link-description">${escapeHtml(description)}</span>` +
158
+ `</span>` +
159
+ `<span class="fd-hover-link-footer">` +
160
+ `<a href="${escapeHtml(href)}" class="fd-hover-link-cta"${targetAttrs}>${escapeHtml(linkLabel)}<span aria-hidden="true">→</span></a>` +
161
+ `</span>` +
162
+ `</span>` +
163
+ `</span>` +
164
+ `</span>`);
165
+ }
46
166
  const calloutIcons = {
47
167
  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>',
48
168
  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>',
@@ -118,7 +238,7 @@ function dedentCode(raw) {
118
238
  *
119
239
  * Designed for server-side use in SvelteKit `+page.server` loaders.
120
240
  */
121
- export async function renderMarkdown(content) {
241
+ export async function renderMarkdown(content, options = {}) {
122
242
  if (!content)
123
243
  return "";
124
244
  const hl = await getHighlighter();
@@ -167,6 +287,12 @@ export async function renderMarkdown(content) {
167
287
  codeBlocks.push(wrapCodeWithCopy(html, raw, title, lang));
168
288
  return placeholder;
169
289
  });
290
+ const hoverLinkBlocks = [];
291
+ result = result.replace(/<HoverLink\s+([^>]*?)>([\s\S]*?)<\/HoverLink>/g, (_, attrSource, children) => {
292
+ const placeholder = `%%HOVERLINK_${hoverLinkBlocks.length}%%`;
293
+ hoverLinkBlocks.push(renderHoverLink(attrSource, children, options.theme));
294
+ return placeholder;
295
+ });
170
296
  // Inline code
171
297
  result = result.replace(/`([^`]+)`/g, "<code>$1</code>");
172
298
  // Headings (h4 → h1 order to avoid prefix collisions)
@@ -272,5 +398,8 @@ export async function renderMarkdown(content) {
272
398
  for (let i = 0; i < tabsBlocks.length; i++) {
273
399
  result = result.replace(`%%TABS_${i}%%`, tabsBlocks[i]);
274
400
  }
401
+ for (let i = 0; i < hoverLinkBlocks.length; i++) {
402
+ result = result.replace(`%%HOVERLINK_${i}%%`, hoverLinkBlocks[i]);
403
+ }
275
404
  return result;
276
405
  }
package/dist/server.js CHANGED
@@ -431,7 +431,7 @@ export function createDocsServer(config = {}) {
431
431
  });
432
432
  }
433
433
  const { data, content } = matter(raw);
434
- const html = await renderMarkdown(content);
434
+ const html = await renderMarkdown(content, { theme: config.theme });
435
435
  const currentUrl = isIndex ? `/${entry}` : `/${entry}/${slug}`;
436
436
  const currentIndex = flatPages.findIndex((p) => p.url === currentUrl);
437
437
  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.0.50",
3
+ "version": "0.0.51",
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.0.50"
59
+ "@farming-labs/docs": "0.0.51"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "@farming-labs/docs": "*"