mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
Core: Support attaching custom properties during page tree builder process
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-core': minor
|
||||
---
|
||||
|
||||
Support attaching custom properties during page tree builder process
|
||||
@@ -21,8 +21,12 @@ export interface Separator {
|
||||
}
|
||||
|
||||
export interface Folder {
|
||||
/**
|
||||
* Optional id to be attached to folders
|
||||
*/
|
||||
id?: string;
|
||||
|
||||
type: 'folder';
|
||||
id: string;
|
||||
name: string;
|
||||
root?: boolean;
|
||||
defaultOpen?: boolean;
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
type Transformer,
|
||||
} from './load-files';
|
||||
import type { FileData, MetaData, PageData, UrlFn } from './types';
|
||||
import type { CreatePageTreeBuilderOptions } from './page-tree-builder';
|
||||
import type { BuildPageTreeOptions } from './page-tree-builder';
|
||||
import { createPageTreeBuilder } from './page-tree-builder';
|
||||
import { splitPath, type FileInfo } from './path';
|
||||
import type { File, Storage } from './file-system';
|
||||
@@ -31,12 +31,17 @@ export interface LoaderOptions {
|
||||
*/
|
||||
baseUrl?: string;
|
||||
languages?: string[];
|
||||
icon?: NonNullable<CreatePageTreeBuilderOptions['resolveIcon']>;
|
||||
icon?: NonNullable<BuildPageTreeOptions['resolveIcon']>;
|
||||
slugs?: LoadOptions['getSlugs'];
|
||||
url?: UrlFn;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Inevitable
|
||||
source: Source<any>;
|
||||
transformers?: Transformer[];
|
||||
|
||||
/**
|
||||
* Additional options for page tree builder
|
||||
*/
|
||||
pageTree?: Partial<Omit<BuildPageTreeOptions, 'storage' | 'getUrl'>>;
|
||||
}
|
||||
|
||||
export interface Source<Config extends SourceConfig> {
|
||||
@@ -149,13 +154,14 @@ export function loader<Options extends LoaderOptions>(
|
||||
|
||||
function createOutput({
|
||||
source,
|
||||
icon,
|
||||
icon: resolveIcon,
|
||||
languages,
|
||||
rootDir = '',
|
||||
transformers,
|
||||
baseUrl = '/',
|
||||
slugs: slugsFn = getSlugs,
|
||||
url: getUrl = createGetUrl(baseUrl),
|
||||
pageTree: pageTreeOptions = {},
|
||||
}: LoaderOptions): LoaderOutput<LoaderConfig> {
|
||||
const storage = loadFiles(
|
||||
typeof source.files === 'function' ? source.files(rootDir) : source.files,
|
||||
@@ -166,15 +172,22 @@ function createOutput({
|
||||
},
|
||||
);
|
||||
const i18nMap = buildPageMap(storage, languages ?? [], getUrl);
|
||||
const builder = createPageTreeBuilder({
|
||||
storage,
|
||||
resolveIcon: icon,
|
||||
getUrl,
|
||||
});
|
||||
const builder = createPageTreeBuilder();
|
||||
const pageTree =
|
||||
languages === undefined
|
||||
? builder.build()
|
||||
: builder.buildI18n({ languages });
|
||||
? builder.build({
|
||||
storage,
|
||||
resolveIcon,
|
||||
getUrl,
|
||||
...pageTreeOptions,
|
||||
})
|
||||
: builder.buildI18n({
|
||||
languages,
|
||||
storage,
|
||||
resolveIcon,
|
||||
getUrl,
|
||||
...pageTreeOptions,
|
||||
});
|
||||
|
||||
return {
|
||||
pageTree: pageTree as LoaderOutput<LoaderConfig>['pageTree'],
|
||||
|
||||
@@ -4,34 +4,50 @@ import type { File, Folder, Storage } from './file-system';
|
||||
import { resolvePath } from './path';
|
||||
import { type FileData, type UrlFn } from './types';
|
||||
|
||||
interface PageTreeBuilderContext extends CreatePageTreeBuilderOptions {
|
||||
interface PageTreeBuilderContext {
|
||||
lang?: string;
|
||||
|
||||
storage: Storage;
|
||||
builder: PageTreeBuilder;
|
||||
options: BuildPageTreeOptions;
|
||||
}
|
||||
|
||||
export interface BuildPageTreeOptionsWithI18n {
|
||||
languages: string[];
|
||||
export interface BuildPageTreeOptions {
|
||||
/**
|
||||
* Attach the `folder.id` property
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
attachFolderIds?: boolean;
|
||||
|
||||
attachFile?: (node: PageTree.Item, file?: File) => PageTree.Item;
|
||||
attachFolder?: (
|
||||
node: PageTree.Folder,
|
||||
folder: Folder,
|
||||
meta?: File,
|
||||
) => PageTree.Folder;
|
||||
attachSeparator?: (node: PageTree.Separator) => PageTree.Separator;
|
||||
|
||||
storage: Storage;
|
||||
getUrl: UrlFn;
|
||||
resolveIcon?: (icon: string | undefined) => ReactElement | undefined;
|
||||
}
|
||||
|
||||
export interface BuildPageTreeOptionsWithI18n extends BuildPageTreeOptions {
|
||||
languages?: string[];
|
||||
}
|
||||
|
||||
export interface PageTreeBuilder {
|
||||
build: () => PageTree.Root;
|
||||
build: (options: BuildPageTreeOptions) => PageTree.Root;
|
||||
|
||||
/**
|
||||
* Build page tree and fallback to the default language if the localized page doesn't exist
|
||||
*/
|
||||
buildI18n: (
|
||||
options?: Partial<BuildPageTreeOptionsWithI18n>,
|
||||
options: BuildPageTreeOptionsWithI18n,
|
||||
) => Record<string, PageTree.Root>;
|
||||
}
|
||||
|
||||
export interface CreatePageTreeBuilderOptions {
|
||||
storage: Storage;
|
||||
|
||||
getUrl: UrlFn;
|
||||
resolveIcon?: (icon: string | undefined) => ReactElement | undefined;
|
||||
}
|
||||
|
||||
const link = /^\[(?<text>.+)]\((?<url>.+)\)$/;
|
||||
const separator = /^---(?<name>.*?)---$/;
|
||||
const rest = '...';
|
||||
@@ -82,12 +98,12 @@ function resolveFolderItem(
|
||||
|
||||
const separateResult = separator.exec(item);
|
||||
if (separateResult?.groups) {
|
||||
return [
|
||||
{
|
||||
type: 'separator',
|
||||
name: separateResult.groups.name,
|
||||
},
|
||||
];
|
||||
const node: PageTree.Separator = {
|
||||
type: 'separator',
|
||||
name: separateResult.groups.name,
|
||||
};
|
||||
|
||||
return [ctx.options.attachSeparator?.(node) ?? node];
|
||||
}
|
||||
|
||||
const linkResult = link.exec(item);
|
||||
@@ -95,14 +111,15 @@ function resolveFolderItem(
|
||||
const { url, text } = linkResult.groups;
|
||||
const isRelative =
|
||||
url.startsWith('/') || url.startsWith('#') || url.startsWith('.');
|
||||
return [
|
||||
{
|
||||
type: 'page',
|
||||
name: text,
|
||||
url,
|
||||
external: !isRelative,
|
||||
},
|
||||
];
|
||||
|
||||
const node: PageTree.Item = {
|
||||
type: 'page',
|
||||
name: text,
|
||||
url,
|
||||
external: !isRelative,
|
||||
};
|
||||
|
||||
return [ctx.options.attachFile?.(node) ?? node];
|
||||
}
|
||||
|
||||
const extractResult = extractor.exec(item);
|
||||
@@ -171,16 +188,23 @@ function buildFolderNode(
|
||||
children = nodes ?? restNodes;
|
||||
}
|
||||
|
||||
return removeUndefined({
|
||||
const node: PageTree.Folder = {
|
||||
type: 'folder',
|
||||
id: folder.file.flattenedPath,
|
||||
name: metadata?.title ?? index?.name ?? pathToName(folder.file.name),
|
||||
icon: ctx.resolveIcon?.(metadata?.icon),
|
||||
icon: ctx.options.resolveIcon?.(metadata?.icon),
|
||||
root: metadata?.root,
|
||||
defaultOpen: metadata?.defaultOpen,
|
||||
index,
|
||||
children,
|
||||
});
|
||||
};
|
||||
|
||||
if (ctx.options.attachFolderIds) {
|
||||
node.id = folder.file.flattenedPath;
|
||||
}
|
||||
|
||||
return removeUndefined(
|
||||
ctx.options.attachFolder?.(node, folder, meta) ?? node,
|
||||
);
|
||||
}
|
||||
|
||||
function buildFileNode(file: File, ctx: PageTreeBuilderContext): PageTree.Item {
|
||||
@@ -188,12 +212,14 @@ function buildFileNode(file: File, ctx: PageTreeBuilderContext): PageTree.Item {
|
||||
findLocalizedFile(file.file.flattenedPath, 'page', ctx) ?? file;
|
||||
const data = localized.data as FileData['file'];
|
||||
|
||||
return removeUndefined({
|
||||
const item: PageTree.Item = {
|
||||
type: 'page',
|
||||
name: data.data.title,
|
||||
icon: ctx.resolveIcon?.(data.data.icon),
|
||||
url: ctx.getUrl(data.slugs, ctx.lang),
|
||||
});
|
||||
icon: ctx.options.resolveIcon?.(data.data.icon),
|
||||
url: ctx.options.getUrl(data.slugs, ctx.lang),
|
||||
};
|
||||
|
||||
return removeUndefined(ctx.options.attachFile?.(item, file) ?? item);
|
||||
}
|
||||
|
||||
function build(ctx: PageTreeBuilderContext): PageTree.Root {
|
||||
@@ -206,27 +232,23 @@ function build(ctx: PageTreeBuilderContext): PageTree.Root {
|
||||
};
|
||||
}
|
||||
|
||||
export function createPageTreeBuilder(
|
||||
options: CreatePageTreeBuilderOptions,
|
||||
): PageTreeBuilder {
|
||||
function getContext(
|
||||
builder: PageTreeBuilder,
|
||||
locale?: string,
|
||||
): PageTreeBuilderContext {
|
||||
return {
|
||||
...options,
|
||||
lang: locale,
|
||||
builder,
|
||||
};
|
||||
}
|
||||
|
||||
export function createPageTreeBuilder(): PageTreeBuilder {
|
||||
return {
|
||||
build() {
|
||||
return build(getContext(this));
|
||||
build(options) {
|
||||
return build({
|
||||
options,
|
||||
builder: this,
|
||||
storage: options.storage,
|
||||
});
|
||||
},
|
||||
buildI18n({ languages = [] } = {}) {
|
||||
buildI18n({ languages = [], ...options }) {
|
||||
const entries = languages.map<[string, PageTree.Root]>((lang) => {
|
||||
const tree = build(getContext(this, lang));
|
||||
const tree = build({
|
||||
lang,
|
||||
options,
|
||||
builder: this,
|
||||
storage: options.storage,
|
||||
});
|
||||
|
||||
return [lang, tree];
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user