mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
Core: Type file system utilities
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-core': patch
|
||||
---
|
||||
|
||||
Type file system utilities (Note: This is an internal module, you're not supposed to reference it)
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
export function ApiIndicator(): ReactNode {
|
||||
return (
|
||||
<span className="ms-auto rounded-full bg-fd-primary px-2 py-0.5 text-xs font-medium text-fd-primary-foreground">
|
||||
API
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
const buttonVariants = cva(
|
||||
'ring-offset-background inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-ring disabled:pointer-events-none disabled:opacity-50',
|
||||
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-fd-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-ring disabled:pointer-events-none disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
||||
@@ -5,6 +5,12 @@ import { loader } from 'fumadocs-core/source';
|
||||
import { icons } from 'lucide-react';
|
||||
import { map } from '@/.map';
|
||||
import { create } from '@/components/ui/icon';
|
||||
import { ApiIndicator } from '@/components/ui/api-indicator';
|
||||
|
||||
const frontmatter = defaultSchemas.frontmatter.extend({
|
||||
preview: z.string().optional(),
|
||||
index: z.boolean().default(false),
|
||||
});
|
||||
|
||||
export const utils = loader({
|
||||
baseUrl: '/docs',
|
||||
@@ -15,12 +21,19 @@ export const utils = loader({
|
||||
},
|
||||
source: createMDXSource(map, {
|
||||
schema: {
|
||||
frontmatter: defaultSchemas.frontmatter.extend({
|
||||
preview: z.string().optional(),
|
||||
index: z.boolean().default(false),
|
||||
}),
|
||||
frontmatter,
|
||||
},
|
||||
}),
|
||||
pageTree: {
|
||||
attachFile(node, file) {
|
||||
if (!file) return node;
|
||||
const data = file.data.data as z.output<typeof frontmatter>;
|
||||
if (!data.full) return node;
|
||||
|
||||
node.name = [node.name, ApiIndicator()];
|
||||
return node;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const blog = loader({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { MetaData, PageData } from '@/source/types';
|
||||
import {
|
||||
parseFilePath,
|
||||
parseFolderPath,
|
||||
@@ -5,12 +6,26 @@ import {
|
||||
type FileInfo,
|
||||
} from './path';
|
||||
|
||||
export interface File {
|
||||
export interface MetaFile {
|
||||
file: FileInfo;
|
||||
format: 'meta' | 'page';
|
||||
data: Record<string, unknown>;
|
||||
format: 'meta';
|
||||
data: {
|
||||
// TODO: Merge it into data (major)
|
||||
data: MetaData;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PageFile {
|
||||
file: FileInfo;
|
||||
format: 'page';
|
||||
data: {
|
||||
slugs: string[];
|
||||
data: PageData;
|
||||
};
|
||||
}
|
||||
|
||||
export type File = MetaFile | PageFile;
|
||||
|
||||
export interface Folder {
|
||||
file: FileInfo;
|
||||
children: (File | Folder)[];
|
||||
@@ -37,8 +52,11 @@ export class Storage {
|
||||
* @param path - flattened path
|
||||
* @param format - file format
|
||||
*/
|
||||
read(path: string, format: string): File | undefined {
|
||||
return this.files.get(`${path}.${format}`);
|
||||
read<F extends File['format']>(
|
||||
path: string,
|
||||
format: F,
|
||||
): Extract<File, { format: F }> | undefined {
|
||||
return this.files.get(`${path}.${format}`) as Extract<File, { format: F }>;
|
||||
}
|
||||
|
||||
readDir(path: string): Folder | undefined {
|
||||
@@ -49,16 +67,16 @@ export class Storage {
|
||||
return this.rootFolder;
|
||||
}
|
||||
|
||||
write(
|
||||
write<F extends File['format']>(
|
||||
path: string,
|
||||
format: 'meta' | 'page',
|
||||
data: Record<string, unknown>,
|
||||
format: F,
|
||||
data: Extract<File, { format: F }>['data'],
|
||||
): void {
|
||||
const node: File = {
|
||||
const node = {
|
||||
format,
|
||||
file: parseFilePath(path),
|
||||
data,
|
||||
};
|
||||
} as File;
|
||||
|
||||
this.makeDir(node.file.dirname);
|
||||
this.readDir(node.file.dirname)?.children.push(node);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type FileData } from '@/source/types';
|
||||
import { type MetaData, type PageData } from '@/source/types';
|
||||
import { parseFilePath, type FileInfo, normalizePath } from './path';
|
||||
import { Storage } from './file-system';
|
||||
|
||||
@@ -42,13 +42,13 @@ export function loadFiles(files: VirtualFile[], options: LoadOptions): Storage {
|
||||
|
||||
storage.write(relativePath, file.type, {
|
||||
slugs,
|
||||
data: file.data,
|
||||
} as FileData['file']);
|
||||
data: file.data as PageData,
|
||||
});
|
||||
}
|
||||
|
||||
if (file.type === 'meta') {
|
||||
storage.write(relativePath, file.type, {
|
||||
data: file.data,
|
||||
data: file.data as MetaData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ import {
|
||||
type VirtualFile,
|
||||
type Transformer,
|
||||
} from './load-files';
|
||||
import type { FileData, MetaData, PageData, UrlFn } from './types';
|
||||
import type { MetaData, PageData, UrlFn } from './types';
|
||||
import type {
|
||||
BuildPageTreeOptions,
|
||||
BuildPageTreeOptionsWithI18n,
|
||||
} from './page-tree-builder';
|
||||
import { createPageTreeBuilder } from './page-tree-builder';
|
||||
import { type FileInfo } from './path';
|
||||
import type { File, Storage } from './file-system';
|
||||
import type { File, PageFile, Storage } from './file-system';
|
||||
|
||||
export interface LoaderConfig {
|
||||
source: SourceConfig;
|
||||
@@ -225,16 +225,14 @@ function createOutput({
|
||||
}
|
||||
|
||||
function fileToPage<Data = PageData>(
|
||||
file: File,
|
||||
file: PageFile,
|
||||
getUrl: UrlFn,
|
||||
locale?: string,
|
||||
): Page<Data> {
|
||||
const data = file.data as FileData['file'];
|
||||
|
||||
return {
|
||||
file: file.file,
|
||||
url: getUrl(data.slugs, locale),
|
||||
slugs: data.slugs,
|
||||
data: data.data as Data,
|
||||
url: getUrl(file.data.slugs, locale),
|
||||
slugs: file.data.slugs,
|
||||
data: file.data.data as Data,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { ReactElement } from 'react';
|
||||
import type * as PageTree from '../server/page-tree';
|
||||
import type { File, Folder, Storage } from './file-system';
|
||||
import type { File, Folder, MetaFile, PageFile, Storage } from './file-system';
|
||||
import { resolvePath } from './path';
|
||||
import { type FileData, type UrlFn } from './types';
|
||||
import { type UrlFn } from './types';
|
||||
|
||||
interface PageTreeBuilderContext {
|
||||
lang?: string;
|
||||
@@ -20,11 +20,12 @@ export interface BuildPageTreeOptions {
|
||||
*/
|
||||
attachFolderIds?: boolean;
|
||||
|
||||
attachFile?: (node: PageTree.Item, file?: File) => PageTree.Item;
|
||||
// TODO: Rename to `attachPage` (major)
|
||||
attachFile?: (node: PageTree.Item, file?: PageFile) => PageTree.Item;
|
||||
attachFolder?: (
|
||||
node: PageTree.Folder,
|
||||
folder: Folder,
|
||||
meta?: File,
|
||||
meta?: MetaFile,
|
||||
) => PageTree.Folder;
|
||||
attachSeparator?: (node: PageTree.Separator) => PageTree.Separator;
|
||||
|
||||
@@ -170,7 +171,7 @@ function buildFolderNode(
|
||||
'page',
|
||||
);
|
||||
|
||||
const metadata = meta?.data.data as FileData['meta']['data'] | undefined;
|
||||
const metadata = meta?.data.data;
|
||||
const index = indexFile ? buildFileNode(indexFile, ctx) : undefined;
|
||||
|
||||
let children: PageTree.Node[];
|
||||
@@ -221,16 +222,18 @@ function buildFolderNode(
|
||||
);
|
||||
}
|
||||
|
||||
function buildFileNode(file: File, ctx: PageTreeBuilderContext): PageTree.Item {
|
||||
function buildFileNode(
|
||||
file: PageFile,
|
||||
ctx: PageTreeBuilderContext,
|
||||
): PageTree.Item {
|
||||
const localized =
|
||||
findLocalizedFile(file.file.flattenedPath, 'page', ctx) ?? file;
|
||||
const data = localized.data as FileData['file'];
|
||||
|
||||
const item: PageTree.Item = {
|
||||
type: 'page',
|
||||
name: data.data.title,
|
||||
icon: ctx.options.resolveIcon?.(data.data.icon),
|
||||
url: ctx.options.getUrl(data.slugs, ctx.lang),
|
||||
name: localized.data.data.title,
|
||||
icon: ctx.options.resolveIcon?.(localized.data.data.icon),
|
||||
url: ctx.options.getUrl(localized.data.slugs, ctx.lang),
|
||||
};
|
||||
|
||||
return removeUndefined(ctx.options.attachFile?.(item, file) ?? item);
|
||||
@@ -272,11 +275,11 @@ export function createPageTreeBuilder(): PageTreeBuilder {
|
||||
};
|
||||
}
|
||||
|
||||
function findLocalizedFile(
|
||||
function findLocalizedFile<F extends File['format']>(
|
||||
path: string,
|
||||
format: 'meta' | 'page',
|
||||
format: F,
|
||||
ctx: PageTreeBuilderContext,
|
||||
): File | undefined {
|
||||
): Extract<File, { format: F }> | undefined {
|
||||
if (!ctx.lang) return;
|
||||
|
||||
return ctx.storage.read(`${path}.${ctx.lang}`, format);
|
||||
|
||||
@@ -29,16 +29,3 @@ export type InferMetaType<Utils extends LoaderOutput<any>> =
|
||||
* @internal
|
||||
*/
|
||||
export type UrlFn = (slugs: string[], locale?: string) => string;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface FileData {
|
||||
meta: {
|
||||
data: MetaData;
|
||||
};
|
||||
file: {
|
||||
slugs: string[];
|
||||
data: PageData;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,8 +7,15 @@ describe('Virtual Storage', () => {
|
||||
const storage = new Storage();
|
||||
|
||||
test('Writing', () => {
|
||||
storage.write('test.mdx', 'page', {});
|
||||
storage.write('meta.json', 'meta', {});
|
||||
storage.write('test.mdx', 'page', {
|
||||
slugs: [],
|
||||
data: {
|
||||
title: 'Hello',
|
||||
},
|
||||
});
|
||||
storage.write('meta.json', 'meta', {
|
||||
data: {},
|
||||
});
|
||||
|
||||
expect(storage.list().length).toBe(2);
|
||||
});
|
||||
@@ -20,7 +27,7 @@ describe('Virtual Storage', () => {
|
||||
|
||||
test('Nested Directories', () => {
|
||||
storage.write('dir1/dir2/meta.json', 'meta', {
|
||||
data: 'Hello',
|
||||
data: {},
|
||||
});
|
||||
|
||||
expect(storage.readDir('dir1')).toBeDefined();
|
||||
|
||||
Reference in New Issue
Block a user