@cyguin/docs 0.1.0
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/.github/workflows/publish.yml +27 -0
- package/LICENSE +7 -0
- package/README.md +152 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +438 -0
- package/dist/index.mjs +438 -0
- package/dist/next.d.mts +10 -0
- package/dist/next.d.ts +10 -0
- package/dist/next.js +53 -0
- package/dist/next.mjs +53 -0
- package/dist/styles.css +11 -0
- package/dist/types-0oOQLVHA.d.mts +52 -0
- package/dist/types-0oOQLVHA.d.ts +52 -0
- package/package.json +46 -0
- package/src/adapters/index.ts +1 -0
- package/src/adapters/sqlite.ts +95 -0
- package/src/app/admin/docs/[...route]/route.ts +31 -0
- package/src/app/api/docs/[...cyguin]/route.ts +10 -0
- package/src/components/DocsWidget.tsx +521 -0
- package/src/components/index.ts +2 -0
- package/src/handlers/admin.ts +89 -0
- package/src/handlers/route.ts +70 -0
- package/src/index.ts +5 -0
- package/src/next.ts +3 -0
- package/src/styles.css +11 -0
- package/src/types.ts +62 -0
- package/tsconfig.json +18 -0
- package/tsup.config.ts +12 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
// src/components/DocsWidget.tsx
|
|
2
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
3
|
+
import { marked } from "marked";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
marked.setOptions({ breaks: true, gfm: true });
|
|
6
|
+
function HelpIcon() {
|
|
7
|
+
return /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
8
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
9
|
+
/* @__PURE__ */ jsx("path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }),
|
|
10
|
+
/* @__PURE__ */ jsx("path", { d: "M12 17h.01" })
|
|
11
|
+
] });
|
|
12
|
+
}
|
|
13
|
+
function SearchIcon() {
|
|
14
|
+
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
15
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "8" }),
|
|
16
|
+
/* @__PURE__ */ jsx("path", { d: "m21 21-4.35-4.35" })
|
|
17
|
+
] });
|
|
18
|
+
}
|
|
19
|
+
function CloseIcon() {
|
|
20
|
+
return /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
21
|
+
/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
|
|
22
|
+
/* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
|
|
23
|
+
] });
|
|
24
|
+
}
|
|
25
|
+
function ChevronRightIcon({ expanded }) {
|
|
26
|
+
return /* @__PURE__ */ jsx(
|
|
27
|
+
"svg",
|
|
28
|
+
{
|
|
29
|
+
width: "14",
|
|
30
|
+
height: "14",
|
|
31
|
+
viewBox: "0 0 24 24",
|
|
32
|
+
fill: "none",
|
|
33
|
+
stroke: "currentColor",
|
|
34
|
+
strokeWidth: "2",
|
|
35
|
+
strokeLinecap: "round",
|
|
36
|
+
strokeLinejoin: "round",
|
|
37
|
+
style: { transform: expanded ? "rotate(90deg)" : "rotate(0deg)", transition: "transform 0.15s" },
|
|
38
|
+
children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" })
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
function BackIcon() {
|
|
43
|
+
return /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m15 18-6-6 6-6" }) });
|
|
44
|
+
}
|
|
45
|
+
function renderMarkdown(md) {
|
|
46
|
+
return marked.parse(md);
|
|
47
|
+
}
|
|
48
|
+
function groupBySection(articles) {
|
|
49
|
+
const map = /* @__PURE__ */ new Map();
|
|
50
|
+
for (const article of articles) {
|
|
51
|
+
const list = map.get(article.section) ?? [];
|
|
52
|
+
list.push(article);
|
|
53
|
+
map.set(article.section, list);
|
|
54
|
+
}
|
|
55
|
+
return Array.from(map.entries()).map(([section, arts]) => ({ section, articles: arts.sort((a, b) => a.article_order - b.article_order) })).sort((a, b) => a.section.localeCompare(b.section));
|
|
56
|
+
}
|
|
57
|
+
function DocsTrigger({ label, onClick }) {
|
|
58
|
+
return /* @__PURE__ */ jsx(
|
|
59
|
+
"button",
|
|
60
|
+
{
|
|
61
|
+
onClick,
|
|
62
|
+
"aria-label": label,
|
|
63
|
+
style: {
|
|
64
|
+
position: "fixed",
|
|
65
|
+
bottom: "24px",
|
|
66
|
+
right: "24px",
|
|
67
|
+
zIndex: 9998,
|
|
68
|
+
width: "48px",
|
|
69
|
+
height: "48px",
|
|
70
|
+
borderRadius: "var(--cyguin-docs-radius)",
|
|
71
|
+
background: "var(--cyguin-docs-accent)",
|
|
72
|
+
color: "var(--cyguin-accent-fg, #0a0a0a)",
|
|
73
|
+
border: "none",
|
|
74
|
+
cursor: "pointer",
|
|
75
|
+
display: "flex",
|
|
76
|
+
alignItems: "center",
|
|
77
|
+
justifyContent: "center",
|
|
78
|
+
boxShadow: "var(--cyguin-docs-shadow)",
|
|
79
|
+
fontWeight: 600,
|
|
80
|
+
fontSize: "14px",
|
|
81
|
+
fontFamily: "var(--cyguin-docs-font)",
|
|
82
|
+
transition: "opacity 0.15s"
|
|
83
|
+
},
|
|
84
|
+
children: /* @__PURE__ */ jsx(HelpIcon, {})
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
function DocsSearch({ value, onChange }) {
|
|
89
|
+
const ref = useRef(null);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const handler = (e) => {
|
|
92
|
+
if (e.key === "/" && !e.ctrlKey && !e.metaKey) {
|
|
93
|
+
const active = document.activeElement;
|
|
94
|
+
const widget = document.getElementById("cyguin-docs-widget");
|
|
95
|
+
if (active && widget?.contains(active)) return;
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
ref.current?.focus();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
document.addEventListener("keydown", handler);
|
|
101
|
+
return () => document.removeEventListener("keydown", handler);
|
|
102
|
+
}, []);
|
|
103
|
+
return /* @__PURE__ */ jsxs("div", { style: { position: "relative", padding: "12px 12px 8px" }, children: [
|
|
104
|
+
/* @__PURE__ */ jsx(SearchIcon, {}),
|
|
105
|
+
/* @__PURE__ */ jsx(
|
|
106
|
+
"input",
|
|
107
|
+
{
|
|
108
|
+
ref,
|
|
109
|
+
type: "text",
|
|
110
|
+
value,
|
|
111
|
+
onChange: (e) => onChange(e.target.value),
|
|
112
|
+
placeholder: "Search docs... (press / to focus)",
|
|
113
|
+
"aria-label": "Search documentation",
|
|
114
|
+
style: {
|
|
115
|
+
width: "100%",
|
|
116
|
+
padding: "8px 8px 8px 32px",
|
|
117
|
+
borderRadius: "var(--cyguin-docs-radius)",
|
|
118
|
+
border: "1px solid var(--cyguin-docs-border)",
|
|
119
|
+
background: "var(--cyguin-bg-subtle, #f5f5f5)",
|
|
120
|
+
color: "var(--cyguin-docs-text)",
|
|
121
|
+
fontSize: "14px",
|
|
122
|
+
fontFamily: "var(--cyguin-docs-font)",
|
|
123
|
+
boxSizing: "border-box",
|
|
124
|
+
outline: "none"
|
|
125
|
+
},
|
|
126
|
+
onFocus: (e) => {
|
|
127
|
+
e.currentTarget.style.borderColor = "var(--cyguin-border-focus, #f5a800)";
|
|
128
|
+
},
|
|
129
|
+
onBlur: (e) => {
|
|
130
|
+
e.currentTarget.style.borderColor = "var(--cyguin-docs-border)";
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
] });
|
|
135
|
+
}
|
|
136
|
+
function DocsNav({ groups, selectedId, selectedIndex, onSelectArticle, searchQuery }) {
|
|
137
|
+
const [expandedSections, setExpandedSections] = useState(new Set(groups.map((g) => g.section)));
|
|
138
|
+
const navRef = useRef(null);
|
|
139
|
+
const flatArticles = groups.flatMap((g) => g.articles);
|
|
140
|
+
const activeIndexRef = useRef(-1);
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
activeIndexRef.current = selectedIndex;
|
|
143
|
+
}, [selectedIndex]);
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
const handler = (e) => {
|
|
146
|
+
if (e.key === "ArrowDown") {
|
|
147
|
+
e.preventDefault();
|
|
148
|
+
const next = Math.min(activeIndexRef.current + 1, flatArticles.length - 1);
|
|
149
|
+
onSelectArticle(flatArticles[next].id, next);
|
|
150
|
+
scrollToSelected(next);
|
|
151
|
+
} else if (e.key === "ArrowUp") {
|
|
152
|
+
e.preventDefault();
|
|
153
|
+
const prev = Math.max(activeIndexRef.current - 1, 0);
|
|
154
|
+
onSelectArticle(flatArticles[prev].id, prev);
|
|
155
|
+
scrollToSelected(prev);
|
|
156
|
+
} else if (e.key === "Enter" && selectedId) {
|
|
157
|
+
e.preventDefault();
|
|
158
|
+
const idx = flatArticles.findIndex((a) => a.id === selectedId);
|
|
159
|
+
if (idx !== -1) onSelectArticle(selectedId, idx);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
const widget = document.getElementById("cyguin-docs-widget");
|
|
163
|
+
widget?.addEventListener("keydown", handler);
|
|
164
|
+
return () => widget?.removeEventListener("keydown", handler);
|
|
165
|
+
}, [flatArticles, selectedId, onSelectArticle]);
|
|
166
|
+
function scrollToSelected(index) {
|
|
167
|
+
const buttons = navRef.current?.querySelectorAll("[data-article-btn]");
|
|
168
|
+
buttons?.[index]?.scrollIntoView({ block: "nearest" });
|
|
169
|
+
}
|
|
170
|
+
function toggleSection(section) {
|
|
171
|
+
setExpandedSections((prev) => {
|
|
172
|
+
const next = new Set(prev);
|
|
173
|
+
if (next.has(section)) next.delete(section);
|
|
174
|
+
else next.add(section);
|
|
175
|
+
return next;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
let articleIdx = 0;
|
|
179
|
+
return /* @__PURE__ */ jsxs("div", { ref: navRef, style: { overflowY: "auto", flex: 1, borderRight: "1px solid var(--cyguin-docs-border)" }, children: [
|
|
180
|
+
groups.length === 0 && /* @__PURE__ */ jsx("p", { style: { padding: "16px", color: "var(--cyguin-docs-muted)", fontSize: "13px", textAlign: "center", fontFamily: "var(--cyguin-docs-font)" }, children: searchQuery ? "No results found" : "No articles yet" }),
|
|
181
|
+
groups.map((group) => {
|
|
182
|
+
const isExpanded = expandedSections.has(group.section);
|
|
183
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
184
|
+
/* @__PURE__ */ jsxs(
|
|
185
|
+
"button",
|
|
186
|
+
{
|
|
187
|
+
onClick: () => toggleSection(group.section),
|
|
188
|
+
style: {
|
|
189
|
+
width: "100%",
|
|
190
|
+
padding: "8px 12px",
|
|
191
|
+
background: "none",
|
|
192
|
+
border: "none",
|
|
193
|
+
cursor: "pointer",
|
|
194
|
+
display: "flex",
|
|
195
|
+
alignItems: "center",
|
|
196
|
+
gap: "6px",
|
|
197
|
+
color: "var(--cyguin-docs-text)",
|
|
198
|
+
fontSize: "12px",
|
|
199
|
+
fontWeight: 700,
|
|
200
|
+
fontFamily: "var(--cyguin-docs-font)",
|
|
201
|
+
textTransform: "uppercase",
|
|
202
|
+
letterSpacing: "0.05em"
|
|
203
|
+
},
|
|
204
|
+
children: [
|
|
205
|
+
/* @__PURE__ */ jsx(ChevronRightIcon, { expanded: isExpanded }),
|
|
206
|
+
group.section
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
),
|
|
210
|
+
isExpanded && group.articles.map((article) => {
|
|
211
|
+
const idx = articleIdx++;
|
|
212
|
+
const isSelected = article.id === selectedId;
|
|
213
|
+
return /* @__PURE__ */ jsx(
|
|
214
|
+
"button",
|
|
215
|
+
{
|
|
216
|
+
"data-article-btn": true,
|
|
217
|
+
onClick: () => onSelectArticle(article.id, idx),
|
|
218
|
+
style: {
|
|
219
|
+
width: "100%",
|
|
220
|
+
padding: "6px 12px 6px 28px",
|
|
221
|
+
background: isSelected ? "var(--cyguin-docs-accent)" : "none",
|
|
222
|
+
color: isSelected ? "var(--cyguin-accent-fg, #0a0a0a)" : "var(--cyguin-docs-text)",
|
|
223
|
+
border: "none",
|
|
224
|
+
cursor: "pointer",
|
|
225
|
+
textAlign: "left",
|
|
226
|
+
fontSize: "13px",
|
|
227
|
+
fontFamily: "var(--cyguin-docs-font)",
|
|
228
|
+
borderRadius: "var(--cyguin-docs-radius)",
|
|
229
|
+
margin: "1px 6px",
|
|
230
|
+
whiteSpace: "nowrap",
|
|
231
|
+
overflow: "hidden",
|
|
232
|
+
textOverflow: "ellipsis"
|
|
233
|
+
},
|
|
234
|
+
children: article.title
|
|
235
|
+
},
|
|
236
|
+
article.id
|
|
237
|
+
);
|
|
238
|
+
})
|
|
239
|
+
] }, group.section);
|
|
240
|
+
})
|
|
241
|
+
] });
|
|
242
|
+
}
|
|
243
|
+
function DocsArticleView({ article, onBack }) {
|
|
244
|
+
const contentRef = useRef(null);
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
if (contentRef.current) {
|
|
247
|
+
contentRef.current.scrollTop = 0;
|
|
248
|
+
}
|
|
249
|
+
}, [article?.id]);
|
|
250
|
+
if (!article) {
|
|
251
|
+
return /* @__PURE__ */ jsx("div", { style: { flex: 2, display: "flex", alignItems: "center", justifyContent: "center", color: "var(--cyguin-docs-muted)", fontSize: "14px", fontFamily: "var(--cyguin-docs-font)" }, children: "Select an article to read" });
|
|
252
|
+
}
|
|
253
|
+
return /* @__PURE__ */ jsxs("div", { style: { flex: 2, display: "flex", flexDirection: "column", overflow: "hidden" }, children: [
|
|
254
|
+
/* @__PURE__ */ jsxs("div", { style: { padding: "12px 16px", borderBottom: "1px solid var(--cyguin-docs-border)", display: "flex", alignItems: "center", gap: "8px" }, children: [
|
|
255
|
+
/* @__PURE__ */ jsx(
|
|
256
|
+
"button",
|
|
257
|
+
{
|
|
258
|
+
onClick: onBack,
|
|
259
|
+
"aria-label": "Back to list",
|
|
260
|
+
style: { background: "none", border: "none", cursor: "pointer", color: "var(--cyguin-docs-muted)", display: "flex", alignItems: "center", padding: "4px", borderRadius: "var(--cyguin-docs-radius)" },
|
|
261
|
+
children: /* @__PURE__ */ jsx(BackIcon, {})
|
|
262
|
+
}
|
|
263
|
+
),
|
|
264
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: "15px", fontWeight: 700, color: "var(--cyguin-docs-text)", fontFamily: "var(--cyguin-docs-font)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: article.title })
|
|
265
|
+
] }),
|
|
266
|
+
/* @__PURE__ */ jsx(
|
|
267
|
+
"div",
|
|
268
|
+
{
|
|
269
|
+
ref: contentRef,
|
|
270
|
+
style: { flex: 1, overflowY: "auto", padding: "20px 24px", fontSize: "14px", lineHeight: 1.7, color: "var(--cyguin-docs-text)", fontFamily: "var(--cyguin-docs-font)" },
|
|
271
|
+
dangerouslySetInnerHTML: { __html: renderMarkdown(article.body_md) }
|
|
272
|
+
}
|
|
273
|
+
)
|
|
274
|
+
] });
|
|
275
|
+
}
|
|
276
|
+
function DocsPanel({ mode, open, onClose, groups, selectedId, selectedIndex, searchQuery, onSearchChange, onSelectArticle, onBack, article }) {
|
|
277
|
+
const isMobile = typeof window !== "undefined" && window.innerWidth < 640;
|
|
278
|
+
const panelStyle = {
|
|
279
|
+
position: "fixed",
|
|
280
|
+
top: 0,
|
|
281
|
+
right: 0,
|
|
282
|
+
bottom: 0,
|
|
283
|
+
width: isMobile ? "100vw" : mode === "sidebar" ? "420px" : "680px",
|
|
284
|
+
maxWidth: "100vw",
|
|
285
|
+
background: "var(--cyguin-docs-bg)",
|
|
286
|
+
borderRadius: mode === "sidebar" || isMobile ? 0 : "var(--cyguin-docs-radius)",
|
|
287
|
+
boxShadow: mode === "modal" ? "var(--cyguin-docs-shadow)" : "none",
|
|
288
|
+
border: mode === "modal" && !isMobile ? "1px solid var(--cyguin-docs-border)" : "none",
|
|
289
|
+
display: "flex",
|
|
290
|
+
flexDirection: "column",
|
|
291
|
+
zIndex: 9999,
|
|
292
|
+
transform: open ? "translateX(0)" : "translateX(100%)",
|
|
293
|
+
transition: "transform 0.25s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
294
|
+
fontFamily: "var(--cyguin-docs-font)"
|
|
295
|
+
};
|
|
296
|
+
if (!open) return null;
|
|
297
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
298
|
+
mode === "modal" && /* @__PURE__ */ jsx(
|
|
299
|
+
"div",
|
|
300
|
+
{
|
|
301
|
+
onClick: onClose,
|
|
302
|
+
style: {
|
|
303
|
+
position: "fixed",
|
|
304
|
+
inset: 0,
|
|
305
|
+
background: `rgba(0, 0, 0, ${parseFloat(String(0.5))})`,
|
|
306
|
+
zIndex: 9998
|
|
307
|
+
},
|
|
308
|
+
"aria-hidden": "true"
|
|
309
|
+
}
|
|
310
|
+
),
|
|
311
|
+
/* @__PURE__ */ jsxs("div", { id: "cyguin-docs-widget", role: "dialog", "aria-modal": mode === "modal", "aria-label": "Documentation", style: panelStyle, children: [
|
|
312
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", padding: "12px 12px 8px", borderBottom: "1px solid var(--cyguin-docs-border)" }, children: [
|
|
313
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1, fontWeight: 700, fontSize: "15px", color: "var(--cyguin-docs-text)", fontFamily: "var(--cyguin-docs-font)" }, children: "Help Center" }),
|
|
314
|
+
/* @__PURE__ */ jsx(
|
|
315
|
+
"button",
|
|
316
|
+
{
|
|
317
|
+
onClick: onClose,
|
|
318
|
+
"aria-label": "Close",
|
|
319
|
+
style: { background: "none", border: "none", cursor: "pointer", color: "var(--cyguin-docs-muted)", display: "flex", alignItems: "center", padding: "4px", borderRadius: "var(--cyguin-docs-radius)" },
|
|
320
|
+
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
] }),
|
|
324
|
+
/* @__PURE__ */ jsx(DocsSearch, { value: searchQuery, onChange: onSearchChange }),
|
|
325
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flex: 1, overflow: "hidden" }, children: [
|
|
326
|
+
/* @__PURE__ */ jsx("div", { style: { width: isMobile ? "100%" : "180px", display: "flex", flexDirection: "column" }, children: /* @__PURE__ */ jsx(
|
|
327
|
+
DocsNav,
|
|
328
|
+
{
|
|
329
|
+
groups,
|
|
330
|
+
selectedId,
|
|
331
|
+
selectedIndex,
|
|
332
|
+
onSelectArticle,
|
|
333
|
+
searchQuery
|
|
334
|
+
}
|
|
335
|
+
) }),
|
|
336
|
+
!isMobile && /* @__PURE__ */ jsx(DocsArticleView, { article, onBack }),
|
|
337
|
+
isMobile && article && /* @__PURE__ */ jsx(DocsArticleView, { article, onBack })
|
|
338
|
+
] })
|
|
339
|
+
] })
|
|
340
|
+
] });
|
|
341
|
+
}
|
|
342
|
+
function DocsWidget({
|
|
343
|
+
apiUrl = "/api/docs",
|
|
344
|
+
mode = "modal",
|
|
345
|
+
triggerLabel = "Help",
|
|
346
|
+
defaultOpen = false,
|
|
347
|
+
className = ""
|
|
348
|
+
}) {
|
|
349
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
350
|
+
const [articles, setArticles] = useState([]);
|
|
351
|
+
const [loading, setLoading] = useState(false);
|
|
352
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
353
|
+
const [selectedId, setSelectedId] = useState(null);
|
|
354
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
355
|
+
const [selectedArticle, setSelectedArticle] = useState(null);
|
|
356
|
+
useEffect(() => {
|
|
357
|
+
if (open && articles.length === 0) {
|
|
358
|
+
setLoading(true);
|
|
359
|
+
fetch(apiUrl).then((r) => r.json()).then((data) => {
|
|
360
|
+
const arts = Array.isArray(data) ? data : data.articles ?? data.data ?? [];
|
|
361
|
+
setArticles(arts);
|
|
362
|
+
}).catch(() => setArticles([])).finally(() => setLoading(false));
|
|
363
|
+
}
|
|
364
|
+
}, [open, apiUrl, articles.length]);
|
|
365
|
+
useEffect(() => {
|
|
366
|
+
if (selectedId) {
|
|
367
|
+
const art = articles.find((a) => a.id === selectedId);
|
|
368
|
+
setSelectedArticle(art ?? null);
|
|
369
|
+
} else {
|
|
370
|
+
setSelectedArticle(null);
|
|
371
|
+
}
|
|
372
|
+
}, [selectedId, articles]);
|
|
373
|
+
useEffect(() => {
|
|
374
|
+
if (!open) return;
|
|
375
|
+
const handler = (e) => {
|
|
376
|
+
if (e.key === "Escape") setOpen(false);
|
|
377
|
+
};
|
|
378
|
+
document.addEventListener("keydown", handler);
|
|
379
|
+
return () => document.removeEventListener("keydown", handler);
|
|
380
|
+
}, [open]);
|
|
381
|
+
const handleSearchChange = useCallback((q) => {
|
|
382
|
+
setSearchQuery(q);
|
|
383
|
+
setSelectedId(null);
|
|
384
|
+
setSelectedIndex(0);
|
|
385
|
+
setSelectedArticle(null);
|
|
386
|
+
}, []);
|
|
387
|
+
const handleSelectArticle = useCallback((id, index) => {
|
|
388
|
+
setSelectedId(id);
|
|
389
|
+
setSelectedIndex(index);
|
|
390
|
+
const art = articles.find((a) => a.id === id);
|
|
391
|
+
setSelectedArticle(art ?? null);
|
|
392
|
+
}, [articles]);
|
|
393
|
+
const handleBack = useCallback(() => {
|
|
394
|
+
setSelectedId(null);
|
|
395
|
+
setSelectedArticle(null);
|
|
396
|
+
}, []);
|
|
397
|
+
const filteredArticles = searchQuery.trim() ? articles.filter(
|
|
398
|
+
(a) => a.title.toLowerCase().includes(searchQuery.toLowerCase()) || a.body_md.toLowerCase().includes(searchQuery.toLowerCase())
|
|
399
|
+
) : articles;
|
|
400
|
+
const groups = groupBySection(filteredArticles);
|
|
401
|
+
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { className, style: { fontFamily: "var(--cyguin-docs-font)" }, children: [
|
|
402
|
+
/* @__PURE__ */ jsx(DocsTrigger, { label: triggerLabel, onClick: () => setOpen(true) }),
|
|
403
|
+
/* @__PURE__ */ jsx(
|
|
404
|
+
DocsPanel,
|
|
405
|
+
{
|
|
406
|
+
mode,
|
|
407
|
+
open,
|
|
408
|
+
onClose: () => setOpen(false),
|
|
409
|
+
groups,
|
|
410
|
+
selectedId,
|
|
411
|
+
selectedIndex,
|
|
412
|
+
searchQuery,
|
|
413
|
+
onSearchChange: handleSearchChange,
|
|
414
|
+
onSelectArticle: handleSelectArticle,
|
|
415
|
+
onBack: handleBack,
|
|
416
|
+
article: selectedArticle
|
|
417
|
+
}
|
|
418
|
+
)
|
|
419
|
+
] }) });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/types.ts
|
|
423
|
+
var defaultCssVars = {
|
|
424
|
+
"--cyguin-docs-bg": "var(--cyguin-surface, #ffffff)",
|
|
425
|
+
"--cyguin-docs-text": "var(--cyguin-text, #1a1a1a)",
|
|
426
|
+
"--cyguin-docs-border": "var(--cyguin-border, #e5e5e5)",
|
|
427
|
+
"--cyguin-docs-accent": "var(--cyguin-primary, #6366f1)",
|
|
428
|
+
"--cyguin-docs-muted": "var(--cyguin-muted, #737373)",
|
|
429
|
+
"--cyguin-docs-backdrop-opacity": "0.5",
|
|
430
|
+
"--cyguin-docs-radius": "var(--cyguin-radius, 8px)",
|
|
431
|
+
"--cyguin-docs-shadow": "var(--cyguin-shadow, 0 4px 24px rgba(0,0,0,0.12))",
|
|
432
|
+
"--cyguin-docs-trigger-size": "48px",
|
|
433
|
+
"--cyguin-docs-font": "var(--cyguin-font, system-ui, sans-serif)"
|
|
434
|
+
};
|
|
435
|
+
export {
|
|
436
|
+
DocsWidget,
|
|
437
|
+
defaultCssVars
|
|
438
|
+
};
|
package/dist/next.d.mts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { b as DocsAdapter } from './types-0oOQLVHA.mjs';
|
|
2
|
+
export { a as DocArticle } from './types-0oOQLVHA.mjs';
|
|
3
|
+
|
|
4
|
+
declare function createDocsHandler(options: {
|
|
5
|
+
adapter?: DocsAdapter;
|
|
6
|
+
}): (req: Request, context?: {
|
|
7
|
+
params?: Record<string, string | string[]>;
|
|
8
|
+
}) => Promise<Response>;
|
|
9
|
+
|
|
10
|
+
export { DocsAdapter, createDocsHandler };
|
package/dist/next.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { b as DocsAdapter } from './types-0oOQLVHA.js';
|
|
2
|
+
export { a as DocArticle } from './types-0oOQLVHA.js';
|
|
3
|
+
|
|
4
|
+
declare function createDocsHandler(options: {
|
|
5
|
+
adapter?: DocsAdapter;
|
|
6
|
+
}): (req: Request, context?: {
|
|
7
|
+
params?: Record<string, string | string[]>;
|
|
8
|
+
}) => Promise<Response>;
|
|
9
|
+
|
|
10
|
+
export { DocsAdapter, createDocsHandler };
|
package/dist/next.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/handlers/route.ts
|
|
2
|
+
var _adapter = null;
|
|
3
|
+
function getDocsAdapter() {
|
|
4
|
+
if (!_adapter) {
|
|
5
|
+
throw new Error("Docs adapter not set. Call setDocsAdapter() first.");
|
|
6
|
+
}
|
|
7
|
+
return _adapter;
|
|
8
|
+
}
|
|
9
|
+
function slugify(title) {
|
|
10
|
+
return title.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
11
|
+
}
|
|
12
|
+
function kebabToTitle(kebab) {
|
|
13
|
+
return kebab.replace(/-/g, " ");
|
|
14
|
+
}
|
|
15
|
+
function createDocsHandler(options) {
|
|
16
|
+
const adapter = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _ => _.adapter]), () => ( getDocsAdapter()));
|
|
17
|
+
return async function handler(req, context) {
|
|
18
|
+
const segments = _nullishCoalesce(_optionalChain([context, 'optionalAccess', _2 => _2.params, 'optionalAccess', _3 => _3.cyguin]), () => ( []));
|
|
19
|
+
if (req.method !== "GET") {
|
|
20
|
+
return Response.json({ error: "Method not allowed" }, { status: 405 });
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
if (segments.length === 0) {
|
|
24
|
+
const articles = await adapter.list({ published: true });
|
|
25
|
+
return Response.json(articles);
|
|
26
|
+
}
|
|
27
|
+
if (segments.length === 1) {
|
|
28
|
+
const [section] = segments;
|
|
29
|
+
const articles = await adapter.list({ section, published: true });
|
|
30
|
+
return Response.json(articles);
|
|
31
|
+
}
|
|
32
|
+
if (segments.length === 2) {
|
|
33
|
+
const [section, slug] = segments;
|
|
34
|
+
const titleQuery = kebabToTitle(slug);
|
|
35
|
+
const articles = await adapter.list({ section, published: true });
|
|
36
|
+
const article = articles.find(
|
|
37
|
+
(a) => slugify(a.title) === slug || a.title.toLowerCase() === titleQuery.toLowerCase()
|
|
38
|
+
);
|
|
39
|
+
if (!article) {
|
|
40
|
+
return Response.json({ error: "Article not found" }, { status: 404 });
|
|
41
|
+
}
|
|
42
|
+
return Response.json(article);
|
|
43
|
+
}
|
|
44
|
+
return Response.json({ error: "Not found" }, { status: 404 });
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error("Docs handler error:", err);
|
|
47
|
+
return Response.json({ error: "Internal server error" }, { status: 500 });
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
exports.createDocsHandler = createDocsHandler;
|
package/dist/next.mjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/handlers/route.ts
|
|
2
|
+
var _adapter = null;
|
|
3
|
+
function getDocsAdapter() {
|
|
4
|
+
if (!_adapter) {
|
|
5
|
+
throw new Error("Docs adapter not set. Call setDocsAdapter() first.");
|
|
6
|
+
}
|
|
7
|
+
return _adapter;
|
|
8
|
+
}
|
|
9
|
+
function slugify(title) {
|
|
10
|
+
return title.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
11
|
+
}
|
|
12
|
+
function kebabToTitle(kebab) {
|
|
13
|
+
return kebab.replace(/-/g, " ");
|
|
14
|
+
}
|
|
15
|
+
function createDocsHandler(options) {
|
|
16
|
+
const adapter = options?.adapter ?? getDocsAdapter();
|
|
17
|
+
return async function handler(req, context) {
|
|
18
|
+
const segments = context?.params?.cyguin ?? [];
|
|
19
|
+
if (req.method !== "GET") {
|
|
20
|
+
return Response.json({ error: "Method not allowed" }, { status: 405 });
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
if (segments.length === 0) {
|
|
24
|
+
const articles = await adapter.list({ published: true });
|
|
25
|
+
return Response.json(articles);
|
|
26
|
+
}
|
|
27
|
+
if (segments.length === 1) {
|
|
28
|
+
const [section] = segments;
|
|
29
|
+
const articles = await adapter.list({ section, published: true });
|
|
30
|
+
return Response.json(articles);
|
|
31
|
+
}
|
|
32
|
+
if (segments.length === 2) {
|
|
33
|
+
const [section, slug] = segments;
|
|
34
|
+
const titleQuery = kebabToTitle(slug);
|
|
35
|
+
const articles = await adapter.list({ section, published: true });
|
|
36
|
+
const article = articles.find(
|
|
37
|
+
(a) => slugify(a.title) === slug || a.title.toLowerCase() === titleQuery.toLowerCase()
|
|
38
|
+
);
|
|
39
|
+
if (!article) {
|
|
40
|
+
return Response.json({ error: "Article not found" }, { status: 404 });
|
|
41
|
+
}
|
|
42
|
+
return Response.json(article);
|
|
43
|
+
}
|
|
44
|
+
return Response.json({ error: "Not found" }, { status: 404 });
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error("Docs handler error:", err);
|
|
47
|
+
return Response.json({ error: "Internal server error" }, { status: 500 });
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
createDocsHandler
|
|
53
|
+
};
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--cyguin-docs-bg: var(--cyguin-bg, #ffffff);
|
|
3
|
+
--cyguin-docs-text: var(--cyguin-fg, #0a0a0a);
|
|
4
|
+
--cyguin-docs-border: var(--cyguin-border, #e5e5e5);
|
|
5
|
+
--cyguin-docs-accent: var(--cyguin-accent, #f5a800);
|
|
6
|
+
--cyguin-docs-muted: var(--cyguin-fg-muted, #888888);
|
|
7
|
+
--cyguin-docs-backdrop-opacity: 0.5;
|
|
8
|
+
--cyguin-docs-radius: var(--cyguin-radius, 6px);
|
|
9
|
+
--cyguin-docs-shadow: var(--cyguin-shadow, 0 1px 4px rgba(0,0,0,0.08));
|
|
10
|
+
--cyguin-docs-font: system-ui, sans-serif;
|
|
11
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
interface DocArticle {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
body_md: string;
|
|
5
|
+
section: string;
|
|
6
|
+
article_order: number;
|
|
7
|
+
published_at: number | null;
|
|
8
|
+
}
|
|
9
|
+
interface CreateArticleInput {
|
|
10
|
+
title: string;
|
|
11
|
+
body_md: string;
|
|
12
|
+
section: string;
|
|
13
|
+
article_order: number;
|
|
14
|
+
published_at?: number | null;
|
|
15
|
+
}
|
|
16
|
+
interface UpdateArticleInput {
|
|
17
|
+
title?: string;
|
|
18
|
+
body_md?: string;
|
|
19
|
+
}
|
|
20
|
+
interface DocsAdapter {
|
|
21
|
+
list(params?: {
|
|
22
|
+
section?: string;
|
|
23
|
+
published?: boolean;
|
|
24
|
+
}): Promise<DocArticle[]>;
|
|
25
|
+
get(id: string): Promise<DocArticle | null>;
|
|
26
|
+
create(data: CreateArticleInput): Promise<DocArticle>;
|
|
27
|
+
update(id: string, data: Partial<UpdateArticleInput>): Promise<DocArticle>;
|
|
28
|
+
delete(id: string): Promise<void>;
|
|
29
|
+
reorder(id: string, newOrder: number): Promise<void>;
|
|
30
|
+
moveSection(id: string, newSection: string): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
interface DocsWidgetProps {
|
|
33
|
+
apiUrl?: string;
|
|
34
|
+
mode?: 'modal' | 'sidebar';
|
|
35
|
+
triggerLabel?: string;
|
|
36
|
+
defaultOpen?: boolean;
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
declare const defaultCssVars: {
|
|
40
|
+
readonly '--cyguin-docs-bg': "var(--cyguin-surface, #ffffff)";
|
|
41
|
+
readonly '--cyguin-docs-text': "var(--cyguin-text, #1a1a1a)";
|
|
42
|
+
readonly '--cyguin-docs-border': "var(--cyguin-border, #e5e5e5)";
|
|
43
|
+
readonly '--cyguin-docs-accent': "var(--cyguin-primary, #6366f1)";
|
|
44
|
+
readonly '--cyguin-docs-muted': "var(--cyguin-muted, #737373)";
|
|
45
|
+
readonly '--cyguin-docs-backdrop-opacity': "0.5";
|
|
46
|
+
readonly '--cyguin-docs-radius': "var(--cyguin-radius, 8px)";
|
|
47
|
+
readonly '--cyguin-docs-shadow': "var(--cyguin-shadow, 0 4px 24px rgba(0,0,0,0.12))";
|
|
48
|
+
readonly '--cyguin-docs-trigger-size': "48px";
|
|
49
|
+
readonly '--cyguin-docs-font': "var(--cyguin-font, system-ui, sans-serif)";
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export { type DocsWidgetProps as D, type DocArticle as a, type DocsAdapter as b, defaultCssVars as d };
|