@solutiondesk/help 1.0.0-1edea54 → 1.0.0-a1101b8
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 +55 -7
- package/dist/fumadocs.d.ts +164 -17
- package/dist/fumadocs.js +329 -26
- package/dist/index.d.cts +41 -2
- package/dist/index.d.ts +41 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,9 +29,9 @@ 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`) 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, `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
|
-
| `page(path, { lang, redirect })` | `GET …/page?path=` | One page: `markdown`, `html`, `headings`, `blocks`, `breadcrumbs`, `previous`/`next`. The home page has the path `""`. |
|
|
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. |
|
|
36
36
|
| `search(query, { lang, limit })` | `GET …/search?q=` | Up to 20 hits: title hits before heading hits before text hits, with an `excerpt` and the best `heading`. |
|
|
37
37
|
| `preview(token, { lang })` | `GET …/preview?token=` | A page in any state (drafts too) for your preview route. |
|
|
@@ -112,7 +112,7 @@ The navigation becomes the files Fumadocs knows from a content folder:
|
|
|
112
112
|
| separator | `"---[Icon]Title---"` (a separator without a title is `"---"`) |
|
|
113
113
|
| link | `"[Icon][Title](url)"`, `"external:[Icon][Title](url)"` |
|
|
114
114
|
|
|
115
|
-
Every page's `data` holds `title`, `description`, `icon`, `path`, `lang`, `load()` (the page with its text, or `null` when the page was taken offline after the source was built — answer with a 404) and `structuredData()` (for Fumadocs search; empty for such a page, so it never breaks the search index).
|
|
115
|
+
Every page's `data` holds `title`, `description`, `icon`, `path`, `lang`, `blocks` (the site's own building blocks, for `HelpBody`), `load()` (the page with its text, or `null` when the page was taken offline after the source was built — answer with a 404) and `structuredData()` (for Fumadocs search; empty for such a page, so it never breaks the search index).
|
|
116
116
|
|
|
117
117
|
**Lazy or eager.** The page list has no texts, so by default (`pages: "lazy"`) building the source costs one request per language, and `page.data.load()` fetches a page's text when it is rendered. Cache that with your framework (the tags above). With `pages: "eager"` every page is fetched while building (`concurrency`, default 6) and kept in `page.data.page`: slower to build, nothing fetched later, best for a fully static export.
|
|
118
118
|
|
|
@@ -153,7 +153,7 @@ export default async function Page({ params }: { params: Promise<{ slug?: string
|
|
|
153
153
|
<DocsTitle>{page.title}</DocsTitle>
|
|
154
154
|
<DocsDescription>{page.description}</DocsDescription>
|
|
155
155
|
<DocsBody>
|
|
156
|
-
<HelpBody
|
|
156
|
+
<HelpBody page={page} blocks={entry.data.blocks} icon={icon} />
|
|
157
157
|
</DocsBody>
|
|
158
158
|
</DocsPage>
|
|
159
159
|
);
|
|
@@ -164,20 +164,67 @@ export async function generateStaticParams() {
|
|
|
164
164
|
}
|
|
165
165
|
```
|
|
166
166
|
|
|
167
|
-
`HelpBody` renders the Markdown with Fumadocs UI: `:::callout` → `Callout`, `::::steps`/`:::step` → `Steps`/`Step`, `::::tabs`/`:::tab` → `Tabs`/`Tab`, `::::cards`/`:::card` → `Cards`/`Card`, a code fence with `title="…"` → `CodeBlock`. Pass `headings` so the anchors match `getHelpToc`. Replace any part with `components`, for example syntax highlighting:
|
|
167
|
+
`page` gives `HelpBody` the page's `markdown`, `headings` and `locale`; you can also pass `markdown`, `headings` and `lang` yourself. `HelpBody` renders the Markdown with Fumadocs UI: `:::callout` → `Callout`, `::::steps`/`:::step` → `Steps`/`Step`, `::::tabs`/`:::tab` → `Tabs`/`Tab`, `::::cards`/`:::card` → `Cards`/`Card`, a code fence with `title="…"` → `CodeBlock`. Pass `headings` (or `page`) so the anchors match `getHelpToc`. Replace any part with `components`, for example syntax highlighting:
|
|
168
168
|
|
|
169
169
|
```tsx
|
|
170
170
|
import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock";
|
|
171
171
|
|
|
172
172
|
<HelpBody
|
|
173
|
-
|
|
174
|
-
headings={page.headings}
|
|
173
|
+
page={page}
|
|
175
174
|
components={{ Code: ({ code, lang, title }) => <DynamicCodeBlock lang={lang ?? "text"} code={code} codeblock={{ title }} /> }}
|
|
176
175
|
/>;
|
|
177
176
|
```
|
|
178
177
|
|
|
179
178
|
The same safety rules as the API's `html` apply: raw HTML is shown as text, links only go to `http(s)`, `mailto:`, `/paths` and `#anchors`, images only come from `http(s)` or `/paths` — never to another host through `//`, `/\`, `\\` or control characters hidden in the address. A directive SolutionDesk doesn't know stays the text it was written as.
|
|
180
179
|
|
|
180
|
+
### Your own building blocks
|
|
181
|
+
|
|
182
|
+
In SolutionDesk, a doc site can define its own building blocks: what a block is called, whether it sits in a sentence, on its own line or around content, and which fields it has. How it looks is up to your website. Map each block's name to one of your components:
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
import { type HelpBlockProps, HelpBody } from "@solutiondesk/help/fumadocs";
|
|
186
|
+
import { icons } from "lucide-react";
|
|
187
|
+
|
|
188
|
+
// :app-button{icon="Palette" label="Your team"} — in a sentence
|
|
189
|
+
function AppButton({ icon, label }: HelpBlockProps<{ icon: string; label: string }>) {
|
|
190
|
+
const Icon = icon ? icons[icon as keyof typeof icons] : undefined;
|
|
191
|
+
return <kbd className="app-button">{Icon ? <Icon /> : null}{label}</kbd>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ::paid-addon{name="Events"} — on its own line
|
|
195
|
+
function PaidAddon({ name, lang }: HelpBlockProps<{ name: string }>) {
|
|
196
|
+
return <aside className="paid-addon">{lang === "de" ? "Kostenpflichtig" : "Paid add-on"}: {name}</aside>;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// :::download-buttons{title="UXP installer" versionsHref="#versions"} … ::: — around content
|
|
200
|
+
function DownloadButtons({ title, versionsHref, children }: HelpBlockProps<{ title: string; versionsHref: string }>) {
|
|
201
|
+
return (
|
|
202
|
+
<section className="downloads">
|
|
203
|
+
<h4>{title}</h4>
|
|
204
|
+
{children}
|
|
205
|
+
{versionsHref ? <a href={versionsHref}>Older versions</a> : null}
|
|
206
|
+
</section>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
<HelpBody
|
|
211
|
+
page={page}
|
|
212
|
+
blocks={entry.data.blocks} // or `site.blocks` from `help.site()`
|
|
213
|
+
components={{ "app-button": AppButton, "paid-addon": PaidAddon, "download-buttons": DownloadButtons }}
|
|
214
|
+
/>;
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
A component gets:
|
|
218
|
+
|
|
219
|
+
- the block's **fields**, already checked the way SolutionDesk checks them: text fields as strings, on/off fields as `true`/`false`. A value that isn't valid (an unsafe address such as `javascript:`, a picture or video address that isn't allowed, an icon name that isn't one, an option that isn't offered) is left out, so every field may be missing — `HelpBlockProps<Fields>` types them as optional.
|
|
220
|
+
- `lang`: the page's language (`page.locale`, or the `lang` you pass) — no need to store it in the block.
|
|
221
|
+
- `block`: the block's name.
|
|
222
|
+
- `children`: for a block in a sentence or on its own line, its text in `[brackets]` (`:app-button[Share]{icon="Share2"}`); for a block around content, that content, rendered like the rest of the page (built-in and own blocks inside included).
|
|
223
|
+
|
|
224
|
+
`block`, `lang` and `children` always mean the above; SolutionDesk doesn't allow fields with these names. A block the site defines but you have no component for shows its **text version** (set in SolutionDesk, e.g. `Paid add-on: Events`) in a neutral `<span class="sd-block" data-block="…" data-props="…">` (in a sentence) or `<div class="sd-block">` (on its own line; around content with `<p class="sd-block-text">` and `<div class="sd-block-body">`) — the same as the API's `html`. A name the site doesn't define, or a block written in the wrong form, stays the text it was written as. Without `blocks`, every one of them stays text. Search data (`structuredData()`) and heading anchors use the text version as well, like the API's search.
|
|
225
|
+
|
|
226
|
+
`helpBlockValues(definition, attributes)` and `helpBlockText(definition, values, label?)` expose the same rules if you render blocks yourself.
|
|
227
|
+
|
|
181
228
|
### Refresh when something changes
|
|
182
229
|
|
|
183
230
|
In SolutionDesk set the site's webhook to a route on your website and copy the signing secret. The handler checks the `X-SolutionDesk-Signature` header and answers `401` when it doesn't match (or is older than 5 minutes) and `400` for a signed body that isn't a help webhook:
|
|
@@ -243,6 +290,7 @@ document.querySelector("article")!.innerHTML = page.html;
|
|
|
243
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) |
|
|
244
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">` |
|
|
245
292
|
| titled code | `<figure class="sd-code"><figcaption>client.ts</figcaption><pre><code class="language-ts">` |
|
|
293
|
+
| the site's own block | `<span class="sd-block" data-block="app-button" data-props="{…}">` in a sentence, `<div class="sd-block">` on its own line or around content (`<p class="sd-block-text">`, `<div class="sd-block-body">`); the text is the block's text version, `data-props` its checked values as JSON — hydrate your own component from it |
|
|
246
294
|
|
|
247
295
|
Build the table of contents from `page.headings` (`#id`), the breadcrumb from `page.breadcrumbs` and the footer links from `page.previous`/`page.next`.
|
|
248
296
|
|
package/dist/fumadocs.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { JSXElementConstructor, ReactNode, ComponentType } from 'react';
|
|
2
2
|
import { MetaData, PageData, StaticSource, VirtualFile, DynamicSource } from 'fumadocs-core/source';
|
|
3
3
|
import { TOCItemType } from 'fumadocs-core/toc';
|
|
4
4
|
|
|
@@ -26,6 +26,40 @@ interface HelpPublicSite {
|
|
|
26
26
|
languages: string[];
|
|
27
27
|
/** The docs website's base address (`https://help.acme.com/docs`), if set. */
|
|
28
28
|
websiteUrl: string | null;
|
|
29
|
+
/** The site's own building blocks pages may use (empty when it defines none). */
|
|
30
|
+
blocks: HelpPublicBlockDefinition[];
|
|
31
|
+
}
|
|
32
|
+
/** What a field of a site's own block holds (see {@link HelpPublicBlockField}). */
|
|
33
|
+
type HelpPublicBlockFieldType = "text" | "longText" | "icon" | "url" | "choice" | "image" | "video" | "boolean";
|
|
34
|
+
/**
|
|
35
|
+
* One field of a site's own block: an attribute of its directive
|
|
36
|
+
* (`key="value"`). `boolean` fields are on when written bare (`key`) or as
|
|
37
|
+
* `key="true"`; every other value is a string. `options` lists a `choice`
|
|
38
|
+
* field's values (null for other types).
|
|
39
|
+
*/
|
|
40
|
+
interface HelpPublicBlockField {
|
|
41
|
+
key: string;
|
|
42
|
+
label: string;
|
|
43
|
+
type: HelpPublicBlockFieldType;
|
|
44
|
+
required: boolean;
|
|
45
|
+
options: string[] | null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A building block the site defines itself — WHAT it is; how it looks is the
|
|
49
|
+
* website's own component. Written in Markdown as `:name[label]{…}` in running
|
|
50
|
+
* text (`inline`), `::name[label]{…}` alone on a line (`block`) or
|
|
51
|
+
* `:::name{…}` … `:::` around Markdown content (`container`). `text` is the
|
|
52
|
+
* template of its text version (`{key}` = a field's value, a `boolean` field's
|
|
53
|
+
* label when on; `{{`/`}}` = a brace) — what to show without a component.
|
|
54
|
+
*/
|
|
55
|
+
interface HelpPublicBlockDefinition {
|
|
56
|
+
/** The directive name (`app-button`). */
|
|
57
|
+
name: string;
|
|
58
|
+
/** Display name. */
|
|
59
|
+
label: string;
|
|
60
|
+
kind: "inline" | "block" | "container";
|
|
61
|
+
fields: HelpPublicBlockField[];
|
|
62
|
+
text: string;
|
|
29
63
|
}
|
|
30
64
|
/** A page in the navigation. */
|
|
31
65
|
interface HelpPublicTreePage {
|
|
@@ -135,12 +169,17 @@ interface HelpPublicPageLink {
|
|
|
135
169
|
}
|
|
136
170
|
/** One page with its text. */
|
|
137
171
|
interface HelpPublicPage extends HelpPublicPageSummary {
|
|
138
|
-
/**
|
|
172
|
+
/**
|
|
173
|
+
* The page's Markdown with directives (`:::callout`, `::::steps`, …, and the
|
|
174
|
+
* site's own blocks as written — see `site.blocks`).
|
|
175
|
+
*/
|
|
139
176
|
markdown: string;
|
|
140
177
|
/** Safe server-rendered HTML of `markdown` (stable `sd-*` classes for the directives). */
|
|
141
178
|
html: string;
|
|
142
179
|
headings: HelpPublicHeading[];
|
|
143
180
|
blocks: HelpPublicBlock[];
|
|
181
|
+
/** The site's own building blocks the page uses, by name (in the site's order). */
|
|
182
|
+
customBlocks: string[];
|
|
144
183
|
breadcrumbs: HelpPublicBreadcrumb[];
|
|
145
184
|
previous: HelpPublicPageLink | null;
|
|
146
185
|
next: HelpPublicPageLink | null;
|
|
@@ -215,8 +254,8 @@ interface HelpWebhookBody {
|
|
|
215
254
|
* Parsing the help center's Markdown the way the API renders it
|
|
216
255
|
* (`packages/db/src/help-markdown.ts`): GFM plus container directives in the
|
|
217
256
|
* generic syntax of `remark-directive`. Only the directive names below mean
|
|
218
|
-
* something
|
|
219
|
-
*
|
|
257
|
+
* something, plus the site's own blocks (`blocks.ts`); every other directive
|
|
258
|
+
* is printed back as the text it was written as.
|
|
220
259
|
*/
|
|
221
260
|
/** The directive names the help center knows. */
|
|
222
261
|
declare const HELP_BLOCKS: readonly ["callout", "steps", "step", "tabs", "tab", "cards", "card"];
|
|
@@ -236,6 +275,38 @@ type HelpCalloutType = (typeof HELP_CALLOUT_TYPES)[number];
|
|
|
236
275
|
*/
|
|
237
276
|
declare function safeHelpHref(raw: string | null | undefined): string | null;
|
|
238
277
|
|
|
278
|
+
/**
|
|
279
|
+
* A doc site's own building blocks (`site.blocks`) in the page Markdown — the
|
|
280
|
+
* same rules as the API (`packages/db/src/help-blocks.ts`, `help-markdown.ts`,
|
|
281
|
+
* contract in `docs/help/06-eigene-bausteine.md`), copied so the package has no
|
|
282
|
+
* dependency on the API's code; `test/blocks.test.tsx` holds both in step.
|
|
283
|
+
*
|
|
284
|
+
* The Markdown parser (micromark's directive extension) finds the directives;
|
|
285
|
+
* a use only counts when its name is defined, it is written in its kind's form
|
|
286
|
+
* (`:name` inline, `::name` alone on a line, `:::name` around content) and its
|
|
287
|
+
* source re-reads under the API's stricter syntax (label and attributes on one
|
|
288
|
+
* line, the basic character references) — otherwise it stays literal text.
|
|
289
|
+
*/
|
|
290
|
+
/** A field's checked value: `boolean` fields are true/false, every other type a string. */
|
|
291
|
+
type HelpBlockValue = string | boolean;
|
|
292
|
+
/** A block's checked field values, in the definition's field order. */
|
|
293
|
+
type HelpBlockValues = Record<string, HelpBlockValue>;
|
|
294
|
+
/**
|
|
295
|
+
* A block's attributes as checked field values, by the API's rules: only
|
|
296
|
+
* defined fields; an absent `boolean` is `false`; an empty value counts as
|
|
297
|
+
* absent; invalid values (a `javascript:` link, a malformed icon, a choice
|
|
298
|
+
* not offered) and attributes without a field are left out.
|
|
299
|
+
*/
|
|
300
|
+
declare function helpBlockValues(block: Pick<HelpPublicBlockDefinition, "fields">, attributes: Readonly<Record<string, string>>): HelpBlockValues;
|
|
301
|
+
/**
|
|
302
|
+
* A block's text version: its template with every `{key}` replaced by the
|
|
303
|
+
* field's value (a `boolean` by its label when on, else nothing; a missing
|
|
304
|
+
* value by nothing), `{{`/`}}` as braces, then `label` after a space;
|
|
305
|
+
* whitespace collapsed. What `HelpBody` shows without a component, and what
|
|
306
|
+
* search and the outline read.
|
|
307
|
+
*/
|
|
308
|
+
declare function helpBlockText(block: Pick<HelpPublicBlockDefinition, "fields" | "text">, values: Readonly<HelpBlockValues>, label?: string): string;
|
|
309
|
+
|
|
239
310
|
interface HelpCalloutProps {
|
|
240
311
|
/** `info`, `warn`, `error`, `success` or `idea` (an unknown type arrives as `info`). */
|
|
241
312
|
type: HelpCalloutType;
|
|
@@ -294,6 +365,40 @@ interface HelpImageProps {
|
|
|
294
365
|
alt: string;
|
|
295
366
|
title?: string;
|
|
296
367
|
}
|
|
368
|
+
/** A field value of a site's own block, as its component receives it. */
|
|
369
|
+
type HelpBlockFieldValue = string | boolean | undefined;
|
|
370
|
+
/**
|
|
371
|
+
* The props of a component for one of the site's own blocks
|
|
372
|
+
* (`docs/help/06`). `Fields` names the block's fields: a `boolean` field is
|
|
373
|
+
* always there (`true`/`false`); any other field is a string, or absent when
|
|
374
|
+
* the page leaves it out or its value isn't valid (a `javascript:` link, an
|
|
375
|
+
* icon name that isn't one, a choice not offered) — even a required one, so
|
|
376
|
+
* every field is optional here.
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* type AppButtonProps = HelpBlockProps<{ icon: string; label: string }>;
|
|
380
|
+
* function AppButton({ icon, label, children, lang }: AppButtonProps) { … }
|
|
381
|
+
*/
|
|
382
|
+
type HelpBlockProps<Fields extends {
|
|
383
|
+
[K in keyof Fields]: HelpBlockFieldValue;
|
|
384
|
+
} = Record<never, never>> = {
|
|
385
|
+
[K in Exclude<keyof Fields, "block" | "lang" | "children">]?: Fields[K];
|
|
386
|
+
} & {
|
|
387
|
+
/** The block's name (`app-button`). */
|
|
388
|
+
block: string;
|
|
389
|
+
/** The page's language (`page.locale`, or `HelpBody`'s `lang`). */
|
|
390
|
+
lang: string;
|
|
391
|
+
/**
|
|
392
|
+
* `inline`/`block`: the `[label]` (rendered Markdown), if written;
|
|
393
|
+
* `container`: the rendered content — built-in and own blocks included.
|
|
394
|
+
*/
|
|
395
|
+
children?: ReactNode;
|
|
396
|
+
};
|
|
397
|
+
/**
|
|
398
|
+
* A component for a site's own block. Typed loosely so any component fits;
|
|
399
|
+
* type yours with {@link HelpBlockProps}.
|
|
400
|
+
*/
|
|
401
|
+
type HelpBlockComponent = JSXElementConstructor<never>;
|
|
297
402
|
/** Everything `HelpBody` renders through a component — replace any of them. */
|
|
298
403
|
interface HelpBodyComponents {
|
|
299
404
|
Callout: ComponentType<HelpCalloutProps>;
|
|
@@ -311,17 +416,47 @@ interface HelpBodyComponents {
|
|
|
311
416
|
}
|
|
312
417
|
/** The Fumadocs UI defaults. */
|
|
313
418
|
declare const helpBodyComponents: HelpBodyComponents;
|
|
419
|
+
/**
|
|
420
|
+
* `HelpBody`'s components: any of the built-in ones by their (capitalised)
|
|
421
|
+
* name, and a component per site block by the block's name (`app-button`).
|
|
422
|
+
*/
|
|
423
|
+
interface HelpBodyComponentMap extends Partial<HelpBodyComponents> {
|
|
424
|
+
[block: string]: HelpBlockComponent | undefined;
|
|
425
|
+
}
|
|
314
426
|
interface HelpBodyProps {
|
|
315
|
-
/**
|
|
316
|
-
|
|
427
|
+
/**
|
|
428
|
+
* The page from the API (`page.data.load()`, `help.page()`, `help.preview()`):
|
|
429
|
+
* its `markdown`, `headings` and `locale` (passed to block components as
|
|
430
|
+
* `lang`). Or pass `markdown` (and `headings`, `lang`) yourself.
|
|
431
|
+
*/
|
|
432
|
+
page?: {
|
|
433
|
+
markdown: string;
|
|
434
|
+
headings?: readonly HelpPublicHeading[];
|
|
435
|
+
locale?: string;
|
|
436
|
+
};
|
|
437
|
+
/** The page's Markdown with directives (`page.markdown` from the API); wins over `page`. */
|
|
438
|
+
markdown?: string;
|
|
317
439
|
/**
|
|
318
440
|
* The page's `headings` from the API: their ids become the headings' anchors,
|
|
319
441
|
* so they always match `getHelpToc(page.headings)`. Without them the ids are
|
|
320
442
|
* computed the API's way.
|
|
321
443
|
*/
|
|
322
444
|
headings?: readonly HelpPublicHeading[];
|
|
323
|
-
/**
|
|
324
|
-
|
|
445
|
+
/** The page's language for block components (default `page.locale`, else `""`). */
|
|
446
|
+
lang?: string;
|
|
447
|
+
/**
|
|
448
|
+
* The site's own blocks (`page.data.blocks` of a help source, or
|
|
449
|
+
* `site.blocks` from `help.site()`). Without them every such directive stays
|
|
450
|
+
* the text it was written as.
|
|
451
|
+
*/
|
|
452
|
+
blocks?: readonly HelpPublicBlockDefinition[];
|
|
453
|
+
/**
|
|
454
|
+
* Replace any of the default Fumadocs UI components (`Callout`, `Code`, …),
|
|
455
|
+
* and render the site's own blocks with your components, keyed by the
|
|
456
|
+
* block's name: `{ "app-button": AppButton }`. A defined block without a
|
|
457
|
+
* component shows its text version.
|
|
458
|
+
*/
|
|
459
|
+
components?: HelpBodyComponentMap;
|
|
325
460
|
/**
|
|
326
461
|
* Turn a card's Lucide icon name (`Rocket`) into an element, e.g. with
|
|
327
462
|
* `lucide-react`: `(name) => { const Icon = icons[name as keyof typeof icons]; return Icon ? <Icon /> : null; }`.
|
|
@@ -332,15 +467,20 @@ interface HelpBodyProps {
|
|
|
332
467
|
/**
|
|
333
468
|
* A help page's Markdown with its blocks (callout, steps, tabs, cards, titled
|
|
334
469
|
* code fences) as Fumadocs UI components — a React Server Component, also fine
|
|
335
|
-
* on the client. The
|
|
336
|
-
*
|
|
337
|
-
*
|
|
470
|
+
* on the client. The site's own blocks (`blocks`) render with your
|
|
471
|
+
* `components`, or as their text version. The API's safety rules apply: raw
|
|
472
|
+
* HTML is shown as text, links only to http(s), mailto, `/paths` and
|
|
473
|
+
* `#anchors`, images only from http(s) or `/paths`; unknown directives stay
|
|
474
|
+
* the text they were written as.
|
|
338
475
|
*
|
|
339
476
|
* @example
|
|
340
|
-
* const
|
|
341
|
-
*
|
|
477
|
+
* const entry = source.getPage(slugs);
|
|
478
|
+
* const page = await entry.data.load();
|
|
479
|
+
* <DocsBody>
|
|
480
|
+
* <HelpBody page={page} blocks={entry.data.blocks} components={{ "app-button": AppButton }} />
|
|
481
|
+
* </DocsBody>
|
|
342
482
|
*/
|
|
343
|
-
declare function HelpBody(
|
|
483
|
+
declare function HelpBody(props: HelpBodyProps): ReactNode;
|
|
344
484
|
|
|
345
485
|
/** Per-call options. */
|
|
346
486
|
interface RequestOptions {
|
|
@@ -483,9 +623,11 @@ interface HelpStructuredData {
|
|
|
483
623
|
/**
|
|
484
624
|
* A page's search data for Fumadocs (`createFromSource`): every heading with
|
|
485
625
|
* its anchor, and every paragraph, quote and table cell under the heading it
|
|
486
|
-
* belongs to — code is left out, as in Fumadocs' own `remark-structure`.
|
|
626
|
+
* belongs to — code is left out, as in Fumadocs' own `remark-structure`. The
|
|
627
|
+
* site's own `blocks` count as their text version (like the API's search): a
|
|
628
|
+
* container's text version is an entry of its own, followed by its content.
|
|
487
629
|
*/
|
|
488
|
-
declare function helpStructuredData(markdown: string, headings?: readonly HelpPublicHeading[]): HelpStructuredData;
|
|
630
|
+
declare function helpStructuredData(markdown: string, headings?: readonly HelpPublicHeading[], blocks?: readonly HelpPublicBlockDefinition[]): HelpStructuredData;
|
|
489
631
|
|
|
490
632
|
/** What every page of a help source carries in `page.data`. */
|
|
491
633
|
interface HelpPageData extends PageData {
|
|
@@ -497,6 +639,11 @@ interface HelpPageData extends PageData {
|
|
|
497
639
|
path: string;
|
|
498
640
|
/** The language this file was built for (the `lang` asked for; the API falls back per page). */
|
|
499
641
|
lang: string;
|
|
642
|
+
/**
|
|
643
|
+
* The site's own building blocks (`site.blocks`) — pass them to `HelpBody`
|
|
644
|
+
* (`blocks={page.data.blocks}`) so pages render them with your components.
|
|
645
|
+
*/
|
|
646
|
+
blocks: HelpPublicBlockDefinition[];
|
|
500
647
|
/**
|
|
501
648
|
* The page with its text (`markdown`, `html`, `headings`, `breadcrumbs`,
|
|
502
649
|
* `previous`/`next`). Fetched when called — or right away with
|
|
@@ -619,4 +766,4 @@ declare function getHelpToc(headings: readonly HelpPublicHeading[] | {
|
|
|
619
766
|
headings: readonly HelpPublicHeading[];
|
|
620
767
|
}, options?: HelpTocOptions): TOCItemType[];
|
|
621
768
|
|
|
622
|
-
export { HELP_BLOCKS, HELP_CALLOUT_TYPES, type HelpBlockName, HelpBody, type HelpBodyComponents, type HelpBodyProps, type HelpCalloutProps, type HelpCalloutType, type HelpCardProps, type HelpCardsProps, type HelpCodeProps, type HelpFilesOptions, type HelpHeadingProps, type HelpImageProps, type HelpLinkProps, type HelpMetaData, type HelpPageData, type HelpRevalidateOptions, type HelpSource, type HelpSourceConfig, type HelpSourceOptions, type HelpStepProps, type HelpStepsProps, type HelpStructuredData, type HelpTabProps, type HelpTabsProps, type HelpTocOptions, type HelpVirtualFile, type HelpWebhookRequest, createHelpRevalidateHandler, createHelpSource, getHelpToc, helpBodyComponents, helpDynamicSource, helpMetaEntry, helpStructuredData, helpTreePaths, helpTreeToFiles, safeHelpHref };
|
|
769
|
+
export { HELP_BLOCKS, HELP_CALLOUT_TYPES, type HelpBlockComponent, type HelpBlockFieldValue, type HelpBlockName, type HelpBlockProps, type HelpBlockValue, type HelpBlockValues, HelpBody, type HelpBodyComponentMap, type HelpBodyComponents, type HelpBodyProps, type HelpCalloutProps, type HelpCalloutType, type HelpCardProps, type HelpCardsProps, type HelpCodeProps, type HelpFilesOptions, type HelpHeadingProps, type HelpImageProps, type HelpLinkProps, type HelpMetaData, type HelpPageData, type HelpRevalidateOptions, type HelpSource, type HelpSourceConfig, type HelpSourceOptions, type HelpStepProps, type HelpStepsProps, type HelpStructuredData, type HelpTabProps, type HelpTabsProps, type HelpTocOptions, type HelpVirtualFile, type HelpWebhookRequest, createHelpRevalidateHandler, createHelpSource, getHelpToc, helpBlockText, helpBlockValues, helpBodyComponents, helpDynamicSource, helpMetaEntry, helpStructuredData, helpTreePaths, helpTreeToFiles, safeHelpHref };
|
package/dist/fumadocs.js
CHANGED
|
@@ -1,26 +1,57 @@
|
|
|
1
|
+
import { directiveFromMarkdown } from 'mdast-util-directive';
|
|
2
|
+
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
3
|
+
import { gfmFromMarkdown } from 'mdast-util-gfm';
|
|
4
|
+
import { directive } from 'micromark-extension-directive';
|
|
5
|
+
import { gfm } from 'micromark-extension-gfm';
|
|
1
6
|
import { Callout } from 'fumadocs-ui/components/callout';
|
|
2
7
|
import { Card, Cards } from 'fumadocs-ui/components/card';
|
|
3
8
|
import { CodeBlock, Pre } from 'fumadocs-ui/components/codeblock';
|
|
4
9
|
import { Heading } from 'fumadocs-ui/components/heading';
|
|
5
10
|
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
|
6
11
|
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
|
|
7
|
-
import { Fragment as Fragment$1 } from 'react';
|
|
8
|
-
import { directiveFromMarkdown } from 'mdast-util-directive';
|
|
9
|
-
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
10
|
-
import { gfmFromMarkdown } from 'mdast-util-gfm';
|
|
11
|
-
import { directive } from 'micromark-extension-directive';
|
|
12
|
-
import { gfm } from 'micromark-extension-gfm';
|
|
12
|
+
import { Fragment as Fragment$1, createElement } from 'react';
|
|
13
13
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
14
14
|
|
|
15
|
-
// src/fumadocs/
|
|
15
|
+
// src/fumadocs/markdown.ts
|
|
16
16
|
var HELP_BLOCKS = ["callout", "steps", "step", "tabs", "tab", "cards", "card"];
|
|
17
17
|
var HELP_CALLOUT_TYPES = ["info", "warn", "error", "success", "idea"];
|
|
18
18
|
var isHelpBlock = (name) => HELP_BLOCKS.includes(name);
|
|
19
|
+
var CODE_FENCE = /^[ \t]*(`{3,}|~{3,})(.*)$/;
|
|
20
|
+
var CLOSING_FENCE = /^([ \t]*):(:{2,}[ \t]*)$/;
|
|
21
|
+
var MASK = "\uE000";
|
|
22
|
+
function maskCodeFences(markdown) {
|
|
23
|
+
if (markdown.includes(MASK)) return markdown;
|
|
24
|
+
let open = null;
|
|
25
|
+
return markdown.split("\n").map((line) => {
|
|
26
|
+
const fence = CODE_FENCE.exec(line);
|
|
27
|
+
if (open) {
|
|
28
|
+
if (fence?.[1]?.[0] === open.char && fence[1].length >= open.length && !fence[2]?.trim()) {
|
|
29
|
+
open = null;
|
|
30
|
+
return line;
|
|
31
|
+
}
|
|
32
|
+
return line.replace(CLOSING_FENCE, `$1${MASK}$2`);
|
|
33
|
+
}
|
|
34
|
+
if (fence?.[1] && !(fence[1][0] === "`" && fence[2]?.includes("`"))) {
|
|
35
|
+
open = { char: fence[1][0] ?? "`", length: fence[1].length };
|
|
36
|
+
}
|
|
37
|
+
return line;
|
|
38
|
+
}).join("\n");
|
|
39
|
+
}
|
|
40
|
+
function unmask(node) {
|
|
41
|
+
if ("value" in node && typeof node.value === "string" && node.value.includes(MASK)) {
|
|
42
|
+
node.value = node.value.replaceAll(MASK, ":");
|
|
43
|
+
}
|
|
44
|
+
if ("children" in node) for (const child of node.children) unmask(child);
|
|
45
|
+
}
|
|
19
46
|
function parseHelpMarkdown(markdown) {
|
|
20
|
-
|
|
47
|
+
const source = markdown ?? "";
|
|
48
|
+
const masked = maskCodeFences(source);
|
|
49
|
+
const root = fromMarkdown(masked, {
|
|
21
50
|
extensions: [gfm(), directive()],
|
|
22
51
|
mdastExtensions: [gfmFromMarkdown(), directiveFromMarkdown()]
|
|
23
52
|
});
|
|
53
|
+
if (masked !== source) unmask(root);
|
|
54
|
+
return root;
|
|
24
55
|
}
|
|
25
56
|
var CONTROL = /[\u0000-\u001f\u007f]/;
|
|
26
57
|
var PROTOCOL_RELATIVE = /^[\\/]{2}/;
|
|
@@ -40,10 +71,14 @@ function safeHelpImageSrc(raw) {
|
|
|
40
71
|
if (/^\/[^\s]*$/.test(src)) return src;
|
|
41
72
|
return null;
|
|
42
73
|
}
|
|
43
|
-
function plainText(node) {
|
|
74
|
+
function plainText(node, override) {
|
|
75
|
+
const own = override?.(node);
|
|
76
|
+
if (own !== void 0) return own;
|
|
44
77
|
if ("value" in node && typeof node.value === "string") return node.value;
|
|
45
78
|
if (node.type === "image") return node.alt ?? "";
|
|
46
|
-
if ("children" in node)
|
|
79
|
+
if ("children" in node) {
|
|
80
|
+
return node.children.map((child) => plainText(child, override)).join("");
|
|
81
|
+
}
|
|
47
82
|
return "";
|
|
48
83
|
}
|
|
49
84
|
function sourceOf(node, markdown) {
|
|
@@ -104,7 +139,7 @@ function collectHeadings(root) {
|
|
|
104
139
|
walk(root);
|
|
105
140
|
return out;
|
|
106
141
|
}
|
|
107
|
-
function headingIds(root, known) {
|
|
142
|
+
function headingIds(root, known, override) {
|
|
108
143
|
const headings = collectHeadings(root);
|
|
109
144
|
const ids = /* @__PURE__ */ new Map();
|
|
110
145
|
if (known && known.length === headings.length) {
|
|
@@ -115,7 +150,7 @@ function headingIds(root, known) {
|
|
|
115
150
|
}
|
|
116
151
|
const used = /* @__PURE__ */ new Map();
|
|
117
152
|
for (const heading of headings) {
|
|
118
|
-
const text = plainText(heading).replace(/\s+/g, " ").trim();
|
|
153
|
+
const text = plainText(heading, override).replace(/\s+/g, " ").trim();
|
|
119
154
|
const base = headingSlug(text) || "section";
|
|
120
155
|
const seen = used.get(base) ?? 0;
|
|
121
156
|
used.set(base, seen + 1);
|
|
@@ -123,6 +158,220 @@ function headingIds(root, known) {
|
|
|
123
158
|
}
|
|
124
159
|
return ids;
|
|
125
160
|
}
|
|
161
|
+
|
|
162
|
+
// src/fumadocs/blocks.ts
|
|
163
|
+
var TEXT_VALUE_MAX = 300;
|
|
164
|
+
var LONG_VALUE_MAX = 2e3;
|
|
165
|
+
var ICON = /^[A-Za-z][A-Za-z0-9]{0,47}$/;
|
|
166
|
+
var NAMED_ENTITIES = {
|
|
167
|
+
amp: "&",
|
|
168
|
+
lt: "<",
|
|
169
|
+
gt: ">",
|
|
170
|
+
quot: '"',
|
|
171
|
+
apos: "'",
|
|
172
|
+
nbsp: "\xA0"
|
|
173
|
+
};
|
|
174
|
+
function decodeEntities(value) {
|
|
175
|
+
return value.replace(/&(#x[0-9a-f]{1,6}|#[0-9]{1,7}|[a-z]+);/gi, (match, ref) => {
|
|
176
|
+
if (ref[0] === "#") {
|
|
177
|
+
const code = ref[1] === "x" || ref[1] === "X" ? parseInt(ref.slice(2), 16) : Number(ref.slice(1));
|
|
178
|
+
return Number.isFinite(code) && code > 0 && code <= 1114111 ? String.fromCodePoint(code) : match;
|
|
179
|
+
}
|
|
180
|
+
return NAMED_ENTITIES[ref.toLowerCase()] ?? match;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
var ATTRIBUTE = /^(?:#([^\s"'<>=`}#.]+)|\.([^\s"'<>=`}#.]+)|([A-Za-z_:][A-Za-z0-9_.:-]*)(?:[ \t]*=[ \t]*(?:"([^"\n]*)"|'([^'\n]*)'|([^\s"'<>=`}]+)))?)/;
|
|
184
|
+
function parseAttributes(src) {
|
|
185
|
+
const attributes = {};
|
|
186
|
+
let pos = 1;
|
|
187
|
+
for (; ; ) {
|
|
188
|
+
while (src[pos] === " " || src[pos] === " ") pos++;
|
|
189
|
+
if (src[pos] === "}") return { attributes, length: pos + 1 };
|
|
190
|
+
const match = ATTRIBUTE.exec(src.slice(pos));
|
|
191
|
+
if (!match) return null;
|
|
192
|
+
const [whole, id, cls, key, dq, sq, bare] = match;
|
|
193
|
+
if (id !== void 0) attributes.id = decodeEntities(id);
|
|
194
|
+
else if (cls !== void 0)
|
|
195
|
+
attributes.class = attributes.class ? `${attributes.class} ${cls}` : cls;
|
|
196
|
+
else if (key !== void 0) attributes[key] = decodeEntities(dq ?? sq ?? bare ?? "");
|
|
197
|
+
pos += whole.length;
|
|
198
|
+
if (src[pos] !== " " && src[pos] !== " " && src[pos] !== "}") return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function parseLabel(src) {
|
|
202
|
+
let depth = 0;
|
|
203
|
+
for (let pos = 0; pos < src.length; pos++) {
|
|
204
|
+
const char = src[pos];
|
|
205
|
+
if (char === "\n") return null;
|
|
206
|
+
if (char === "\\") {
|
|
207
|
+
pos++;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (char === "[") depth++;
|
|
211
|
+
else if (char === "]") {
|
|
212
|
+
depth--;
|
|
213
|
+
if (depth === 0) return { label: src.slice(1, pos), length: pos + 1 };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
function parseDirectiveTail(src) {
|
|
219
|
+
let rest = src;
|
|
220
|
+
let label;
|
|
221
|
+
let attributes = {};
|
|
222
|
+
if (rest.startsWith("[")) {
|
|
223
|
+
const parsed = parseLabel(rest);
|
|
224
|
+
if (!parsed) return null;
|
|
225
|
+
label = parsed.label;
|
|
226
|
+
rest = rest.slice(parsed.length);
|
|
227
|
+
}
|
|
228
|
+
if (rest.startsWith("{")) {
|
|
229
|
+
const parsed = parseAttributes(rest);
|
|
230
|
+
if (!parsed) return null;
|
|
231
|
+
attributes = parsed.attributes;
|
|
232
|
+
rest = rest.slice(parsed.length);
|
|
233
|
+
}
|
|
234
|
+
return { ...label !== void 0 ? { label } : {}, attributes, rest };
|
|
235
|
+
}
|
|
236
|
+
function plainLabel(label) {
|
|
237
|
+
if (label === void 0) return void 0;
|
|
238
|
+
const text = label.replace(/\\(.)/g, "$1").replace(/[*_`~]/g, "").trim();
|
|
239
|
+
return text || void 0;
|
|
240
|
+
}
|
|
241
|
+
var isVideoAddress = (value) => safeHelpHref(value) === value && (/^https:\/\//i.test(value) || value.startsWith("/"));
|
|
242
|
+
function fieldValue(field, raw) {
|
|
243
|
+
if (field.type === "boolean") {
|
|
244
|
+
const flag = raw.trim().toLowerCase();
|
|
245
|
+
if (flag === "" || flag === "true") return true;
|
|
246
|
+
if (flag === "false") return false;
|
|
247
|
+
return void 0;
|
|
248
|
+
}
|
|
249
|
+
const value = raw.trim();
|
|
250
|
+
switch (field.type) {
|
|
251
|
+
case "text":
|
|
252
|
+
return !/[\r\n]/.test(value) && value.length <= TEXT_VALUE_MAX ? value : void 0;
|
|
253
|
+
case "longText":
|
|
254
|
+
return value.length <= LONG_VALUE_MAX ? value : void 0;
|
|
255
|
+
case "icon":
|
|
256
|
+
return ICON.test(value) ? value : void 0;
|
|
257
|
+
case "url":
|
|
258
|
+
return value.length <= LONG_VALUE_MAX && safeHelpHref(value) === value ? value : void 0;
|
|
259
|
+
case "choice":
|
|
260
|
+
return field.options?.includes(value) ? value : void 0;
|
|
261
|
+
case "image":
|
|
262
|
+
return value.length <= LONG_VALUE_MAX && safeHelpImageSrc(value) === value ? value : void 0;
|
|
263
|
+
case "video":
|
|
264
|
+
return value.length <= LONG_VALUE_MAX && isVideoAddress(value) ? value : void 0;
|
|
265
|
+
default:
|
|
266
|
+
return void 0;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function helpBlockValues(block, attributes) {
|
|
270
|
+
const values = {};
|
|
271
|
+
for (const field of block.fields) {
|
|
272
|
+
const raw = Object.hasOwn(attributes, field.key) ? attributes[field.key] : void 0;
|
|
273
|
+
if (field.type === "boolean") {
|
|
274
|
+
if (raw === void 0) {
|
|
275
|
+
values[field.key] = false;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
} else if (raw === void 0 || raw.trim() === "") {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const value = fieldValue(field, raw);
|
|
282
|
+
if (value !== void 0) values[field.key] = value;
|
|
283
|
+
}
|
|
284
|
+
return values;
|
|
285
|
+
}
|
|
286
|
+
function isTemplate(template) {
|
|
287
|
+
for (let i = 0; i < template.length; i++) {
|
|
288
|
+
const char = template[i];
|
|
289
|
+
if ((char === "{" || char === "}") && template[i + 1] === char) {
|
|
290
|
+
i++;
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (char === "}") return false;
|
|
294
|
+
if (char !== "{") continue;
|
|
295
|
+
const end = template.indexOf("}", i + 1);
|
|
296
|
+
if (end === -1) return false;
|
|
297
|
+
const key = template.slice(i + 1, end).trim();
|
|
298
|
+
if (!key || key.includes("{")) return false;
|
|
299
|
+
i = end;
|
|
300
|
+
}
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
function helpBlockText(block, values, label) {
|
|
304
|
+
const template = block.text ?? "";
|
|
305
|
+
const fields = new Map(block.fields.map((f) => [f.key, f]));
|
|
306
|
+
let text = "";
|
|
307
|
+
if (isTemplate(template)) {
|
|
308
|
+
for (let i = 0; i < template.length; i++) {
|
|
309
|
+
const char = template[i] ?? "";
|
|
310
|
+
if ((char === "{" || char === "}") && template[i + 1] === char) {
|
|
311
|
+
text += char;
|
|
312
|
+
i++;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (char !== "{") {
|
|
316
|
+
text += char;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const end = template.indexOf("}", i + 1);
|
|
320
|
+
const key = template.slice(i + 1, end).trim();
|
|
321
|
+
const value = values[key];
|
|
322
|
+
if (value === true) text += fields.get(key)?.label ?? "";
|
|
323
|
+
else if (typeof value === "string") text += value;
|
|
324
|
+
i = end;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return `${text} ${label ?? ""}`.replace(/\s+/g, " ").trim();
|
|
328
|
+
}
|
|
329
|
+
var FORMS = {
|
|
330
|
+
textDirective: "inline",
|
|
331
|
+
leafDirective: "block",
|
|
332
|
+
containerDirective: "container"
|
|
333
|
+
};
|
|
334
|
+
function helpBlockMatcher(blocks, markdown) {
|
|
335
|
+
const defined = new Map((blocks ?? []).map((block) => [block.name, block]));
|
|
336
|
+
const cache = /* @__PURE__ */ new Map();
|
|
337
|
+
const find = (node) => {
|
|
338
|
+
if (defined.size === 0) return null;
|
|
339
|
+
if (node.type !== "textDirective" && node.type !== "leafDirective" && node.type !== "containerDirective") {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
const block = defined.get(node.name);
|
|
343
|
+
if (!block || block.kind !== FORMS[node.type]) return null;
|
|
344
|
+
const start = node.position?.start.offset;
|
|
345
|
+
const end = node.position?.end.offset;
|
|
346
|
+
if (start === void 0 || end === void 0) return null;
|
|
347
|
+
const lineEnd = markdown.indexOf("\n", start);
|
|
348
|
+
const line = markdown.slice(start, lineEnd === -1 ? void 0 : lineEnd);
|
|
349
|
+
const head = /^(:+)([A-Za-z][A-Za-z0-9_-]*)/.exec(line);
|
|
350
|
+
const colons = head?.[1]?.length ?? 0;
|
|
351
|
+
const form = colons === 1 ? "textDirective" : colons === 2 ? "leafDirective" : "containerDirective";
|
|
352
|
+
if (!head || form !== node.type || head[2] !== node.name) return null;
|
|
353
|
+
const tail = parseDirectiveTail(line.slice(head[0].length));
|
|
354
|
+
if (!tail) return null;
|
|
355
|
+
if (node.type === "textDirective") {
|
|
356
|
+
if (line.length - tail.rest.length !== end - start) return null;
|
|
357
|
+
} else if (tail.rest.trim() !== "") return null;
|
|
358
|
+
const values = helpBlockValues(block, tail.attributes);
|
|
359
|
+
const label = node.type === "containerDirective" ? void 0 : plainLabel(tail.label);
|
|
360
|
+
return { block, node, values, text: helpBlockText(block, values, label) };
|
|
361
|
+
};
|
|
362
|
+
const use = (node) => {
|
|
363
|
+
if (!cache.has(node)) cache.set(node, find(node));
|
|
364
|
+
return cache.get(node) ?? null;
|
|
365
|
+
};
|
|
366
|
+
const text = (node) => {
|
|
367
|
+
const found = use(node);
|
|
368
|
+
if (!found) return void 0;
|
|
369
|
+
if (found.node.type !== "containerDirective") return found.text;
|
|
370
|
+
const inner = contentOf(found.node).map((child) => plainText(child, text)).join(" ");
|
|
371
|
+
return `${found.text} ${inner}`.trim();
|
|
372
|
+
};
|
|
373
|
+
return { use, text };
|
|
374
|
+
}
|
|
126
375
|
var PLAIN_CODE_COLORS = {
|
|
127
376
|
"--shiki-light": "var(--color-fd-foreground)",
|
|
128
377
|
"--shiki-dark": "var(--color-fd-foreground)"
|
|
@@ -152,6 +401,15 @@ var helpBodyComponents = {
|
|
|
152
401
|
Link: ({ href, title, children }) => /* @__PURE__ */ jsx("a", { href, title, children }),
|
|
153
402
|
Image: ({ src, alt, title }) => /* @__PURE__ */ jsx("img", { src, alt, title, loading: "lazy" })
|
|
154
403
|
};
|
|
404
|
+
var BUILT_IN_KEYS = Object.keys(helpBodyComponents);
|
|
405
|
+
function pickBuiltIns(components) {
|
|
406
|
+
const out = {};
|
|
407
|
+
for (const key of BUILT_IN_KEYS) {
|
|
408
|
+
const component = components[key];
|
|
409
|
+
if (component) out[key] = component;
|
|
410
|
+
}
|
|
411
|
+
return out;
|
|
412
|
+
}
|
|
155
413
|
var normalizeId = (id) => id.trim().replace(/\s+/g, " ").toLowerCase();
|
|
156
414
|
function collectDefinitions(root) {
|
|
157
415
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -254,8 +512,30 @@ function renderTabs(children, ctx) {
|
|
|
254
512
|
);
|
|
255
513
|
}) });
|
|
256
514
|
}
|
|
515
|
+
function renderCustomBlock(use, ctx) {
|
|
516
|
+
const { block, node, values, text } = use;
|
|
517
|
+
const isContainer = node.type === "containerDirective";
|
|
518
|
+
const children = node.type === "containerDirective" ? renderAll(contentOf(node), { ...ctx, tight: false }) : node.children.length ? renderAll(node.children, { ...ctx, inline: true }) : void 0;
|
|
519
|
+
const Component = Object.hasOwn(ctx.custom, block.name) ? ctx.custom[block.name] : void 0;
|
|
520
|
+
if (Component) {
|
|
521
|
+
return createElement(Component, { ...values, block: block.name, lang: ctx.lang }, children);
|
|
522
|
+
}
|
|
523
|
+
const attributes = {
|
|
524
|
+
className: "sd-block",
|
|
525
|
+
"data-block": block.name,
|
|
526
|
+
"data-props": JSON.stringify(values)
|
|
527
|
+
};
|
|
528
|
+
if (block.kind === "inline") return /* @__PURE__ */ jsx("span", { ...attributes, children: text });
|
|
529
|
+
if (!isContainer) return /* @__PURE__ */ jsx("div", { ...attributes, children: text });
|
|
530
|
+
return /* @__PURE__ */ jsxs("div", { ...attributes, children: [
|
|
531
|
+
text ? /* @__PURE__ */ jsx("p", { className: "sd-block-text", children: text }) : null,
|
|
532
|
+
/* @__PURE__ */ jsx("div", { className: "sd-block-body", children })
|
|
533
|
+
] });
|
|
534
|
+
}
|
|
257
535
|
function renderDirective(node, ctx) {
|
|
258
536
|
const { c } = ctx;
|
|
537
|
+
const custom = ctx.blocks.use(node);
|
|
538
|
+
if (custom) return renderCustomBlock(custom, ctx);
|
|
259
539
|
if (!isHelpBlock(node.name)) {
|
|
260
540
|
const source = sourceOf(node, ctx.markdown);
|
|
261
541
|
const lines = source.split("\n");
|
|
@@ -358,24 +638,34 @@ function renderNode(node, ctx) {
|
|
|
358
638
|
return null;
|
|
359
639
|
case "containerDirective":
|
|
360
640
|
return renderDirective(node, { ...ctx, tight: false });
|
|
361
|
-
// Not ours: printed back as written.
|
|
362
641
|
case "leafDirective":
|
|
642
|
+
case "textDirective": {
|
|
643
|
+
const custom = ctx.blocks.use(node);
|
|
644
|
+
if (custom) return renderCustomBlock(custom, ctx);
|
|
645
|
+
return node.type === "leafDirective" ? literalBlock(sourceOf(node, ctx.markdown)) : sourceOf(node, ctx.markdown);
|
|
646
|
+
}
|
|
647
|
+
// Not ours: printed back as written.
|
|
363
648
|
case "footnoteDefinition":
|
|
364
649
|
return literalBlock(sourceOf(node, ctx.markdown));
|
|
365
|
-
case "textDirective":
|
|
366
650
|
case "footnoteReference":
|
|
367
651
|
return sourceOf(node, ctx.markdown);
|
|
368
652
|
default:
|
|
369
653
|
return "children" in node ? renderAll(node.children, ctx) : "value" in node ? String(node.value) : plainText(node);
|
|
370
654
|
}
|
|
371
655
|
}
|
|
372
|
-
function HelpBody(
|
|
373
|
-
const
|
|
656
|
+
function HelpBody(props) {
|
|
657
|
+
const { page, components = {}, icon } = props;
|
|
658
|
+
const source = props.markdown ?? page?.markdown ?? "";
|
|
659
|
+
const headings = props.headings ?? page?.headings;
|
|
374
660
|
const root = parseHelpMarkdown(source);
|
|
661
|
+
const blocks = helpBlockMatcher(props.blocks, source);
|
|
375
662
|
const ctx = {
|
|
376
663
|
markdown: source,
|
|
377
|
-
|
|
378
|
-
|
|
664
|
+
blocks,
|
|
665
|
+
custom: components,
|
|
666
|
+
lang: props.lang ?? page?.locale ?? "",
|
|
667
|
+
ids: headingIds(root, headings, blocks.text),
|
|
668
|
+
c: { ...helpBodyComponents, ...pickBuiltIns(components) },
|
|
379
669
|
icon,
|
|
380
670
|
definitions: collectDefinitions(root),
|
|
381
671
|
linkless: false,
|
|
@@ -484,12 +774,17 @@ function parseBody(raw) {
|
|
|
484
774
|
}
|
|
485
775
|
|
|
486
776
|
// src/fumadocs/structured.ts
|
|
487
|
-
function helpStructuredData(markdown, headings) {
|
|
488
|
-
const
|
|
489
|
-
const
|
|
777
|
+
function helpStructuredData(markdown, headings, blocks) {
|
|
778
|
+
const source = markdown ?? "";
|
|
779
|
+
const root = parseHelpMarkdown(source);
|
|
780
|
+
const matcher = helpBlockMatcher(blocks, source);
|
|
781
|
+
const ids = headingIds(root, headings, matcher.text);
|
|
490
782
|
const out = { headings: [], contents: [] };
|
|
491
783
|
let current;
|
|
492
|
-
const text = (node) => plainText(node).replace(/\s+/g, " ").trim();
|
|
784
|
+
const text = (node) => plainText(node, matcher.text).replace(/\s+/g, " ").trim();
|
|
785
|
+
const add = (content) => {
|
|
786
|
+
if (content) out.contents.push({ heading: current, content });
|
|
787
|
+
};
|
|
493
788
|
const walk = (node) => {
|
|
494
789
|
if (node.type === "heading") {
|
|
495
790
|
current = ids.get(node);
|
|
@@ -497,9 +792,15 @@ function helpStructuredData(markdown, headings) {
|
|
|
497
792
|
if (current && content) out.headings.push({ id: current, content });
|
|
498
793
|
return;
|
|
499
794
|
}
|
|
795
|
+
const use = matcher.use(node);
|
|
796
|
+
if (use) {
|
|
797
|
+
add(use.text);
|
|
798
|
+
if (use.node.type === "containerDirective")
|
|
799
|
+
for (const child of contentOf(use.node)) walk(child);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
500
802
|
if (node.type === "paragraph" || node.type === "tableCell") {
|
|
501
|
-
|
|
502
|
-
if (content) out.contents.push({ heading: current, content });
|
|
803
|
+
add(text(node));
|
|
503
804
|
return;
|
|
504
805
|
}
|
|
505
806
|
if (node.type === "code" || node.type === "html" || node.type === "definition") return;
|
|
@@ -540,6 +841,7 @@ function helpTreeToFiles(payload, options) {
|
|
|
540
841
|
const ext = (name, extension) => `${name}${options.suffix ? `.${options.suffix}` : ""}.${extension}`;
|
|
541
842
|
const join = (dir, name) => dir ? `${dir}/${name}` : name;
|
|
542
843
|
const seen = /* @__PURE__ */ new Set();
|
|
844
|
+
const blocks = payload.site.blocks ?? [];
|
|
543
845
|
const addPage = (page, file) => {
|
|
544
846
|
if (seen.has(file)) return;
|
|
545
847
|
seen.add(file);
|
|
@@ -552,10 +854,11 @@ function helpTreeToFiles(payload, options) {
|
|
|
552
854
|
...page.icon ? { icon: page.icon } : {},
|
|
553
855
|
path: page.path,
|
|
554
856
|
lang: options.lang,
|
|
857
|
+
blocks,
|
|
555
858
|
load,
|
|
556
859
|
structuredData: async () => {
|
|
557
860
|
const full = await load();
|
|
558
|
-
return full ? helpStructuredData(full.markdown, full.headings) : { headings: [], contents: [] };
|
|
861
|
+
return full ? helpStructuredData(full.markdown, full.headings, blocks) : { headings: [], contents: [] };
|
|
559
862
|
},
|
|
560
863
|
...prefetched ? { page: prefetched } : {}
|
|
561
864
|
};
|
|
@@ -662,4 +965,4 @@ function getHelpToc(headings, options = {}) {
|
|
|
662
965
|
return list.filter((heading) => heading.level >= min && heading.level <= max && heading.id).map((heading) => ({ title: heading.text, url: `#${heading.id}`, depth: heading.level }));
|
|
663
966
|
}
|
|
664
967
|
|
|
665
|
-
export { HELP_BLOCKS, HELP_CALLOUT_TYPES, HelpBody, createHelpRevalidateHandler, createHelpSource, getHelpToc, helpBodyComponents, helpDynamicSource, helpMetaEntry, helpStructuredData, helpTreePaths, helpTreeToFiles, safeHelpHref };
|
|
968
|
+
export { HELP_BLOCKS, HELP_CALLOUT_TYPES, HelpBody, createHelpRevalidateHandler, createHelpSource, getHelpToc, helpBlockText, helpBlockValues, helpBodyComponents, helpDynamicSource, helpMetaEntry, helpStructuredData, helpTreePaths, helpTreeToFiles, safeHelpHref };
|
package/dist/index.d.cts
CHANGED
|
@@ -153,6 +153,40 @@ interface HelpPublicSite {
|
|
|
153
153
|
languages: string[];
|
|
154
154
|
/** The docs website's base address (`https://help.acme.com/docs`), if set. */
|
|
155
155
|
websiteUrl: string | null;
|
|
156
|
+
/** The site's own building blocks pages may use (empty when it defines none). */
|
|
157
|
+
blocks: HelpPublicBlockDefinition[];
|
|
158
|
+
}
|
|
159
|
+
/** What a field of a site's own block holds (see {@link HelpPublicBlockField}). */
|
|
160
|
+
type HelpPublicBlockFieldType = "text" | "longText" | "icon" | "url" | "choice" | "image" | "video" | "boolean";
|
|
161
|
+
/**
|
|
162
|
+
* One field of a site's own block: an attribute of its directive
|
|
163
|
+
* (`key="value"`). `boolean` fields are on when written bare (`key`) or as
|
|
164
|
+
* `key="true"`; every other value is a string. `options` lists a `choice`
|
|
165
|
+
* field's values (null for other types).
|
|
166
|
+
*/
|
|
167
|
+
interface HelpPublicBlockField {
|
|
168
|
+
key: string;
|
|
169
|
+
label: string;
|
|
170
|
+
type: HelpPublicBlockFieldType;
|
|
171
|
+
required: boolean;
|
|
172
|
+
options: string[] | null;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* A building block the site defines itself — WHAT it is; how it looks is the
|
|
176
|
+
* website's own component. Written in Markdown as `:name[label]{…}` in running
|
|
177
|
+
* text (`inline`), `::name[label]{…}` alone on a line (`block`) or
|
|
178
|
+
* `:::name{…}` … `:::` around Markdown content (`container`). `text` is the
|
|
179
|
+
* template of its text version (`{key}` = a field's value, a `boolean` field's
|
|
180
|
+
* label when on; `{{`/`}}` = a brace) — what to show without a component.
|
|
181
|
+
*/
|
|
182
|
+
interface HelpPublicBlockDefinition {
|
|
183
|
+
/** The directive name (`app-button`). */
|
|
184
|
+
name: string;
|
|
185
|
+
/** Display name. */
|
|
186
|
+
label: string;
|
|
187
|
+
kind: "inline" | "block" | "container";
|
|
188
|
+
fields: HelpPublicBlockField[];
|
|
189
|
+
text: string;
|
|
156
190
|
}
|
|
157
191
|
/** A page in the navigation. */
|
|
158
192
|
interface HelpPublicTreePage {
|
|
@@ -262,12 +296,17 @@ interface HelpPublicPageLink {
|
|
|
262
296
|
}
|
|
263
297
|
/** One page with its text. */
|
|
264
298
|
interface HelpPublicPage extends HelpPublicPageSummary {
|
|
265
|
-
/**
|
|
299
|
+
/**
|
|
300
|
+
* The page's Markdown with directives (`:::callout`, `::::steps`, …, and the
|
|
301
|
+
* site's own blocks as written — see `site.blocks`).
|
|
302
|
+
*/
|
|
266
303
|
markdown: string;
|
|
267
304
|
/** Safe server-rendered HTML of `markdown` (stable `sd-*` classes for the directives). */
|
|
268
305
|
html: string;
|
|
269
306
|
headings: HelpPublicHeading[];
|
|
270
307
|
blocks: HelpPublicBlock[];
|
|
308
|
+
/** The site's own building blocks the page uses, by name (in the site's order). */
|
|
309
|
+
customBlocks: string[];
|
|
271
310
|
breadcrumbs: HelpPublicBreadcrumb[];
|
|
272
311
|
previous: HelpPublicPageLink | null;
|
|
273
312
|
next: HelpPublicPageLink | null;
|
|
@@ -430,4 +469,4 @@ declare function createHelpClient(options: HelpClientOptions): HelpClient;
|
|
|
430
469
|
*/
|
|
431
470
|
declare function verifyHelpWebhook(secret: string, rawBody: WebhookBody, signatureHeader: string | null | undefined, options?: VerifyWebhookOptions): Promise<HelpWebhookBody | null>;
|
|
432
471
|
|
|
433
|
-
export { type BaseClientOptions, type CachedResponse, DEFAULT_BASE_URL, EVENT_HEADER, type EtagCache, type FetchInitLike, type FetchLike, type FetchResponseLike, type GetPageParams, type HelpClient, type HelpClientOptions, type HelpPageResult, type HelpPublicBlock, type HelpPublicBreadcrumb, type HelpPublicHeading, type HelpPublicPage, type HelpPublicPageLink, type HelpPublicPagePayload, type HelpPublicPageSummary, type HelpPublicPagesPayload, type HelpPublicPreviewPage, type HelpPublicPreviewPayload, type HelpPublicRedirect, type HelpPublicSearchHit, type HelpPublicSearchPayload, type HelpPublicSite, type HelpPublicSitePayload, type HelpPublicTreeFolder, type HelpPublicTreeLink, type HelpPublicTreeNode, type HelpPublicTreePage, type HelpPublicTreeSeparator, type HelpWebhookBody, type HelpWebhookEvent, type LangParams, type RequestOptions, SIGNATURE_HEADER, type SearchParams, SolutionDeskError, type VerifyWebhookOptions, type WebhookBody, createHelpClient, createMemoryEtagCache, normalizeHelpPath, verifyHelpWebhook, verifyWebhookSignature };
|
|
472
|
+
export { type BaseClientOptions, type CachedResponse, DEFAULT_BASE_URL, EVENT_HEADER, type EtagCache, type FetchInitLike, type FetchLike, type FetchResponseLike, type GetPageParams, type HelpClient, type HelpClientOptions, type HelpPageResult, type HelpPublicBlock, type HelpPublicBlockDefinition, type HelpPublicBlockField, type HelpPublicBlockFieldType, type HelpPublicBreadcrumb, type HelpPublicHeading, type HelpPublicPage, type HelpPublicPageLink, type HelpPublicPagePayload, type HelpPublicPageSummary, type HelpPublicPagesPayload, type HelpPublicPreviewPage, type HelpPublicPreviewPayload, type HelpPublicRedirect, type HelpPublicSearchHit, type HelpPublicSearchPayload, type HelpPublicSite, type HelpPublicSitePayload, type HelpPublicTreeFolder, type HelpPublicTreeLink, type HelpPublicTreeNode, type HelpPublicTreePage, type HelpPublicTreeSeparator, type HelpWebhookBody, type HelpWebhookEvent, type LangParams, type RequestOptions, SIGNATURE_HEADER, type SearchParams, SolutionDeskError, type VerifyWebhookOptions, type WebhookBody, createHelpClient, createMemoryEtagCache, normalizeHelpPath, verifyHelpWebhook, verifyWebhookSignature };
|
package/dist/index.d.ts
CHANGED
|
@@ -153,6 +153,40 @@ interface HelpPublicSite {
|
|
|
153
153
|
languages: string[];
|
|
154
154
|
/** The docs website's base address (`https://help.acme.com/docs`), if set. */
|
|
155
155
|
websiteUrl: string | null;
|
|
156
|
+
/** The site's own building blocks pages may use (empty when it defines none). */
|
|
157
|
+
blocks: HelpPublicBlockDefinition[];
|
|
158
|
+
}
|
|
159
|
+
/** What a field of a site's own block holds (see {@link HelpPublicBlockField}). */
|
|
160
|
+
type HelpPublicBlockFieldType = "text" | "longText" | "icon" | "url" | "choice" | "image" | "video" | "boolean";
|
|
161
|
+
/**
|
|
162
|
+
* One field of a site's own block: an attribute of its directive
|
|
163
|
+
* (`key="value"`). `boolean` fields are on when written bare (`key`) or as
|
|
164
|
+
* `key="true"`; every other value is a string. `options` lists a `choice`
|
|
165
|
+
* field's values (null for other types).
|
|
166
|
+
*/
|
|
167
|
+
interface HelpPublicBlockField {
|
|
168
|
+
key: string;
|
|
169
|
+
label: string;
|
|
170
|
+
type: HelpPublicBlockFieldType;
|
|
171
|
+
required: boolean;
|
|
172
|
+
options: string[] | null;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* A building block the site defines itself — WHAT it is; how it looks is the
|
|
176
|
+
* website's own component. Written in Markdown as `:name[label]{…}` in running
|
|
177
|
+
* text (`inline`), `::name[label]{…}` alone on a line (`block`) or
|
|
178
|
+
* `:::name{…}` … `:::` around Markdown content (`container`). `text` is the
|
|
179
|
+
* template of its text version (`{key}` = a field's value, a `boolean` field's
|
|
180
|
+
* label when on; `{{`/`}}` = a brace) — what to show without a component.
|
|
181
|
+
*/
|
|
182
|
+
interface HelpPublicBlockDefinition {
|
|
183
|
+
/** The directive name (`app-button`). */
|
|
184
|
+
name: string;
|
|
185
|
+
/** Display name. */
|
|
186
|
+
label: string;
|
|
187
|
+
kind: "inline" | "block" | "container";
|
|
188
|
+
fields: HelpPublicBlockField[];
|
|
189
|
+
text: string;
|
|
156
190
|
}
|
|
157
191
|
/** A page in the navigation. */
|
|
158
192
|
interface HelpPublicTreePage {
|
|
@@ -262,12 +296,17 @@ interface HelpPublicPageLink {
|
|
|
262
296
|
}
|
|
263
297
|
/** One page with its text. */
|
|
264
298
|
interface HelpPublicPage extends HelpPublicPageSummary {
|
|
265
|
-
/**
|
|
299
|
+
/**
|
|
300
|
+
* The page's Markdown with directives (`:::callout`, `::::steps`, …, and the
|
|
301
|
+
* site's own blocks as written — see `site.blocks`).
|
|
302
|
+
*/
|
|
266
303
|
markdown: string;
|
|
267
304
|
/** Safe server-rendered HTML of `markdown` (stable `sd-*` classes for the directives). */
|
|
268
305
|
html: string;
|
|
269
306
|
headings: HelpPublicHeading[];
|
|
270
307
|
blocks: HelpPublicBlock[];
|
|
308
|
+
/** The site's own building blocks the page uses, by name (in the site's order). */
|
|
309
|
+
customBlocks: string[];
|
|
271
310
|
breadcrumbs: HelpPublicBreadcrumb[];
|
|
272
311
|
previous: HelpPublicPageLink | null;
|
|
273
312
|
next: HelpPublicPageLink | null;
|
|
@@ -430,4 +469,4 @@ declare function createHelpClient(options: HelpClientOptions): HelpClient;
|
|
|
430
469
|
*/
|
|
431
470
|
declare function verifyHelpWebhook(secret: string, rawBody: WebhookBody, signatureHeader: string | null | undefined, options?: VerifyWebhookOptions): Promise<HelpWebhookBody | null>;
|
|
432
471
|
|
|
433
|
-
export { type BaseClientOptions, type CachedResponse, DEFAULT_BASE_URL, EVENT_HEADER, type EtagCache, type FetchInitLike, type FetchLike, type FetchResponseLike, type GetPageParams, type HelpClient, type HelpClientOptions, type HelpPageResult, type HelpPublicBlock, type HelpPublicBreadcrumb, type HelpPublicHeading, type HelpPublicPage, type HelpPublicPageLink, type HelpPublicPagePayload, type HelpPublicPageSummary, type HelpPublicPagesPayload, type HelpPublicPreviewPage, type HelpPublicPreviewPayload, type HelpPublicRedirect, type HelpPublicSearchHit, type HelpPublicSearchPayload, type HelpPublicSite, type HelpPublicSitePayload, type HelpPublicTreeFolder, type HelpPublicTreeLink, type HelpPublicTreeNode, type HelpPublicTreePage, type HelpPublicTreeSeparator, type HelpWebhookBody, type HelpWebhookEvent, type LangParams, type RequestOptions, SIGNATURE_HEADER, type SearchParams, SolutionDeskError, type VerifyWebhookOptions, type WebhookBody, createHelpClient, createMemoryEtagCache, normalizeHelpPath, verifyHelpWebhook, verifyWebhookSignature };
|
|
472
|
+
export { type BaseClientOptions, type CachedResponse, DEFAULT_BASE_URL, EVENT_HEADER, type EtagCache, type FetchInitLike, type FetchLike, type FetchResponseLike, type GetPageParams, type HelpClient, type HelpClientOptions, type HelpPageResult, type HelpPublicBlock, type HelpPublicBlockDefinition, type HelpPublicBlockField, type HelpPublicBlockFieldType, type HelpPublicBreadcrumb, type HelpPublicHeading, type HelpPublicPage, type HelpPublicPageLink, type HelpPublicPagePayload, type HelpPublicPageSummary, type HelpPublicPagesPayload, type HelpPublicPreviewPage, type HelpPublicPreviewPayload, type HelpPublicRedirect, type HelpPublicSearchHit, type HelpPublicSearchPayload, type HelpPublicSite, type HelpPublicSitePayload, type HelpPublicTreeFolder, type HelpPublicTreeLink, type HelpPublicTreeNode, type HelpPublicTreePage, type HelpPublicTreeSeparator, type HelpWebhookBody, type HelpWebhookEvent, type LangParams, type RequestOptions, SIGNATURE_HEADER, type SearchParams, SolutionDeskError, type VerifyWebhookOptions, type WebhookBody, createHelpClient, createMemoryEtagCache, normalizeHelpPath, verifyHelpWebhook, verifyWebhookSignature };
|
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-a1101b8",
|
|
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",
|