@farming-labs/nuxt-theme 0.0.2-beta.17
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 +71 -0
- package/src/components/Breadcrumb.vue +45 -0
- package/src/components/DocsContent.vue +77 -0
- package/src/components/DocsLayout.vue +354 -0
- package/src/components/DocsPage.vue +146 -0
- package/src/components/FloatingAIChat.vue +436 -0
- package/src/components/SearchDialog.vue +94 -0
- package/src/components/TableOfContents.vue +302 -0
- package/src/components/ThemeToggle.vue +42 -0
- package/src/index.d.ts +17 -0
- package/src/index.js +18 -0
- package/src/lib/renderMarkdown.js +108 -0
- package/src/themes/colorful.d.ts +2 -0
- package/src/themes/colorful.js +42 -0
- package/src/themes/darksharp.d.ts +4 -0
- package/src/themes/darksharp.js +42 -0
- package/src/themes/default.d.ts +4 -0
- package/src/themes/default.js +42 -0
- package/src/themes/pixel-border.d.ts +4 -0
- package/src/themes/pixel-border.js +38 -0
- package/styles/colorful-bundle.css +2 -0
- package/styles/colorful.css +148 -0
- package/styles/darksharp-bundle.css +6 -0
- package/styles/darksharp.css +206 -0
- package/styles/docs.css +2218 -0
- package/styles/pixel-border-bundle.css +6 -0
- package/styles/pixel-border.css +606 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from "vue";
|
|
3
|
+
|
|
4
|
+
interface TocItem {
|
|
5
|
+
title: string;
|
|
6
|
+
url: string;
|
|
7
|
+
depth: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const props = defineProps<{
|
|
11
|
+
items?: TocItem[];
|
|
12
|
+
tocStyle?: "default" | "directional";
|
|
13
|
+
}>();
|
|
14
|
+
|
|
15
|
+
const items = computed(() => props.items ?? []);
|
|
16
|
+
const isDirectional = computed(() => props.tocStyle === "directional");
|
|
17
|
+
const activeIds = ref(new Set<string>());
|
|
18
|
+
|
|
19
|
+
const listRef = ref<HTMLUListElement | null>(null);
|
|
20
|
+
const containerRef = ref<HTMLDivElement | null>(null);
|
|
21
|
+
const thumbTop = ref(0);
|
|
22
|
+
const thumbHeight = ref(0);
|
|
23
|
+
const svgPath = ref("");
|
|
24
|
+
|
|
25
|
+
let observer: IntersectionObserver | null = null;
|
|
26
|
+
|
|
27
|
+
function getItemOffset(depth: number): number {
|
|
28
|
+
if (depth <= 2) return 14;
|
|
29
|
+
if (depth === 3) return 26;
|
|
30
|
+
return 36;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getLineOffset(depth: number): number {
|
|
34
|
+
return depth >= 3 ? 10 : 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function observeHeadings() {
|
|
38
|
+
if (!observer) return;
|
|
39
|
+
observer.disconnect();
|
|
40
|
+
for (const item of items.value) {
|
|
41
|
+
const el = document.querySelector(item.url);
|
|
42
|
+
if (el) observer.observe(el);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isActive(item: TocItem): boolean {
|
|
47
|
+
return activeIds.value.has(item.url.slice(1));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function defaultLinkStyle(item: TocItem) {
|
|
51
|
+
const indent = (item.depth - 2) * 12;
|
|
52
|
+
return { paddingLeft: `${12 + indent}px` };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function clerkLinkStyle(item: TocItem) {
|
|
56
|
+
return {
|
|
57
|
+
position: "relative" as const,
|
|
58
|
+
paddingLeft: `${getItemOffset(item.depth)}px`,
|
|
59
|
+
paddingTop: "6px",
|
|
60
|
+
paddingBottom: "6px",
|
|
61
|
+
fontSize: item.depth <= 2 ? "14px" : "13px",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function verticalLineStyle(item: TocItem, index: number) {
|
|
66
|
+
const list = items.value;
|
|
67
|
+
const prevDepth = index > 0 ? list[index - 1].depth : item.depth;
|
|
68
|
+
const nextDepth = index < list.length - 1 ? list[index + 1].depth : item.depth;
|
|
69
|
+
const depthChanged = prevDepth !== item.depth;
|
|
70
|
+
const depthChangesNext = nextDepth !== item.depth;
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
position: "absolute" as const,
|
|
74
|
+
left: `${getLineOffset(item.depth)}px`,
|
|
75
|
+
top: depthChanged ? "6px" : "0",
|
|
76
|
+
bottom: depthChangesNext ? "6px" : "0",
|
|
77
|
+
width: "1px",
|
|
78
|
+
background: "hsla(0, 0%, 50%, 0.1)",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function hasDiagonal(index: number): boolean {
|
|
83
|
+
if (index === 0) return false;
|
|
84
|
+
const list = items.value;
|
|
85
|
+
return list[index - 1].depth !== list[index].depth;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function diagonalSvg(index: number) {
|
|
89
|
+
const list = items.value;
|
|
90
|
+
const prevDepth = list[index - 1].depth;
|
|
91
|
+
const currDepth = list[index].depth;
|
|
92
|
+
const upperOffset = getLineOffset(prevDepth);
|
|
93
|
+
const currentOffset = getLineOffset(currDepth);
|
|
94
|
+
return { upperOffset, currentOffset };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function buildSvgPath() {
|
|
98
|
+
if (!listRef.value) return;
|
|
99
|
+
const links = listRef.value.querySelectorAll<HTMLAnchorElement>(".fd-toc-clerk-link");
|
|
100
|
+
if (links.length === 0) {
|
|
101
|
+
svgPath.value = "";
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const containerTop = listRef.value.offsetTop;
|
|
106
|
+
let d = "";
|
|
107
|
+
const list = items.value;
|
|
108
|
+
|
|
109
|
+
links.forEach((el, i) => {
|
|
110
|
+
if (i >= list.length) return;
|
|
111
|
+
const depth = list[i].depth;
|
|
112
|
+
const x = getLineOffset(depth) + 1;
|
|
113
|
+
const top = el.offsetTop - containerTop;
|
|
114
|
+
const bottom = top + el.clientHeight;
|
|
115
|
+
const cmd = i === 0 ? "M" : "L";
|
|
116
|
+
d += `${cmd}${x} ${top} L${x} ${bottom} `;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
svgPath.value = d.trim();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function calcThumb() {
|
|
123
|
+
if (!listRef.value || activeIds.value.size === 0) {
|
|
124
|
+
thumbTop.value = 0;
|
|
125
|
+
thumbHeight.value = 0;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const containerTop = listRef.value.offsetTop;
|
|
130
|
+
let upper = Infinity;
|
|
131
|
+
let lower = 0;
|
|
132
|
+
|
|
133
|
+
for (const id of activeIds.value) {
|
|
134
|
+
const el = listRef.value.querySelector<HTMLAnchorElement>(`a[href="#${id}"]`);
|
|
135
|
+
if (!el) continue;
|
|
136
|
+
const styles = getComputedStyle(el);
|
|
137
|
+
const elTop = el.offsetTop - containerTop;
|
|
138
|
+
upper = Math.min(upper, elTop + parseFloat(styles.paddingTop));
|
|
139
|
+
lower = Math.max(lower, elTop + el.clientHeight - parseFloat(styles.paddingBottom));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (upper === Infinity) {
|
|
143
|
+
thumbTop.value = 0;
|
|
144
|
+
thumbHeight.value = 0;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
thumbTop.value = upper;
|
|
149
|
+
thumbHeight.value = lower - upper;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function maskSvgUrl(): string {
|
|
153
|
+
if (!svgPath.value) return "none";
|
|
154
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"><path d="${svgPath.value}" stroke="white" stroke-width="2" fill="none"/></svg>`;
|
|
155
|
+
return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
onMounted(() => {
|
|
159
|
+
observer = new IntersectionObserver(
|
|
160
|
+
(entries) => {
|
|
161
|
+
const next = new Set(activeIds.value);
|
|
162
|
+
for (const entry of entries) {
|
|
163
|
+
if (entry.isIntersecting) {
|
|
164
|
+
next.add(entry.target.id);
|
|
165
|
+
} else {
|
|
166
|
+
next.delete(entry.target.id);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
activeIds.value = next;
|
|
170
|
+
},
|
|
171
|
+
{ rootMargin: "-80px 0px -80% 0px" }
|
|
172
|
+
);
|
|
173
|
+
observeHeadings();
|
|
174
|
+
|
|
175
|
+
if (isDirectional.value) {
|
|
176
|
+
nextTick(() => {
|
|
177
|
+
buildSvgPath();
|
|
178
|
+
calcThumb();
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
watch(items, () => {
|
|
184
|
+
observeHeadings();
|
|
185
|
+
if (isDirectional.value) {
|
|
186
|
+
nextTick(() => {
|
|
187
|
+
buildSvgPath();
|
|
188
|
+
calcThumb();
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}, { flush: "post" });
|
|
192
|
+
|
|
193
|
+
watch(activeIds, () => {
|
|
194
|
+
if (isDirectional.value) {
|
|
195
|
+
calcThumb();
|
|
196
|
+
}
|
|
197
|
+
}, { deep: true });
|
|
198
|
+
|
|
199
|
+
watch(isDirectional, (val) => {
|
|
200
|
+
if (val) {
|
|
201
|
+
nextTick(() => {
|
|
202
|
+
buildSvgPath();
|
|
203
|
+
calcThumb();
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
onUnmounted(() => {
|
|
209
|
+
observer?.disconnect();
|
|
210
|
+
});
|
|
211
|
+
</script>
|
|
212
|
+
|
|
213
|
+
<template>
|
|
214
|
+
<div ref="containerRef" class="fd-toc-inner" :class="{ 'fd-toc-directional': isDirectional }">
|
|
215
|
+
<h3 class="fd-toc-title">
|
|
216
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
217
|
+
<line x1="3" y1="6" x2="21" y2="6" />
|
|
218
|
+
<line x1="3" y1="12" x2="15" y2="12" />
|
|
219
|
+
<line x1="3" y1="18" x2="18" y2="18" />
|
|
220
|
+
</svg>
|
|
221
|
+
On this page
|
|
222
|
+
</h3>
|
|
223
|
+
<p v-if="items.length === 0" class="fd-toc-empty">No Headings</p>
|
|
224
|
+
|
|
225
|
+
<!-- Default style -->
|
|
226
|
+
<ul v-else-if="!isDirectional" class="fd-toc-list">
|
|
227
|
+
<li v-for="item in items" :key="item.url" class="fd-toc-item">
|
|
228
|
+
<a
|
|
229
|
+
:href="item.url"
|
|
230
|
+
class="fd-toc-link"
|
|
231
|
+
:class="{ 'fd-toc-link-active': isActive(item) }"
|
|
232
|
+
:style="defaultLinkStyle(item)"
|
|
233
|
+
>
|
|
234
|
+
{{ item.title }}
|
|
235
|
+
</a>
|
|
236
|
+
</li>
|
|
237
|
+
</ul>
|
|
238
|
+
|
|
239
|
+
<!-- Clerk / directional style -->
|
|
240
|
+
<ul v-else ref="listRef" class="fd-toc-list fd-toc-clerk" style="position: relative;">
|
|
241
|
+
<li v-for="(item, index) in items" :key="item.url" class="fd-toc-item">
|
|
242
|
+
<a
|
|
243
|
+
:href="item.url"
|
|
244
|
+
class="fd-toc-link fd-toc-clerk-link"
|
|
245
|
+
:style="clerkLinkStyle(item)"
|
|
246
|
+
:data-active="isActive(item) || undefined"
|
|
247
|
+
>
|
|
248
|
+
<!-- Vertical line segment -->
|
|
249
|
+
<div :style="verticalLineStyle(item, index)" />
|
|
250
|
+
|
|
251
|
+
<!-- Diagonal SVG connector when depth changes -->
|
|
252
|
+
<svg
|
|
253
|
+
v-if="hasDiagonal(index)"
|
|
254
|
+
viewBox="0 0 16 16"
|
|
255
|
+
width="16"
|
|
256
|
+
height="16"
|
|
257
|
+
style="position: absolute; top: -6px; left: 0;"
|
|
258
|
+
>
|
|
259
|
+
<line
|
|
260
|
+
:x1="diagonalSvg(index).upperOffset"
|
|
261
|
+
y1="0"
|
|
262
|
+
:x2="diagonalSvg(index).currentOffset"
|
|
263
|
+
y2="12"
|
|
264
|
+
stroke="hsla(0, 0%, 50%, 0.1)"
|
|
265
|
+
stroke-width="1"
|
|
266
|
+
/>
|
|
267
|
+
</svg>
|
|
268
|
+
|
|
269
|
+
{{ item.title }}
|
|
270
|
+
</a>
|
|
271
|
+
</li>
|
|
272
|
+
|
|
273
|
+
<!-- Mask container with thumb for active highlight -->
|
|
274
|
+
<div
|
|
275
|
+
v-if="svgPath"
|
|
276
|
+
class="fd-toc-clerk-mask"
|
|
277
|
+
:style="{
|
|
278
|
+
position: 'absolute',
|
|
279
|
+
left: '0',
|
|
280
|
+
top: '0',
|
|
281
|
+
width: '100%',
|
|
282
|
+
height: '100%',
|
|
283
|
+
pointerEvents: 'none',
|
|
284
|
+
maskImage: maskSvgUrl(),
|
|
285
|
+
WebkitMaskImage: maskSvgUrl(),
|
|
286
|
+
maskRepeat: 'no-repeat',
|
|
287
|
+
WebkitMaskRepeat: 'no-repeat',
|
|
288
|
+
}"
|
|
289
|
+
>
|
|
290
|
+
<div
|
|
291
|
+
class="fd-toc-clerk-thumb"
|
|
292
|
+
:style="{
|
|
293
|
+
marginTop: `${thumbTop}px`,
|
|
294
|
+
height: `${thumbHeight}px`,
|
|
295
|
+
background: 'var(--color-fd-primary)',
|
|
296
|
+
transition: 'all 0.15s',
|
|
297
|
+
}"
|
|
298
|
+
/>
|
|
299
|
+
</div>
|
|
300
|
+
</ul>
|
|
301
|
+
</div>
|
|
302
|
+
</template>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, onMounted } from "vue";
|
|
3
|
+
|
|
4
|
+
const theme = ref<"light" | "dark">("light");
|
|
5
|
+
|
|
6
|
+
onMounted(() => {
|
|
7
|
+
const stored = document.cookie.match(/(?:^|;\s*)theme=(\w+)/);
|
|
8
|
+
if (stored) {
|
|
9
|
+
theme.value = stored[1] as "light" | "dark";
|
|
10
|
+
} else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
|
11
|
+
theme.value = "dark";
|
|
12
|
+
} else {
|
|
13
|
+
theme.value = document.documentElement.classList.contains("dark") ? "dark" : "light";
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function toggle() {
|
|
18
|
+
theme.value = theme.value === "dark" ? "light" : "dark";
|
|
19
|
+
document.documentElement.classList.remove("light", "dark");
|
|
20
|
+
document.documentElement.classList.add(theme.value);
|
|
21
|
+
document.cookie = `theme=${theme.value};path=/;max-age=31536000;SameSite=Lax`;
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<button class="fd-theme-toggle" type="button" aria-label="Toggle theme" @click="toggle">
|
|
27
|
+
<svg v-if="theme === 'dark'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
28
|
+
<circle cx="12" cy="12" r="5" />
|
|
29
|
+
<line x1="12" y1="1" x2="12" y2="3" />
|
|
30
|
+
<line x1="12" y1="21" x2="12" y2="23" />
|
|
31
|
+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
|
32
|
+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
|
33
|
+
<line x1="1" y1="12" x2="3" y2="12" />
|
|
34
|
+
<line x1="21" y1="12" x2="23" y2="12" />
|
|
35
|
+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
|
36
|
+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
|
37
|
+
</svg>
|
|
38
|
+
<svg v-else width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
39
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
|
40
|
+
</svg>
|
|
41
|
+
</button>
|
|
42
|
+
</template>
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Component } from "vue";
|
|
2
|
+
import type { DocsTheme } from "@farming-labs/docs";
|
|
3
|
+
|
|
4
|
+
export const DocsLayout: Component;
|
|
5
|
+
export const DocsContent: Component;
|
|
6
|
+
export const DocsPage: Component;
|
|
7
|
+
export const TableOfContents: Component;
|
|
8
|
+
export const Breadcrumb: Component;
|
|
9
|
+
export const SearchDialog: Component;
|
|
10
|
+
export const FloatingAIChat: Component;
|
|
11
|
+
|
|
12
|
+
export declare const fumadocs: (overrides?: Partial<DocsTheme>) => DocsTheme;
|
|
13
|
+
export declare const DefaultUIDefaults: Record<string, any>;
|
|
14
|
+
export declare const pixelBorder: (overrides?: Partial<DocsTheme>) => DocsTheme;
|
|
15
|
+
export declare const PixelBorderUIDefaults: Record<string, any>;
|
|
16
|
+
export declare const darksharp: (overrides?: Partial<DocsTheme>) => DocsTheme;
|
|
17
|
+
export declare const DarksharpUIDefaults: Record<string, any>;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @farming-labs/nuxt-theme
|
|
3
|
+
*
|
|
4
|
+
* Nuxt/Vue UI components and theme presets for documentation sites.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { default as DocsLayout } from "./components/DocsLayout.vue";
|
|
8
|
+
export { default as DocsContent } from "./components/DocsContent.vue";
|
|
9
|
+
export { default as DocsPage } from "./components/DocsPage.vue";
|
|
10
|
+
export { default as TableOfContents } from "./components/TableOfContents.vue";
|
|
11
|
+
export { default as Breadcrumb } from "./components/Breadcrumb.vue";
|
|
12
|
+
export { default as SearchDialog } from "./components/SearchDialog.vue";
|
|
13
|
+
export { default as FloatingAIChat } from "./components/FloatingAIChat.vue";
|
|
14
|
+
export { default as ThemeToggle } from "./components/ThemeToggle.vue";
|
|
15
|
+
|
|
16
|
+
export { fumadocs, DefaultUIDefaults } from "./themes/default.js";
|
|
17
|
+
export { pixelBorder, PixelBorderUIDefaults } from "./themes/pixel-border.js";
|
|
18
|
+
export { darksharp, DarksharpUIDefaults } from "./themes/darksharp.js";
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown renderer for AI chat responses.
|
|
3
|
+
*
|
|
4
|
+
* Uses sugar-high for syntax highlighting in fenced code blocks.
|
|
5
|
+
* Supports: fenced code blocks, tables, inline code, bold, italic,
|
|
6
|
+
* links, headings, bullet lists, numbered lists.
|
|
7
|
+
*/
|
|
8
|
+
import { highlight } from "sugar-high";
|
|
9
|
+
|
|
10
|
+
function escapeHtml(s) {
|
|
11
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function buildCodeBlock(lang, code) {
|
|
15
|
+
const trimmed = code.replace(/\n$/, "");
|
|
16
|
+
const highlighted = highlight(trimmed).replace(/<\/span>\n<span/g, "</span><span");
|
|
17
|
+
const langLabel = lang ? `<div class="fd-ai-code-lang">${escapeHtml(lang)}</div>` : "";
|
|
18
|
+
const copyBtn = `<button class="fd-ai-code-copy" onclick="(function(btn){var code=btn.closest('.fd-ai-code-block').querySelector('code').textContent;navigator.clipboard.writeText(code).then(function(){btn.textContent='Copied!';setTimeout(function(){btn.textContent='Copy'},1500)})})(this)">Copy</button>`;
|
|
19
|
+
return `<div class="fd-ai-code-block">`
|
|
20
|
+
+ `<div class="fd-ai-code-header">${langLabel}${copyBtn}</div>`
|
|
21
|
+
+ `<pre><code>${highlighted}</code></pre>`
|
|
22
|
+
+ `</div>`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isTableRow(line) {
|
|
26
|
+
const trimmed = line.trim();
|
|
27
|
+
return trimmed.startsWith("|") && trimmed.endsWith("|") && trimmed.includes("|");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isTableSeparator(line) {
|
|
31
|
+
return /^\|[\s:]*-+[\s:]*(\|[\s:]*-+[\s:]*)*\|$/.test(line.trim());
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function renderTable(rows) {
|
|
35
|
+
const parseRow = (row) =>
|
|
36
|
+
row.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map(c => c.trim());
|
|
37
|
+
|
|
38
|
+
const headerCells = parseRow(rows[0]);
|
|
39
|
+
const thead = `<thead><tr>${headerCells.map(c => `<th>${c}</th>`).join("")}</tr></thead>`;
|
|
40
|
+
|
|
41
|
+
const bodyRows = rows.slice(1).map(row => {
|
|
42
|
+
const cells = parseRow(row);
|
|
43
|
+
return `<tr>${cells.map(c => `<td>${c}</td>`).join("")}</tr>`;
|
|
44
|
+
}).join("");
|
|
45
|
+
|
|
46
|
+
return `<table>${thead}<tbody>${bodyRows}</tbody></table>`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function renderMarkdown(text) {
|
|
50
|
+
if (!text) return "";
|
|
51
|
+
|
|
52
|
+
const codeBlocks = [];
|
|
53
|
+
|
|
54
|
+
let processed = text.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
|
|
55
|
+
codeBlocks.push(buildCodeBlock(lang, code));
|
|
56
|
+
return `\x00CB${codeBlocks.length - 1}\x00`;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
processed = processed.replace(/```(\w*)\n([\s\S]*)$/, (_match, lang, code) => {
|
|
60
|
+
codeBlocks.push(buildCodeBlock(lang, code));
|
|
61
|
+
return `\x00CB${codeBlocks.length - 1}\x00`;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const lines = processed.split("\n");
|
|
65
|
+
const output = [];
|
|
66
|
+
let i = 0;
|
|
67
|
+
|
|
68
|
+
while (i < lines.length) {
|
|
69
|
+
if (isTableRow(lines[i]) && i + 1 < lines.length && isTableSeparator(lines[i + 1])) {
|
|
70
|
+
const tableLines = [lines[i]];
|
|
71
|
+
i++;
|
|
72
|
+
i++;
|
|
73
|
+
while (i < lines.length && isTableRow(lines[i])) {
|
|
74
|
+
tableLines.push(lines[i]);
|
|
75
|
+
i++;
|
|
76
|
+
}
|
|
77
|
+
output.push(renderTable(tableLines));
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
output.push(lines[i]);
|
|
81
|
+
i++;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let result = output.join("\n");
|
|
85
|
+
|
|
86
|
+
result = result
|
|
87
|
+
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
|
88
|
+
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
|
|
89
|
+
.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "<em>$1</em>")
|
|
90
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>')
|
|
91
|
+
.replace(/^### (.*$)/gm, "<h4>$1</h4>")
|
|
92
|
+
.replace(/^## (.*$)/gm, "<h3>$1</h3>")
|
|
93
|
+
.replace(/^# (.*$)/gm, "<h2>$1</h2>")
|
|
94
|
+
.replace(
|
|
95
|
+
/^[-*] (.*$)/gm,
|
|
96
|
+
'<div style="display:flex;gap:8px;padding:2px 0"><span style="opacity:0.5">\u2022</span><span>$1</span></div>',
|
|
97
|
+
)
|
|
98
|
+
.replace(
|
|
99
|
+
/^(\d+)\. (.*$)/gm,
|
|
100
|
+
'<div style="display:flex;gap:8px;padding:2px 0"><span style="opacity:0.5">$1.</span><span>$2</span></div>',
|
|
101
|
+
)
|
|
102
|
+
.replace(/\n\n/g, '<div style="height:8px"></div>')
|
|
103
|
+
.replace(/\n/g, "<br>");
|
|
104
|
+
|
|
105
|
+
result = result.replace(/\x00CB(\d+)\x00/g, (_m, idx) => codeBlocks[Number(idx)]);
|
|
106
|
+
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createTheme } from "@farming-labs/docs";
|
|
2
|
+
|
|
3
|
+
const ColorfulUIDefaults = {
|
|
4
|
+
colors: {
|
|
5
|
+
primary: "hsl(40, 96%, 40%)",
|
|
6
|
+
background: "#ffffff",
|
|
7
|
+
muted: "#64748b",
|
|
8
|
+
border: "#e5e7eb",
|
|
9
|
+
},
|
|
10
|
+
typography: {
|
|
11
|
+
font: {
|
|
12
|
+
style: {
|
|
13
|
+
sans: "Inter, system-ui, sans-serif",
|
|
14
|
+
mono: "JetBrains Mono, monospace",
|
|
15
|
+
},
|
|
16
|
+
h1: { size: "1.875rem", weight: 700, lineHeight: "1.2", letterSpacing: "-0.02em" },
|
|
17
|
+
h2: { size: "1.5rem", weight: 600, lineHeight: "1.3" },
|
|
18
|
+
h3: { size: "1.25rem", weight: 600, lineHeight: "1.4" },
|
|
19
|
+
h4: { size: "1.125rem", weight: 600, lineHeight: "1.4" },
|
|
20
|
+
body: { size: "1rem", weight: 400, lineHeight: "1.75" },
|
|
21
|
+
small: { size: "0.875rem", weight: 400, lineHeight: "1.5" },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
layout: {
|
|
25
|
+
contentWidth: 768,
|
|
26
|
+
sidebarWidth: 260,
|
|
27
|
+
toc: { enabled: true, depth: 3, style: "directional" },
|
|
28
|
+
header: { height: 56, sticky: true },
|
|
29
|
+
},
|
|
30
|
+
components: {
|
|
31
|
+
Callout: { variant: "soft", icon: true },
|
|
32
|
+
CodeBlock: { showCopyButton: true },
|
|
33
|
+
Tabs: { style: "default" },
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const colorful = createTheme({
|
|
38
|
+
name: "fumadocs-colorful",
|
|
39
|
+
ui: ColorfulUIDefaults,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export { ColorfulUIDefaults };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createTheme } from "@farming-labs/docs";
|
|
2
|
+
|
|
3
|
+
const DarksharpUIDefaults = {
|
|
4
|
+
colors: {
|
|
5
|
+
primary: "#fafaf9",
|
|
6
|
+
background: "#000000",
|
|
7
|
+
muted: "#a8a29e",
|
|
8
|
+
border: "#292524",
|
|
9
|
+
},
|
|
10
|
+
typography: {
|
|
11
|
+
font: {
|
|
12
|
+
style: {
|
|
13
|
+
sans: "Geist, system-ui, sans-serif",
|
|
14
|
+
mono: "Geist Mono, monospace",
|
|
15
|
+
},
|
|
16
|
+
h1: { size: "2rem", weight: 700, lineHeight: "1.2", letterSpacing: "-0.02em" },
|
|
17
|
+
h2: { size: "1.5rem", weight: 600, lineHeight: "1.3" },
|
|
18
|
+
h3: { size: "1.25rem", weight: 600, lineHeight: "1.4" },
|
|
19
|
+
h4: { size: "1.125rem", weight: 600, lineHeight: "1.4" },
|
|
20
|
+
body: { size: "1rem", weight: 400, lineHeight: "1.75" },
|
|
21
|
+
small: { size: "0.875rem", weight: 400, lineHeight: "1.5" },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
layout: {
|
|
25
|
+
contentWidth: 768,
|
|
26
|
+
sidebarWidth: 280,
|
|
27
|
+
toc: { enabled: true, depth: 3 },
|
|
28
|
+
header: { height: 56, sticky: true },
|
|
29
|
+
},
|
|
30
|
+
components: {
|
|
31
|
+
Callout: { variant: "soft", icon: true },
|
|
32
|
+
CodeBlock: { showCopyButton: true },
|
|
33
|
+
Tabs: { style: "default" },
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const darksharp = createTheme({
|
|
38
|
+
name: "fumadocs-darksharp",
|
|
39
|
+
ui: DarksharpUIDefaults,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export { DarksharpUIDefaults };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createTheme } from "@farming-labs/docs";
|
|
2
|
+
|
|
3
|
+
const DefaultUIDefaults = {
|
|
4
|
+
colors: {
|
|
5
|
+
primary: "#6366f1",
|
|
6
|
+
background: "#ffffff",
|
|
7
|
+
muted: "#64748b",
|
|
8
|
+
border: "#e5e7eb",
|
|
9
|
+
},
|
|
10
|
+
typography: {
|
|
11
|
+
font: {
|
|
12
|
+
style: {
|
|
13
|
+
sans: "Inter, system-ui, sans-serif",
|
|
14
|
+
mono: "JetBrains Mono, monospace",
|
|
15
|
+
},
|
|
16
|
+
h1: { size: "2rem", weight: 700, lineHeight: "1.2", letterSpacing: "-0.02em" },
|
|
17
|
+
h2: { size: "1.5rem", weight: 600, lineHeight: "1.3" },
|
|
18
|
+
h3: { size: "1.25rem", weight: 600, lineHeight: "1.4" },
|
|
19
|
+
h4: { size: "1.125rem", weight: 600, lineHeight: "1.4" },
|
|
20
|
+
body: { size: "1rem", weight: 400, lineHeight: "1.75" },
|
|
21
|
+
small: { size: "0.875rem", weight: 400, lineHeight: "1.5" },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
layout: {
|
|
25
|
+
contentWidth: 768,
|
|
26
|
+
sidebarWidth: 280,
|
|
27
|
+
toc: { enabled: true, depth: 3 },
|
|
28
|
+
header: { height: 72, sticky: true },
|
|
29
|
+
},
|
|
30
|
+
components: {
|
|
31
|
+
Callout: { variant: "soft", icon: true },
|
|
32
|
+
CodeBlock: { showCopyButton: true },
|
|
33
|
+
Tabs: { style: "default" },
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const fumadocs = createTheme({
|
|
38
|
+
name: "fumadocs-default",
|
|
39
|
+
ui: DefaultUIDefaults,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export { DefaultUIDefaults };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createTheme } from "@farming-labs/docs";
|
|
2
|
+
|
|
3
|
+
const PixelBorderUIDefaults = {
|
|
4
|
+
colors: {
|
|
5
|
+
primary: "oklch(0.985 0.001 106.423)",
|
|
6
|
+
background: "hsl(0 0% 2%)",
|
|
7
|
+
muted: "hsl(0 0% 55%)",
|
|
8
|
+
border: "hsl(0 0% 15%)",
|
|
9
|
+
},
|
|
10
|
+
typography: {
|
|
11
|
+
font: {
|
|
12
|
+
style: {
|
|
13
|
+
sans: "system-ui, -apple-system, sans-serif",
|
|
14
|
+
mono: "ui-monospace, monospace",
|
|
15
|
+
},
|
|
16
|
+
h1: { size: "2.25rem", weight: 700, lineHeight: "1.2", letterSpacing: "-0.02em" },
|
|
17
|
+
h2: { size: "1.5rem", weight: 600, lineHeight: "1.3", letterSpacing: "-0.01em" },
|
|
18
|
+
h3: { size: "1.25rem", weight: 600, lineHeight: "1.4" },
|
|
19
|
+
h4: { size: "1.125rem", weight: 600, lineHeight: "1.4" },
|
|
20
|
+
body: { size: "1rem", weight: 400, lineHeight: "1.75" },
|
|
21
|
+
small: { size: "0.875rem", weight: 400, lineHeight: "1.5" },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
layout: {
|
|
25
|
+
contentWidth: 860,
|
|
26
|
+
sidebarWidth: 286,
|
|
27
|
+
toc: { enabled: true, depth: 3 },
|
|
28
|
+
header: { height: 56, sticky: true },
|
|
29
|
+
},
|
|
30
|
+
components: {},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const pixelBorder = createTheme({
|
|
34
|
+
name: "fumadocs-pixel-border",
|
|
35
|
+
ui: PixelBorderUIDefaults,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export { PixelBorderUIDefaults };
|