@solutiondesk/help 1.0.0-a316e3a → 1.0.0-a6af2d6
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/README.md +4 -4
- package/dist/fumadocs.d.ts +11 -0
- package/dist/fumadocs.js +42 -29
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ const { page } = await help.page("guides/login"); // one page with its text
|
|
|
29
29
|
|
|
30
30
|
| Method | API | Returns |
|
|
31
31
|
| --- | --- | --- |
|
|
32
|
-
| `site({ lang })` | `GET …/site` | The site (`name`, `languages`, `websiteUrl`, its own building `blocks`) and its navigation tree: pages, folders (with index page, `root` tab, `group`, `defaultOpen`, `collapsible`), separators and links. Empty folders are left out. |
|
|
32
|
+
| `site({ lang })` | `GET …/site` | The site (`name`, `languages`, `websiteUrl`, its own building `blocks`) and its navigation tree: pages, folders (with index page, `listIndex`, `root` tab, `description`, `group`, `defaultOpen`, `collapsible`), separators and links. Empty folders are left out. |
|
|
33
33
|
| `pages({ lang })` | `GET …/pages` | Every visible page without its text, in reading order (for builds and sitemaps). |
|
|
34
34
|
| `page(path, { lang, redirect })` | `GET …/page?path=` | One page: `markdown`, `html`, `headings`, `blocks`, `customBlocks`, `breadcrumbs`, `previous`/`next`. The home page has the path `""`. |
|
|
35
35
|
| `pageMarkdown(path, { lang })` | `GET …/page.md?path=` | One page as plain Markdown. |
|
|
@@ -37,7 +37,7 @@ const { page } = await help.page("guides/login"); // one page with its text
|
|
|
37
37
|
| `preview(token, { lang })` | `GET …/preview?token=` | A page in any state (drafts too) for your preview route. |
|
|
38
38
|
| `llmsTxt({ lang })` / `llmsFullTxt({ lang })` | `GET …/llms.txt`, `…/llms-full.txt` | The site for language models: the index, or every page as Markdown. |
|
|
39
39
|
|
|
40
|
-
`html` is safe to insert as it is: raw HTML in the Markdown is shown as text, links only go to web addresses, `mailto:`, paths and anchors, images only come from your help center. Headings carry the `id`s listed in `headings
|
|
40
|
+
`html` is safe to insert as it is: raw HTML in the Markdown is shown as text, links only go to web addresses, `mailto:`, paths and anchors, images only come from your help center. Headings carry the `id`s listed in `headings`, made the way GitHub and Fumadocs make them (umlauts kept, `-1` for a repeat), so a site moved from Fumadocs keeps its `#links`; a step's title is a heading too.
|
|
41
41
|
|
|
42
42
|
### Moved pages
|
|
43
43
|
|
|
@@ -104,7 +104,7 @@ The navigation becomes the files Fumadocs knows from a content folder:
|
|
|
104
104
|
| --- | --- |
|
|
105
105
|
| page `guides/login` | `guides/login.mdx` (`slugs: ["guides", "login"]`) |
|
|
106
106
|
| home page | `index.mdx` at the top, or `(core)/index.mdx` when it is a top-level group's own page |
|
|
107
|
-
| folder `guides` | `guides/meta.json` with `title`, `icon`, `root`, `defaultOpen`, `collapsible`, `pages` |
|
|
107
|
+
| folder `guides` | `guides/meta.json` with `title`, `description` (shown under a tab's name), `pagesIndex` — or, with `listIndex`, `"index"` first in `pages` (after a leading separator), so the page is an entry and the name only opens the folder —, `icon`, `root`, `defaultOpen`, `collapsible`, `pages` |
|
|
108
108
|
| group folder `core` (adds no address part) | `(core)/` — a Fumadocs folder group, so `(core)/sign-up/index.mdx` is still `sign-up` |
|
|
109
109
|
| folder's own page | `guides/index.mdx` (`pagesIndex: "index"`, so it also works for `root` tabs) |
|
|
110
110
|
| any other page with the slug `index` | `guides/index_.mdx` (`slugs: ["guides", "index"]`), so it never collides with the folder's own page |
|
|
@@ -286,7 +286,7 @@ document.querySelector("article")!.innerHTML = page.html;
|
|
|
286
286
|
| Block | HTML |
|
|
287
287
|
| --- | --- |
|
|
288
288
|
| callout | `<div class="sd-callout" data-type="info\|warn\|error\|success\|idea" role="note">` with `<p class="sd-callout-title">` and `<div class="sd-callout-body">` |
|
|
289
|
-
| steps | `<ol class="sd-steps">` of `<li class="sd-step">` with `<
|
|
289
|
+
| steps | `<ol class="sd-steps">` of `<li class="sd-step">` with `<h3 class="sd-step-title" id="…">` and `<div class="sd-step-body">` |
|
|
290
290
|
| tabs | `<div class="sd-tabs">` of `<section class="sd-tab" data-title="…">` (build the tab buttons from `data-title`; `page.blocks` says when a page has tabs) |
|
|
291
291
|
| cards | `<div class="sd-cards">` of `<a class="sd-card" href="…" data-icon="Rocket">` (or `<div class="sd-card">`) with `<p class="sd-card-title">` and `<div class="sd-card-body">` |
|
|
292
292
|
| titled code | `<figure class="sd-code"><figcaption>client.ts</figcaption><pre><code class="language-ts">` |
|
package/dist/fumadocs.d.ts
CHANGED
|
@@ -81,6 +81,8 @@ interface HelpPublicTreeFolder {
|
|
|
81
81
|
/** The folder's path (its index page, if any, lives here; a group's is its parent's). */
|
|
82
82
|
path: string;
|
|
83
83
|
title: string;
|
|
84
|
+
/** A short line under a tab's name in the tab menu (Fumadocs `description`); null without one. */
|
|
85
|
+
description: string | null;
|
|
84
86
|
icon: string | null;
|
|
85
87
|
/** A tab (top level only; Fumadocs `root`). */
|
|
86
88
|
root: boolean;
|
|
@@ -93,6 +95,11 @@ interface HelpPublicTreeFolder {
|
|
|
93
95
|
collapsible: boolean;
|
|
94
96
|
/** The folder's own page, shown at the folder's path; null without one. */
|
|
95
97
|
index: HelpPublicTreePage | null;
|
|
98
|
+
/**
|
|
99
|
+
* List `index` as the folder's first entry (after a section title it starts
|
|
100
|
+
* with); the folder's name then only opens it (Fumadocs: `index` in `pages`).
|
|
101
|
+
*/
|
|
102
|
+
listIndex: boolean;
|
|
96
103
|
children: HelpPublicTreeNode[];
|
|
97
104
|
}
|
|
98
105
|
/** A labelled divider between entries (Fumadocs `---Title---`). */
|
|
@@ -318,6 +325,8 @@ interface HelpStepsProps {
|
|
|
318
325
|
}
|
|
319
326
|
interface HelpStepProps {
|
|
320
327
|
title?: string;
|
|
328
|
+
/** The title's anchor id (a step's title is a heading of the page, level 3). */
|
|
329
|
+
id?: string;
|
|
321
330
|
children: ReactNode;
|
|
322
331
|
}
|
|
323
332
|
interface HelpTabsProps {
|
|
@@ -664,6 +673,8 @@ interface HelpPageData extends PageData {
|
|
|
664
673
|
/** What every folder's `meta.json` of a help source carries. */
|
|
665
674
|
interface HelpMetaData extends MetaData {
|
|
666
675
|
title?: string;
|
|
676
|
+
/** Shown under a tab's name in Fumadocs' tab menu. */
|
|
677
|
+
description?: string;
|
|
667
678
|
icon?: string;
|
|
668
679
|
root?: boolean;
|
|
669
680
|
defaultOpen?: boolean;
|
package/dist/fumadocs.js
CHANGED
|
@@ -112,28 +112,25 @@ function codeInfo(lang, meta) {
|
|
|
112
112
|
const title = (match ? match[1] ?? match[2] ?? match[3] : void 0)?.trim() || void 0;
|
|
113
113
|
return { language, title };
|
|
114
114
|
}
|
|
115
|
-
var
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
};
|
|
130
|
-
function headingSlug(text) {
|
|
131
|
-
return text.normalize("NFC").toLowerCase().replace(/[äöüßæœøåđðþłı]/g, (ch) => TRANSLITERATIONS[ch] ?? ch).normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96).replace(/-+$/, "");
|
|
115
|
+
var GITHUB_SLUG_DROP = /[^\p{L}\p{M}\p{N}\p{Pc} -]/gu;
|
|
116
|
+
function headingId(text, used) {
|
|
117
|
+
const base = text.toLowerCase().replace(GITHUB_SLUG_DROP, "").replace(/ /g, "-") || "section";
|
|
118
|
+
let id = base;
|
|
119
|
+
while (used.has(id)) {
|
|
120
|
+
const count = (used.get(base) ?? 0) + 1;
|
|
121
|
+
used.set(base, count);
|
|
122
|
+
id = `${base}-${count}`;
|
|
123
|
+
}
|
|
124
|
+
used.set(id, 0);
|
|
125
|
+
return id;
|
|
126
|
+
}
|
|
127
|
+
function isTitledStep(node) {
|
|
128
|
+
return node.type === "containerDirective" && node.name === "step" && !!directiveTitle(node);
|
|
132
129
|
}
|
|
133
130
|
function collectHeadings(root) {
|
|
134
131
|
const out = [];
|
|
135
132
|
const walk = (node) => {
|
|
136
|
-
if (node.type === "heading") out.push(node);
|
|
133
|
+
if (node.type === "heading" || isTitledStep(node)) out.push(node);
|
|
137
134
|
if ("children" in node) for (const child of node.children) walk(child);
|
|
138
135
|
};
|
|
139
136
|
walk(root);
|
|
@@ -149,12 +146,14 @@ function headingIds(root, known, override) {
|
|
|
149
146
|
return ids;
|
|
150
147
|
}
|
|
151
148
|
const used = /* @__PURE__ */ new Map();
|
|
149
|
+
const withoutBlocks = override ? (node) => {
|
|
150
|
+
if (override(node) === void 0) return void 0;
|
|
151
|
+
if (node.type !== "textDirective") return "";
|
|
152
|
+
return node.children.map((child) => plainText(child)).join("").trim();
|
|
153
|
+
} : void 0;
|
|
152
154
|
for (const heading of headings) {
|
|
153
|
-
const text = plainText(heading,
|
|
154
|
-
|
|
155
|
-
const seen = used.get(base) ?? 0;
|
|
156
|
-
used.set(base, seen + 1);
|
|
157
|
-
ids.set(heading, seen === 0 ? base : `${base}-${seen + 1}`);
|
|
155
|
+
const text = heading.type === "heading" ? plainText(heading, withoutBlocks) : (directiveTitle(heading) ?? "").replace(/\s+/g, " ").trim();
|
|
156
|
+
ids.set(heading, headingId(text, used));
|
|
158
157
|
}
|
|
159
158
|
return ids;
|
|
160
159
|
}
|
|
@@ -379,8 +378,8 @@ var PLAIN_CODE_COLORS = {
|
|
|
379
378
|
var helpBodyComponents = {
|
|
380
379
|
Callout: ({ type, title, children }) => /* @__PURE__ */ jsx(Callout, { type, title, children }),
|
|
381
380
|
Steps: ({ children }) => /* @__PURE__ */ jsx(Steps, { children }),
|
|
382
|
-
Step: ({ title, children }) => /* @__PURE__ */ jsxs(Step, { children: [
|
|
383
|
-
title ? /* @__PURE__ */ jsx("h3",
|
|
381
|
+
Step: ({ title, id, children }) => /* @__PURE__ */ jsxs(Step, { children: [
|
|
382
|
+
title ? /* @__PURE__ */ jsx(Heading, { as: "h3", id, children: title }) : null,
|
|
384
383
|
children
|
|
385
384
|
] }),
|
|
386
385
|
Tabs: ({ items, children }) => /* @__PURE__ */ jsx(Tabs, { items, children }),
|
|
@@ -555,7 +554,7 @@ function renderDirective(node, ctx) {
|
|
|
555
554
|
return /* @__PURE__ */ jsx(c.Steps, { children: content.map(
|
|
556
555
|
(child, i) => isDirective(child, "step") ? (
|
|
557
556
|
// biome-ignore lint/suspicious/noArrayIndexKey: static content, never reordered.
|
|
558
|
-
/* @__PURE__ */ jsx(c.Step, { title: directiveTitle(child), children: renderAll(contentOf(child), ctx) }, i)
|
|
557
|
+
/* @__PURE__ */ jsx(c.Step, { title: directiveTitle(child), id: ctx.ids.get(child), children: renderAll(contentOf(child), ctx) }, i)
|
|
559
558
|
) : (
|
|
560
559
|
// A loose block between steps becomes a step of its own.
|
|
561
560
|
// biome-ignore lint/suspicious/noArrayIndexKey: static content, never reordered.
|
|
@@ -563,7 +562,7 @@ function renderDirective(node, ctx) {
|
|
|
563
562
|
)
|
|
564
563
|
) });
|
|
565
564
|
case "step":
|
|
566
|
-
return /* @__PURE__ */ jsx(c.Steps, { children: /* @__PURE__ */ jsx(c.Step, { title, children: renderAll(content, ctx) }) });
|
|
565
|
+
return /* @__PURE__ */ jsx(c.Steps, { children: /* @__PURE__ */ jsx(c.Step, { title, id: ctx.ids.get(node), children: renderAll(content, ctx) }) });
|
|
567
566
|
case "tabs":
|
|
568
567
|
return renderTabs(content, ctx);
|
|
569
568
|
case "tab":
|
|
@@ -792,6 +791,13 @@ function helpStructuredData(markdown, headings, blocks) {
|
|
|
792
791
|
if (current && content) out.headings.push({ id: current, content });
|
|
793
792
|
return;
|
|
794
793
|
}
|
|
794
|
+
if (isTitledStep(node)) {
|
|
795
|
+
current = ids.get(node);
|
|
796
|
+
const content = directiveTitle(node) ?? "";
|
|
797
|
+
if (current) out.headings.push({ id: current, content });
|
|
798
|
+
for (const child of contentOf(node)) walk(child);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
795
801
|
const use = matcher.use(node);
|
|
796
802
|
if (use) {
|
|
797
803
|
add(use.text);
|
|
@@ -864,16 +870,23 @@ function helpTreeToFiles(payload, options) {
|
|
|
864
870
|
};
|
|
865
871
|
files.push({ type: "page", path: file, slugs: slugsOf(page.path), data });
|
|
866
872
|
};
|
|
873
|
+
const folderPages = (folder) => {
|
|
874
|
+
const pages = folder.children.map(helpMetaEntry);
|
|
875
|
+
if (!folder.index || !folder.listIndex) return pages;
|
|
876
|
+
const at = folder.children[0]?.type === "separator" ? 1 : 0;
|
|
877
|
+
return [...pages.slice(0, at), "index", ...pages.slice(at)];
|
|
878
|
+
};
|
|
867
879
|
const addFolder = (folder, dir) => {
|
|
868
880
|
if (folder.index) addPage(folder.index, join(dir, ext("index", "mdx")));
|
|
869
881
|
const data = {
|
|
870
882
|
title: folder.title,
|
|
883
|
+
...folder.description ? { description: folder.description } : {},
|
|
871
884
|
...folder.icon ? { icon: folder.icon } : {},
|
|
872
885
|
...folder.root ? { root: true } : {},
|
|
873
886
|
defaultOpen: folder.defaultOpen,
|
|
874
887
|
collapsible: folder.collapsible,
|
|
875
|
-
...folder.index ? { pagesIndex: "index" } : {},
|
|
876
|
-
pages: folder
|
|
888
|
+
...folder.index && !folder.listIndex ? { pagesIndex: "index" } : {},
|
|
889
|
+
pages: folderPages(folder)
|
|
877
890
|
};
|
|
878
891
|
files.push({ type: "meta", path: join(dir, ext("meta", "json")), data });
|
|
879
892
|
addChildren(folder.children, dir);
|
package/dist/index.d.cts
CHANGED
|
@@ -208,6 +208,8 @@ interface HelpPublicTreeFolder {
|
|
|
208
208
|
/** The folder's path (its index page, if any, lives here; a group's is its parent's). */
|
|
209
209
|
path: string;
|
|
210
210
|
title: string;
|
|
211
|
+
/** A short line under a tab's name in the tab menu (Fumadocs `description`); null without one. */
|
|
212
|
+
description: string | null;
|
|
211
213
|
icon: string | null;
|
|
212
214
|
/** A tab (top level only; Fumadocs `root`). */
|
|
213
215
|
root: boolean;
|
|
@@ -220,6 +222,11 @@ interface HelpPublicTreeFolder {
|
|
|
220
222
|
collapsible: boolean;
|
|
221
223
|
/** The folder's own page, shown at the folder's path; null without one. */
|
|
222
224
|
index: HelpPublicTreePage | null;
|
|
225
|
+
/**
|
|
226
|
+
* List `index` as the folder's first entry (after a section title it starts
|
|
227
|
+
* with); the folder's name then only opens it (Fumadocs: `index` in `pages`).
|
|
228
|
+
*/
|
|
229
|
+
listIndex: boolean;
|
|
223
230
|
children: HelpPublicTreeNode[];
|
|
224
231
|
}
|
|
225
232
|
/** A labelled divider between entries (Fumadocs `---Title---`). */
|
package/dist/index.d.ts
CHANGED
|
@@ -208,6 +208,8 @@ interface HelpPublicTreeFolder {
|
|
|
208
208
|
/** The folder's path (its index page, if any, lives here; a group's is its parent's). */
|
|
209
209
|
path: string;
|
|
210
210
|
title: string;
|
|
211
|
+
/** A short line under a tab's name in the tab menu (Fumadocs `description`); null without one. */
|
|
212
|
+
description: string | null;
|
|
211
213
|
icon: string | null;
|
|
212
214
|
/** A tab (top level only; Fumadocs `root`). */
|
|
213
215
|
root: boolean;
|
|
@@ -220,6 +222,11 @@ interface HelpPublicTreeFolder {
|
|
|
220
222
|
collapsible: boolean;
|
|
221
223
|
/** The folder's own page, shown at the folder's path; null without one. */
|
|
222
224
|
index: HelpPublicTreePage | null;
|
|
225
|
+
/**
|
|
226
|
+
* List `index` as the folder's first entry (after a section title it starts
|
|
227
|
+
* with); the folder's name then only opens it (Fumadocs: `index` in `pages`).
|
|
228
|
+
*/
|
|
229
|
+
listIndex: boolean;
|
|
223
230
|
children: HelpPublicTreeNode[];
|
|
224
231
|
}
|
|
225
232
|
/** A labelled divider between entries (Fumadocs `---Title---`). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solutiondesk/help",
|
|
3
|
-
"version": "1.0.0-
|
|
3
|
+
"version": "1.0.0-a6af2d6",
|
|
4
4
|
"description": "Typed, zero-dependency client for the SolutionDesk public help center API, plus a Fumadocs adapter: navigation, pages, search, previews, llms.txt and webhook verification.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"solutiondesk",
|