@solutiondesk/help 1.0.0-85cf20e → 1.0.0-a316e3a

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 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, `listIndex`, `root` tab, `description`, `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, `root` tab, `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`, 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.
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`.
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`, `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` |
107
+ | folder `guides` | `guides/meta.json` with `title`, `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 `<h3 class="sd-step-title" id="…">` and `<div class="sd-step-body">` |
289
+ | steps | `<ol class="sd-steps">` of `<li class="sd-step">` with `<p class="sd-step-title">` 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">` |
@@ -81,8 +81,6 @@ 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;
86
84
  icon: string | null;
87
85
  /** A tab (top level only; Fumadocs `root`). */
88
86
  root: boolean;
@@ -95,11 +93,6 @@ interface HelpPublicTreeFolder {
95
93
  collapsible: boolean;
96
94
  /** The folder's own page, shown at the folder's path; null without one. */
97
95
  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;
103
96
  children: HelpPublicTreeNode[];
104
97
  }
105
98
  /** A labelled divider between entries (Fumadocs `---Title---`). */
@@ -325,8 +318,6 @@ interface HelpStepsProps {
325
318
  }
326
319
  interface HelpStepProps {
327
320
  title?: string;
328
- /** The title's anchor id (a step's title is a heading of the page, level 3). */
329
- id?: string;
330
321
  children: ReactNode;
331
322
  }
