mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 06:31:49 +00:00
Core: Introduce page tree transformer API
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
---
|
||||
'fumadocs-core': minor
|
||||
---
|
||||
|
||||
**Introduce page tree transformer API**
|
||||
|
||||
You can now define page tree transformer.
|
||||
|
||||
```ts
|
||||
export const source = loader({
|
||||
// ...
|
||||
pageTree: {
|
||||
transformers: [{
|
||||
root(root) {
|
||||
return root;
|
||||
},
|
||||
file(node, file) {
|
||||
return node;
|
||||
},
|
||||
folder(node, dir, metaPath) {
|
||||
return node;
|
||||
},
|
||||
separator(node) {
|
||||
return node;
|
||||
}
|
||||
}]
|
||||
}
|
||||
});
|
||||
```
|
||||
@@ -193,14 +193,19 @@ import { loader } from 'fumadocs-core/source';
|
||||
|
||||
loader({
|
||||
pageTree: {
|
||||
attachFile(node, file) {
|
||||
// you can access its file information
|
||||
console.log(file?.data);
|
||||
// JSX nodes are allowed
|
||||
node.name = <>Some JSX Nodes here</>;
|
||||
transformers: [
|
||||
{
|
||||
file(node, file) {
|
||||
// you can access its file information
|
||||
if (file) console.log(file, this.storage.read(file));
|
||||
|
||||
return node;
|
||||
},
|
||||
// JSX nodes are allowed
|
||||
node.name = <>Some JSX Nodes here</>;
|
||||
|
||||
return node;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -42,13 +42,13 @@ export const openapi = createOpenAPI({
|
||||
```
|
||||
|
||||
```ts title="lib/source.ts"
|
||||
import { attachFile } from 'fumadocs-openapi/server';
|
||||
import { transformerOpenAPI } from 'fumadocs-openapi/server';
|
||||
import { loader } from 'fumadocs-core/source';
|
||||
|
||||
export const source = loader({
|
||||
pageTree: {
|
||||
// [!code ++] adds a badge to each page item in page tree
|
||||
attachFile,
|
||||
transformers: [transformerOpenAPI()],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createMDXSource } from 'fumadocs-mdx';
|
||||
import type { InferMetaType, InferPageType } from 'fumadocs-core/source';
|
||||
import { loader } from 'fumadocs-core/source';
|
||||
import { icons } from 'lucide-react';
|
||||
import { attachFile } from 'fumadocs-openapi/server';
|
||||
import { transformerOpenAPI } from 'fumadocs-openapi/server';
|
||||
import { createElement } from 'react';
|
||||
import { blog as blogPosts, docs } from '@/.source';
|
||||
|
||||
@@ -14,7 +14,7 @@ export const source = loader({
|
||||
},
|
||||
source: docs.toFumadocsSource(),
|
||||
pageTree: {
|
||||
attachFile,
|
||||
transformers: [transformerOpenAPI()],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { createMDXSource } from 'fumadocs-mdx';
|
||||
import { loader } from 'fumadocs-core/source';
|
||||
import { attachFile } from 'fumadocs-openapi/server';
|
||||
import { transformerOpenAPI } from 'fumadocs-openapi/server';
|
||||
import { docs, meta } from '@/.source';
|
||||
|
||||
export const source = loader({
|
||||
baseUrl: '/docs',
|
||||
source: createMDXSource(docs, meta),
|
||||
pageTree: {
|
||||
attachFile,
|
||||
transformers: [transformerOpenAPI()],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
export * from './page-tree-builder';
|
||||
export {
|
||||
createPageTreeBuilder,
|
||||
type PageTreeTransformer,
|
||||
type PageTreeBuilder,
|
||||
type PageTreeBuilderContext,
|
||||
type BaseOptions,
|
||||
} from './page-tree/builder';
|
||||
export * from './loader';
|
||||
export { FileSystem } from './file-system';
|
||||
|
||||
|
||||
@@ -9,7 +9,10 @@ export interface LoadOptions {
|
||||
buildFile: (file: VirtualFile) => MetaFile | PageFile;
|
||||
}
|
||||
|
||||
export type ContentStorage = FileSystem<MetaFile | PageFile>;
|
||||
export type ContentStorage<
|
||||
Page extends PageData = PageData,
|
||||
Meta extends MetaData = MetaData,
|
||||
> = FileSystem<MetaFile<Meta> | PageFile<Page>>;
|
||||
|
||||
export interface MetaFile<Data extends MetaData = MetaData> {
|
||||
path: string;
|
||||
|
||||
@@ -8,10 +8,7 @@ import {
|
||||
type Transformer,
|
||||
} from './load-files';
|
||||
import type { MetaData, PageData, UrlFn } from './types';
|
||||
import {
|
||||
type BuildPageTreeOptions,
|
||||
createPageTreeBuilder,
|
||||
} from './page-tree-builder';
|
||||
import { type BaseOptions, createPageTreeBuilder } from './page-tree/builder';
|
||||
import {
|
||||
basename,
|
||||
dirname,
|
||||
@@ -37,7 +34,7 @@ export interface LoaderOptions<
|
||||
> {
|
||||
baseUrl: string;
|
||||
|
||||
icon?: NonNullable<BuildPageTreeOptions['resolveIcon']>;
|
||||
icon?: NonNullable<BaseOptions['resolveIcon']>;
|
||||
slugs?: (info: FileInfo) => string[];
|
||||
url?: UrlFn;
|
||||
|
||||
@@ -47,9 +44,7 @@ export interface LoaderOptions<
|
||||
/**
|
||||
* Additional options for page tree builder
|
||||
*/
|
||||
pageTree?: Partial<
|
||||
Omit<BuildPageTreeOptions<T['pageData'], T['metaData']>, 'storage'>
|
||||
>;
|
||||
pageTree?: Partial<BaseOptions<T['pageData'], T['metaData']>>;
|
||||
|
||||
/**
|
||||
* Configure i18n
|
||||
|
||||
+151
-79
@@ -1,70 +1,106 @@
|
||||
import type { ReactElement } from 'react';
|
||||
import type { I18nConfig } from '@/i18n';
|
||||
import type * as PageTree from '../server/page-tree';
|
||||
import type * as PageTree from '../../server/page-tree';
|
||||
import { joinPath } from '@/utils/path';
|
||||
import type { MetaData, PageData, UrlFn } from './types';
|
||||
import type { ContentStorage, MetaFile, PageFile } from '@/source/load-files';
|
||||
import type { MetaData, PageData, UrlFn } from '../types';
|
||||
import type { ContentStorage } from '@/source/load-files';
|
||||
import { basename, extname } from '@/source/path';
|
||||
import {
|
||||
legacyTransformer,
|
||||
type LegacyTransformerOptions,
|
||||
} from '@/source/page-tree/legacy';
|
||||
import { transformerFallback } from '@/source/page-tree/transformer-fallback';
|
||||
|
||||
interface PageTreeBuilderContext {
|
||||
storage: ContentStorage;
|
||||
resolveName: (name: string, format: 'meta' | 'page') => string;
|
||||
|
||||
locale?: string;
|
||||
localeStorage?: ContentStorage;
|
||||
|
||||
builder: PageTreeBuilder;
|
||||
options: BaseOptions;
|
||||
getUrl: UrlFn;
|
||||
}
|
||||
|
||||
interface BaseOptions<
|
||||
export interface PageTreeBuilderContext<
|
||||
Page extends PageData = PageData,
|
||||
Meta extends MetaData = MetaData,
|
||||
> {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
resolveName: (name: string, format: 'meta' | 'page') => string;
|
||||
options: BaseOptions<Page, Meta>;
|
||||
transformers: PageTreeTransformer<Page, Meta>[];
|
||||
|
||||
builder: PageTreeBuilder<Page, Meta>;
|
||||
storage: ContentStorage<Page, Meta>;
|
||||
|
||||
locale?: string;
|
||||
localeStorage?: ContentStorage<Page, Meta>;
|
||||
getUrl: UrlFn;
|
||||
}
|
||||
|
||||
export interface PageTreeTransformer<
|
||||
// eslint-disable-next-line
|
||||
Page extends PageData = any,
|
||||
// eslint-disable-next-line
|
||||
Meta extends MetaData = any,
|
||||
> {
|
||||
name?: string;
|
||||
|
||||
file?: (
|
||||
this: PageTreeBuilderContext<Page, Meta>,
|
||||
node: PageTree.Item,
|
||||
filePath?: string,
|
||||
) => PageTree.Item;
|
||||
folder?: (
|
||||
this: PageTreeBuilderContext<Page, Meta>,
|
||||
node: PageTree.Folder,
|
||||
folderPath: string,
|
||||
metaPath?: string,
|
||||
) => PageTree.Folder;
|
||||
separator?: (
|
||||
this: PageTreeBuilderContext<Page, Meta>,
|
||||
node: PageTree.Separator,
|
||||
) => PageTree.Separator;
|
||||
root?: (
|
||||
this: PageTreeBuilderContext<Page, Meta>,
|
||||
node: PageTree.Root,
|
||||
) => PageTree.Root;
|
||||
}
|
||||
|
||||
export interface BaseOptions<
|
||||
Page extends PageData = PageData,
|
||||
Meta extends MetaData = MetaData,
|
||||
> extends LegacyTransformerOptions<Page, Meta> {
|
||||
/**
|
||||
* Remove references to the file path of original nodes (`$ref`)
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
noRef?: boolean;
|
||||
|
||||
attachFile?: (node: PageTree.Item, file?: PageFile<Page>) => PageTree.Item;
|
||||
attachFolder?: (
|
||||
node: PageTree.Folder,
|
||||
folder: {
|
||||
children: (PageFile<Page> | MetaFile<Meta>)[];
|
||||
},
|
||||
meta?: MetaFile<Meta>,
|
||||
) => PageTree.Folder;
|
||||
attachSeparator?: (node: PageTree.Separator) => PageTree.Separator;
|
||||
|
||||
transformers?: PageTreeTransformer<Page, Meta>[];
|
||||
resolveIcon?: (icon: string | undefined) => ReactElement | undefined;
|
||||
}
|
||||
|
||||
export interface BuildPageTreeOptions<
|
||||
export interface PageTreeBuilder<
|
||||
Page extends PageData = PageData,
|
||||
Meta extends MetaData = MetaData,
|
||||
> extends BaseOptions<Page, Meta> {
|
||||
storage: ContentStorage;
|
||||
}
|
||||
> {
|
||||
build: (
|
||||
options: BaseOptions<Page, Meta> & {
|
||||
id?: string;
|
||||
|
||||
export interface BuildPageTreeOptionsWithI18n<
|
||||
Page extends PageData = PageData,
|
||||
Meta extends MetaData = MetaData,
|
||||
> extends BaseOptions<Page, Meta> {
|
||||
storages: Record<string, ContentStorage>;
|
||||
i18n: I18nConfig;
|
||||
}
|
||||
storage: ContentStorage<Page, Meta>;
|
||||
|
||||
export interface PageTreeBuilder {
|
||||
build: (options: BuildPageTreeOptions) => PageTree.Root;
|
||||
/**
|
||||
* generate fallback page tree
|
||||
*
|
||||
* @defaultValue true
|
||||
*/
|
||||
generateFallback?: boolean;
|
||||
},
|
||||
) => PageTree.Root;
|
||||
|
||||
/**
|
||||
* Build page tree and fallback to the default language if the localized page doesn't exist
|
||||
*/
|
||||
buildI18n: (
|
||||
options: BuildPageTreeOptionsWithI18n,
|
||||
options: BaseOptions<Page, Meta> & {
|
||||
id?: string;
|
||||
storages: Record<string, ContentStorage<Page, Meta>>;
|
||||
i18n: I18nConfig;
|
||||
},
|
||||
) => Record<string, PageTree.Root>;
|
||||
}
|
||||
|
||||
@@ -109,20 +145,25 @@ function resolveFolderItem(
|
||||
ctx: PageTreeBuilderContext,
|
||||
idx: number,
|
||||
restNodePaths: Set<string>,
|
||||
): PageTree.Node[] | typeof rest | typeof restReversed {
|
||||
): PageTree.Node[] | '...' | 'z...a' {
|
||||
if (item === rest || item === restReversed) return item;
|
||||
const { options, resolveName } = ctx;
|
||||
|
||||
let match = separator.exec(item);
|
||||
if (match?.groups) {
|
||||
const node: PageTree.Separator = {
|
||||
let node: PageTree.Separator = {
|
||||
$id: `${folderPath}#${idx}`,
|
||||
type: 'separator',
|
||||
icon: options.resolveIcon?.(match.groups.icon),
|
||||
name: match.groups.name,
|
||||
};
|
||||
|
||||
return [options.attachSeparator?.(node) ?? node];
|
||||
for (const transformer of ctx.transformers) {
|
||||
if (!transformer.separator) continue;
|
||||
node = transformer.separator.call(ctx, node);
|
||||
}
|
||||
|
||||
return [node];
|
||||
}
|
||||
|
||||
match = link.exec(item);
|
||||
@@ -131,7 +172,7 @@ function resolveFolderItem(
|
||||
const isRelative =
|
||||
url.startsWith('/') || url.startsWith('#') || url.startsWith('.');
|
||||
|
||||
const node: PageTree.Item = {
|
||||
let node: PageTree.Item = {
|
||||
type: 'page',
|
||||
icon: options.resolveIcon?.(icon),
|
||||
name,
|
||||
@@ -139,7 +180,12 @@ function resolveFolderItem(
|
||||
external: !isRelative,
|
||||
};
|
||||
|
||||
return [options.attachFile?.(node) ?? node];
|
||||
for (const transformer of ctx.transformers) {
|
||||
if (!transformer.file) continue;
|
||||
node = transformer.file.call(ctx, node);
|
||||
}
|
||||
|
||||
return [node];
|
||||
}
|
||||
|
||||
const isExcept = item.startsWith(excludePrefix);
|
||||
@@ -171,7 +217,7 @@ function buildFolderNode(
|
||||
isGlobalRoot: boolean,
|
||||
ctx: PageTreeBuilderContext,
|
||||
): PageTree.Folder | undefined {
|
||||
const { storage, localeStorage, options, resolveName } = ctx;
|
||||
const { storage, localeStorage, options, resolveName, transformers } = ctx;
|
||||
const files = storage.readDir(folderPath);
|
||||
if (!files) return;
|
||||
|
||||
@@ -183,12 +229,15 @@ function buildFolderNode(
|
||||
meta = undefined;
|
||||
}
|
||||
|
||||
const isRoot = meta?.data.root ?? isGlobalRoot;
|
||||
let indexDisabled = false;
|
||||
let indexDisabled = meta?.data.root ?? isGlobalRoot;
|
||||
let children: PageTree.Node[];
|
||||
|
||||
if (!meta?.data.pages) {
|
||||
children = buildAll(files, ctx, (file) => isRoot || file !== indexPath);
|
||||
children = buildAll(
|
||||
files,
|
||||
ctx,
|
||||
(file) => indexDisabled || file !== indexPath,
|
||||
);
|
||||
} else {
|
||||
const restItems = new Set<string>(files);
|
||||
const resolved = meta.data.pages.flatMap<
|
||||
@@ -196,7 +245,7 @@ function buildFolderNode(
|
||||
>((item, i) => resolveFolderItem(folderPath, item, ctx, i, restItems));
|
||||
|
||||
// disable folder index if it is used in `pages`
|
||||
if (!isRoot && !restItems.has(indexPath)) {
|
||||
if (!indexDisabled && !restItems.has(indexPath)) {
|
||||
indexDisabled = true;
|
||||
}
|
||||
|
||||
@@ -207,8 +256,7 @@ function buildFolderNode(
|
||||
const items = buildAll(
|
||||
files,
|
||||
ctx,
|
||||
// index files are not included in ... unless it's a root folder
|
||||
(file) => (file !== indexPath || isRoot) && restItems.has(file),
|
||||
(file) => (indexDisabled || file !== indexPath) && restItems.has(file),
|
||||
item === restReversed,
|
||||
);
|
||||
|
||||
@@ -227,7 +275,7 @@ function buildFolderNode(
|
||||
name = pathToName(group.exec(folderName)?.[1] ?? folderName);
|
||||
}
|
||||
|
||||
const node: PageTree.Folder = {
|
||||
let node: PageTree.Folder = {
|
||||
type: 'folder',
|
||||
name,
|
||||
icon: options.resolveIcon?.(meta?.data.icon) ?? index?.icon,
|
||||
@@ -245,28 +293,24 @@ function buildFolderNode(
|
||||
: undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
options.attachFolder?.(
|
||||
node,
|
||||
{
|
||||
get children() {
|
||||
return files.flatMap((file) => storage.read(file) ?? []);
|
||||
},
|
||||
},
|
||||
meta,
|
||||
) ?? node
|
||||
);
|
||||
for (const transformer of transformers) {
|
||||
if (!transformer.folder) continue;
|
||||
node = transformer.folder.call(ctx, node, folderPath, metaPath);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function buildFileNode(
|
||||
path: string,
|
||||
{ options, getUrl, storage, localeStorage, locale }: PageTreeBuilderContext,
|
||||
ctx: PageTreeBuilderContext,
|
||||
): PageTree.Item | undefined {
|
||||
const { options, getUrl, storage, localeStorage, locale, transformers } = ctx;
|
||||
const page = localeStorage?.read(path) ?? storage.read(path);
|
||||
if (page?.format !== 'page') return;
|
||||
|
||||
const { title, description, icon } = page.data;
|
||||
const item: PageTree.Item = {
|
||||
let item: PageTree.Item = {
|
||||
$id: path,
|
||||
type: 'page',
|
||||
name: title ?? pathToName(basename(path, extname(path))),
|
||||
@@ -280,20 +324,45 @@ function buildFileNode(
|
||||
: undefined,
|
||||
};
|
||||
|
||||
return options.attachFile?.(item, page) ?? item;
|
||||
for (const transformer of transformers) {
|
||||
if (!transformer.file) continue;
|
||||
item = transformer.file.call(ctx, item, path);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
function build(ctx: PageTreeBuilderContext): PageTree.Root {
|
||||
function build(id: string, ctx: PageTreeBuilderContext): PageTree.Root {
|
||||
const folder = buildFolderNode('', true, ctx)!;
|
||||
|
||||
return {
|
||||
$id: ctx.locale ?? 'root',
|
||||
let root: PageTree.Root = {
|
||||
$id: id,
|
||||
name: folder.name,
|
||||
children: folder.children,
|
||||
};
|
||||
|
||||
for (const transformer of ctx.transformers) {
|
||||
if (!transformer.root) continue;
|
||||
root = transformer.root.call(ctx, root);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
export function createPageTreeBuilder(getUrl: UrlFn): PageTreeBuilder {
|
||||
function getTransformers(options: BaseOptions, generateFallback = true) {
|
||||
const transformers: PageTreeTransformer[] = [legacyTransformer(options)];
|
||||
|
||||
if (options.transformers) {
|
||||
transformers.push(...options.transformers);
|
||||
}
|
||||
|
||||
if (generateFallback) {
|
||||
transformers.push(transformerFallback());
|
||||
}
|
||||
|
||||
return transformers;
|
||||
}
|
||||
|
||||
function createFlattenPathResolver(storage: ContentStorage) {
|
||||
const map = new Map<string, string>();
|
||||
const files = storage.getFiles();
|
||||
@@ -310,28 +379,31 @@ export function createPageTreeBuilder(getUrl: UrlFn): PageTreeBuilder {
|
||||
}
|
||||
|
||||
return {
|
||||
build(options) {
|
||||
const resolve = createFlattenPathResolver(options.storage);
|
||||
build({ storage, id, generateFallback, ...options }) {
|
||||
const resolve = createFlattenPathResolver(storage);
|
||||
|
||||
return build({
|
||||
return build(id ?? 'root', {
|
||||
transformers: getTransformers(options, generateFallback),
|
||||
options,
|
||||
builder: this,
|
||||
storage: options.storage,
|
||||
storage,
|
||||
getUrl,
|
||||
resolveName(name, format) {
|
||||
return resolve(name, format) ?? name;
|
||||
},
|
||||
});
|
||||
},
|
||||
buildI18n({ i18n, ...options }) {
|
||||
buildI18n({ id, i18n, ...options }) {
|
||||
const storage = options.storages[i18n.defaultLanguage];
|
||||
const resolve = createFlattenPathResolver(storage);
|
||||
const transformers = getTransformers(options);
|
||||
|
||||
const entries = i18n.languages.map<[string, PageTree.Root]>((lang) => {
|
||||
const tree = build({
|
||||
const tree = build(id ?? lang ?? 'root', {
|
||||
transformers,
|
||||
builder: this,
|
||||
options,
|
||||
getUrl,
|
||||
builder: this,
|
||||
locale: lang,
|
||||
storage,
|
||||
localeStorage: options.storages[lang],
|
||||
@@ -0,0 +1,74 @@
|
||||
import type {
|
||||
MetaData,
|
||||
MetaFile,
|
||||
PageData,
|
||||
PageFile,
|
||||
PageTreeTransformer,
|
||||
} from '@/source';
|
||||
import type * as PageTree from '@/server/page-tree';
|
||||
|
||||
export interface LegacyTransformerOptions<
|
||||
Page extends PageData,
|
||||
Meta extends MetaData,
|
||||
> {
|
||||
/**
|
||||
* @deprecated use `transformers` instead
|
||||
*/
|
||||
attachFile?: (node: PageTree.Item, file?: PageFile<Page>) => PageTree.Item;
|
||||
/**
|
||||
* @deprecated use `transformers` instead
|
||||
*/
|
||||
attachFolder?: (
|
||||
node: PageTree.Folder,
|
||||
folder: {
|
||||
children: (PageFile<Page> | MetaFile<Meta>)[];
|
||||
},
|
||||
meta?: MetaFile<Meta>,
|
||||
) => PageTree.Folder;
|
||||
/**
|
||||
* @deprecated use `transformers` instead
|
||||
*/
|
||||
attachSeparator?: (node: PageTree.Separator) => PageTree.Separator;
|
||||
}
|
||||
|
||||
export function legacyTransformer<Page extends PageData, Meta extends MetaData>(
|
||||
transformer: LegacyTransformerOptions<Page, Meta>,
|
||||
): PageTreeTransformer<Page, Meta> {
|
||||
return {
|
||||
file(node, file) {
|
||||
if (!transformer.attachFile) return node;
|
||||
const content = file
|
||||
? (this.localeStorage?.read(file) ?? this.storage.read(file))
|
||||
: undefined;
|
||||
|
||||
return transformer.attachFile(
|
||||
node,
|
||||
content?.format === 'page' ? content : undefined,
|
||||
);
|
||||
},
|
||||
folder(node, folderPath, metaPath) {
|
||||
if (!transformer.attachFolder) return node;
|
||||
|
||||
const files = this.storage.readDir(folderPath) ?? [];
|
||||
const meta = metaPath
|
||||
? (this.localeStorage?.read(metaPath) ?? this.storage.read(metaPath))
|
||||
: undefined;
|
||||
|
||||
return transformer.attachFolder(
|
||||
node,
|
||||
{
|
||||
children: files.flatMap(
|
||||
(file) =>
|
||||
this.localeStorage?.read(file) ?? this.storage.read(file) ?? [],
|
||||
),
|
||||
},
|
||||
meta?.format === 'meta' ? meta : undefined,
|
||||
);
|
||||
},
|
||||
separator(node) {
|
||||
if (!transformer.attachSeparator) return node;
|
||||
|
||||
return transformer.attachSeparator(node);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MethodLabel } from '@/ui/components/method-label';
|
||||
import type { PageTree } from 'fumadocs-core/server';
|
||||
import type { PageFile } from 'fumadocs-core/source';
|
||||
import type { PageFile, PageTreeTransformer } from 'fumadocs-core/source';
|
||||
|
||||
/**
|
||||
* Source API Integration
|
||||
@@ -39,3 +39,14 @@ export const attachFile = (
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
export function transformerOpenAPI(): PageTreeTransformer {
|
||||
return {
|
||||
file(node, file) {
|
||||
if (!file) return node;
|
||||
const content = this.storage.read(file);
|
||||
|
||||
return attachFile(node, content?.format === 'page' ? content : undefined);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,10 +24,13 @@ export function TreeContextProvider(props: {
|
||||
// using the id here to make sure this never happens
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const tree = useMemo(() => props.tree, [props.tree.$id ?? props.tree]);
|
||||
const path = useMemo(
|
||||
() => searchPath(tree.children, pathname) ?? [],
|
||||
[tree, pathname],
|
||||
);
|
||||
const path = useMemo(() => {
|
||||
let result = searchPath(tree.children, pathname);
|
||||
if (result) return result;
|
||||
|
||||
if (tree.fallback) result = searchPath(tree.fallback.children, pathname);
|
||||
return result ?? [];
|
||||
}, [tree, pathname]);
|
||||
|
||||
const root =
|
||||
path.findLast((item) => item.type === 'folder' && item.root) ?? tree;
|
||||
|
||||
Reference in New Issue
Block a user