332
323
  interface HelpTabsProps {
@@ -673,8 +664,6 @@ interface HelpPageData extends PageData {
673
664
  /** What every folder's `meta.json` of a help source carries. */
674
665
  interface HelpMetaData extends MetaData {
675
666
  title?: string;
676
- /** Shown under a tab's name in Fumadocs' tab menu. */
677
- description?: string;
678
667
  icon?: string;
679
668
  root?: boolean;
680
669
  defaultOpen?: boolean;
package/dist/fumadocs.js CHANGED
@@ -112,25 +112,28 @@ 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 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);
115
+ var TRANSLITERATIONS = {
116
+ \u00E4: "ae",
117
+ \u00F6: "oe",
118
+ \u00FC: "ue",
119
+ \u00DF: "ss",
120
+ \u00E6: "ae",
121
+ \u0153: "oe",
122
+ \u00F8: "o",
123
+ \u00E5: "a",
124
+ \u0111: "d",
125
+ \u00F0: "d",
126
+ \u00FE: "th",
127
+ \u0142: "l",
128
+ \u0131: "i"
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(/-+$/, "");
129
132
  }
130
133
  function collectHeadings(root) {
131
134
  const out = [];
132
135
  const walk = (node) => {
133
- if (node.type === "heading" || isTitledStep(node)) out.push(node);
136
+ if (node.type === "heading") out.push(node);
134
137
  if ("children" in node) for (const child of node.children) walk(child);
135
138
  };
136
139
  walk(root);
@@ -146,14 +149,12 @@ function headingIds(root, known, override) {
146
149
  return ids;
147
150
  }
148
151
  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;
154
152
  for (const heading of headings) {
155
- const text = heading.type === "heading" ? plainText(heading, withoutBlocks) : (directiveTitle(heading) ?? "").replace(/\s+/g, " ").trim();
156
- ids.set(heading, headingId(text, used));
153
+ const text = plainText(heading, override).replace(/\s+/g, " ").trim();
154
+ const base = headingSlug(text) || "section";
155
+ const seen = used.get(base) ?? 0;
156
+ used.set(base, seen + 1);
157
+ ids.set(heading, seen === 0 ? base : `${base}-${seen + 1}`);
157
158
  }
158
159
  return ids;
159
160
  }
@@ -378,8 +379,8 @@ var PLAIN_CODE_COLORS = {
378
379
  var helpBodyComponents = {
379
380
  Callout: ({ type, title, children }) => /* @__PURE__ */ jsx(Callout, { type, title, children }),
380
381
  Steps: ({ children }) => /* @__PURE__ */ jsx(Steps, { children }),
381
- Step: ({ title, id, children }) => /* @__PURE__ */ jsxs(Step, { children: [
382
- title ? /* @__PURE__ */ jsx(Heading, { as: "h3", id, children: title }) : null,
382
+ Step: ({ title, children }) => /* @__PURE__ */ jsxs(Step, { children: [
383
+ title ? /* @__PURE__ */ jsx("h3", { children: title }) : null,
383
384
  children
384
385
  ] }),
385
386
  Tabs: ({ items, children }) => /* @__PURE__ */ jsx(Tabs, { items, children }),
@@ -554,7 +555,7 @@ function renderDirective(node, ctx) {
554
555
  return /* @__PURE__ */ jsx(c.Steps, { children: content.map(
555
556
  (child, i) => isDirective(child, "step") ? (
556
557
  // biome-ignore lint/suspicious/noArrayIndexKey: static content, never reordered.
557
- /* @__PURE__ */ jsx(c.Step, { title: directiveTitle(child), id: ctx.ids.get(child), children: renderAll(contentOf(child), ctx) }, i)
558
+ /* @__PURE__ */ jsx(c.Step, { title: directiveTitle(child), children: renderAll(contentOf(child), ctx) }, i)
558
559
  ) : (
559
560
  // A loose block between steps becomes a step of its own.
560
561
  // biome-ignore lint/suspicious/noArrayIndexKey: static content, never reordered.
@@ -562,7 +563,7 @@ function renderDirective(node, ctx) {
562
563
  )
563
564
  ) });
564
565
  case "step":
565
- return /* @__PURE__ */ jsx(c.Steps, { children: /* @__PURE__ */ jsx(c.Step, { title, id: ctx.ids.get(node), children: renderAll(content, ctx) }) });
566
+ return /* @__PURE__ */ jsx(c.Steps, { children: /* @__PURE__ */ jsx(c.Step, { title, children: renderAll(content, ctx) }) });
566
567
  case "tabs":
567
568
  return renderTabs(content, ctx);
568
569
  case "tab":
@@ -791,13 +792,6 @@ function helpStructuredData(markdown, headings, blocks) {
791
792
  if (current && content) out.headings.push({ id: current, content });
792
793
  return;
793
794
  }
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
- }
801
795
  const use = matcher.use(node);
802
796
  if (use) {
803
797
  add(use.text);
@@ -870,23 +864,16 @@ function helpTreeToFiles(payload, options) {
870
864
  };
871
865
  files.push({ type: "page", path: file, slugs: slugsOf(page.path), data });
872
866
  };
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
- };
879
867
  const addFolder = (folder, dir) => {
880
868
  if (folder.index) addPage(folder.index, join(dir, ext("index", "mdx")));
881
869
  const data = {
882
870
  title: folder.title,
883
- ...folder.description ? { description: folder.description } : {},
884
871
  ...folder.icon ? { icon: folder.icon } : {},
885
872
  ...folder.root ? { root: true } : {},
886
873
  defaultOpen: folder.defaultOpen,
887
874
  collapsible: folder.collapsible,
888
- ...folder.index && !folder.listIndex ? { pagesIndex: "index" } : {},
889
- pages: folderPages(folder)
875
+ ...folder.index ? { pagesIndex: "index" } : {},
876
+ pages: folder.children.map(helpMetaEntry)
890
877
  };
891
878
  files.push({ type: "meta", path: join(dir, ext("meta", "json")), data });
892
879
  addChildren(folder.children, dir);
package/dist/index.d.cts CHANGED
@@ -208,8 +208,6 @@ 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;
213
211
  icon: string | null;
214
212
  /** A tab (top level only; Fumadocs `root`). */
215
213
  root: boolean;
@@ -222,11 +220,6 @@ interface HelpPublicTreeFolder {
222
220
  collapsible: boolean;
223
221
  /** The folder's own page, shown at the folder's path; null without one. */
224
222
  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;
230
223
  children: HelpPublicTreeNode[];
231
224
  }
232
225
  /** A labelled divider between entries (Fumadocs `---Title---`). */
package/dist/index.d.ts CHANGED
@@ -208,8 +208,6 @@ 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;
213
211
  icon: string | null;
214
212
  /** A tab (top level only; Fumadocs `root`). */
215
213
  root: boolean;
@@ -222,11 +220,6 @@ interface HelpPublicTreeFolder {
222
220
  collapsible: boolean;
223
221
  /** The folder's own page, shown at the folder's path; null without one. */
224
222
  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;
230
223
  children: HelpPublicTreeNode[];
231
224
  }
232
225
  /** 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-85cf20e",
3
+ "version": "1.0.0-a316e3a",
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",