mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
Core: refactor internal type parameters
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'fumadocs-core': minor
|
||||
---
|
||||
|
||||
**[Loader API] Refactor internal type parameters**
|
||||
|
||||
Internal types like `ContentStorage`, `PageTreeTransformer` now use a single `Config extends SourceConfig` generic parameter.
|
||||
|
||||
It makes extending their parameters easier, this should not affect normal usages.
|
||||
@@ -1,19 +1,9 @@
|
||||
export {
|
||||
createPageTreeBuilder,
|
||||
type PageTreeTransformer,
|
||||
type PageTreeBuilder,
|
||||
type PageTreeBuilderContext,
|
||||
type PageTreeOptions,
|
||||
} from './page-tree/builder';
|
||||
export * from './source';
|
||||
export * from './loader';
|
||||
export { getSlugs } from './plugins/slugs';
|
||||
export { FileSystem } from './storage/file-system';
|
||||
|
||||
export {
|
||||
type PageFile,
|
||||
type MetaFile,
|
||||
type ContentStorage,
|
||||
} from './storage/content';
|
||||
export type * from './types';
|
||||
export * as PathUtils from './path';
|
||||
|
||||
export type * from './page-tree/builder';
|
||||
export type * from './storage/content';
|
||||
export type * from './plugins';
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
import type * as PageTree from '@/page-tree/definitions';
|
||||
import type { I18nConfig } from '@/i18n';
|
||||
import {
|
||||
buildContentStorage,
|
||||
type ContentStorage,
|
||||
type MetaFile,
|
||||
type PageFile,
|
||||
} from './storage/content';
|
||||
import type {
|
||||
MetaData,
|
||||
PageData,
|
||||
UrlFn,
|
||||
VirtualFile,
|
||||
VirtualMeta,
|
||||
VirtualPage,
|
||||
} from './types';
|
||||
import { buildContentStorage, type ContentStorage } from './storage/content';
|
||||
import {
|
||||
createPageTreeBuilder,
|
||||
type PageTreeOptions,
|
||||
@@ -27,6 +14,7 @@ import {
|
||||
} from '@/source/plugins';
|
||||
import { slugsPlugin } from '@/source/plugins/slugs';
|
||||
import { iconPlugin, type IconResolver } from '@/source/plugins/icon';
|
||||
import type { Source, SourceConfig, PageData, MetaData } from './source';
|
||||
|
||||
export interface LoaderConfig {
|
||||
source: SourceConfig;
|
||||
@@ -36,7 +24,7 @@ export interface LoaderConfig {
|
||||
export interface LoaderOptions<C extends LoaderConfig = LoaderConfig> {
|
||||
baseUrl: string;
|
||||
i18n?: C['i18n'];
|
||||
url?: UrlFn;
|
||||
url?: (slugs: string[], locale?: string) => string;
|
||||
|
||||
/**
|
||||
* Additional options for page tree builder
|
||||
@@ -54,22 +42,13 @@ export interface LoaderOptions<C extends LoaderConfig = LoaderConfig> {
|
||||
|
||||
export interface ResolvedLoaderConfig {
|
||||
source: Source;
|
||||
url: UrlFn;
|
||||
url: (slugs: string[], locale?: string) => string;
|
||||
|
||||
plugins?: LoaderPlugin[];
|
||||
pageTree?: PageTreeOptions;
|
||||
i18n?: I18nConfig | undefined;
|
||||
}
|
||||
|
||||
export interface Source<Config extends SourceConfig = SourceConfig> {
|
||||
files: VirtualFile<Config>[];
|
||||
}
|
||||
|
||||
export interface SourceConfig {
|
||||
pageData: PageData;
|
||||
metaData: MetaData;
|
||||
}
|
||||
|
||||
interface SharedFileInfo {
|
||||
/**
|
||||
* Virtualized file path (relative to content directory)
|
||||
@@ -79,9 +58,9 @@ interface SharedFileInfo {
|
||||
path: string;
|
||||
|
||||
/**
|
||||
* Absolute path of the file (can be empty)
|
||||
* Absolute path of the file
|
||||
*/
|
||||
absolutePath: string;
|
||||
absolutePath?: string;
|
||||
}
|
||||
|
||||
export interface Page<Data = PageData> extends SharedFileInfo {
|
||||
@@ -174,7 +153,10 @@ export interface LoaderOutput<Config extends LoaderConfig> {
|
||||
) => (Record<TSlug, string[]> & Record<TLang, string>)[];
|
||||
}
|
||||
|
||||
function indexPages(storages: Record<string, ContentStorage>, getUrl: UrlFn) {
|
||||
function indexPages(
|
||||
storages: Record<string, ContentStorage>,
|
||||
{ url }: ResolvedLoaderConfig,
|
||||
) {
|
||||
const result = {
|
||||
// (locale.slugs -> page)
|
||||
pages: new Map<string, Page>(),
|
||||
@@ -190,11 +172,22 @@ function indexPages(storages: Record<string, ContentStorage>, getUrl: UrlFn) {
|
||||
const path = `${lang}.${filePath}`;
|
||||
|
||||
if (item.format === 'meta') {
|
||||
result.pathToMeta.set(path, fileToMeta(item));
|
||||
result.pathToMeta.set(path, {
|
||||
path: item.path,
|
||||
absolutePath: item.absolutePath,
|
||||
data: item.data,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const page = fileToPage(item, getUrl, lang);
|
||||
const page: Page = {
|
||||
absolutePath: item.absolutePath,
|
||||
path: item.path,
|
||||
url: url(item.slugs, lang),
|
||||
slugs: item.slugs,
|
||||
data: item.data,
|
||||
locale: lang,
|
||||
};
|
||||
result.pathToPage.set(path, page);
|
||||
result.pages.set(`${lang}.${page.slugs.join('/')}`, page);
|
||||
}
|
||||
@@ -203,7 +196,10 @@ function indexPages(storages: Record<string, ContentStorage>, getUrl: UrlFn) {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createGetUrl(baseUrl: string, i18n?: I18nConfig): UrlFn {
|
||||
export function createGetUrl(
|
||||
baseUrl: string,
|
||||
i18n?: I18nConfig,
|
||||
): ResolvedLoaderConfig['url'] {
|
||||
const baseSlugs = baseUrl.split('/');
|
||||
|
||||
return (slugs, locale) => {
|
||||
@@ -276,13 +272,11 @@ function resolveConfig(
|
||||
source: Source,
|
||||
{ slugs, icon, plugins = [], baseUrl, url, ...base }: LoaderOptions,
|
||||
): ResolvedLoaderConfig {
|
||||
const getUrl: UrlFn = url
|
||||
? (...args) => normalizeUrl(url(...args))
|
||||
: createGetUrl(baseUrl, base.i18n);
|
||||
|
||||
let config: ResolvedLoaderConfig = {
|
||||
...base,
|
||||
url: getUrl,
|
||||
url: url
|
||||
? (...args) => normalizeUrl(url(...args))
|
||||
: createGetUrl(baseUrl, base.i18n),
|
||||
source,
|
||||
plugins: buildPlugins([
|
||||
slugsPlugin(slugs),
|
||||
@@ -303,51 +297,20 @@ function resolveConfig(
|
||||
return config;
|
||||
}
|
||||
|
||||
function createOutput({
|
||||
source: { files },
|
||||
url: getUrl,
|
||||
i18n,
|
||||
plugins = [],
|
||||
pageTree: pageTreeConfig,
|
||||
}: ResolvedLoaderConfig): LoaderOutput<LoaderConfig> {
|
||||
function createOutput(
|
||||
loaderConfig: ResolvedLoaderConfig,
|
||||
): LoaderOutput<LoaderConfig> {
|
||||
const { i18n } = loaderConfig;
|
||||
const defaultLanguage = i18n?.defaultLanguage ?? '';
|
||||
|
||||
const storages = buildContentStorage(
|
||||
files,
|
||||
(file) => {
|
||||
if (file.type === 'page') {
|
||||
return {
|
||||
format: 'page',
|
||||
path: file.path,
|
||||
slugs: file.slugs,
|
||||
data: file.data,
|
||||
absolutePath: file.absolutePath ?? '',
|
||||
} as PageFile;
|
||||
}
|
||||
|
||||
return {
|
||||
format: 'meta',
|
||||
path: file.path,
|
||||
absolutePath: file.absolutePath ?? '',
|
||||
data: file.data,
|
||||
} as MetaFile;
|
||||
},
|
||||
plugins,
|
||||
i18n ?? {
|
||||
defaultLanguage,
|
||||
parser: 'none',
|
||||
languages: [defaultLanguage],
|
||||
},
|
||||
);
|
||||
|
||||
const walker = indexPages(storages, getUrl);
|
||||
const builder = createPageTreeBuilder(getUrl, plugins);
|
||||
const storages = buildContentStorage(loaderConfig, defaultLanguage);
|
||||
const walker = indexPages(storages, loaderConfig);
|
||||
const builder = createPageTreeBuilder(loaderConfig);
|
||||
let pageTree: Record<string, PageTree.Root> | undefined;
|
||||
|
||||
return {
|
||||
_i18n: i18n,
|
||||
get pageTree() {
|
||||
pageTree ??= builder.buildI18n(storages, pageTreeConfig);
|
||||
pageTree ??= builder.buildI18n(storages);
|
||||
|
||||
return i18n
|
||||
? (pageTree as unknown as LoaderOutput<LoaderConfig>['pageTree'])
|
||||
@@ -452,84 +415,14 @@ function createOutput({
|
||||
};
|
||||
}
|
||||
|
||||
function fileToMeta<Data = MetaData>(file: MetaFile): Meta<Data> {
|
||||
return {
|
||||
path: file.path,
|
||||
absolutePath: file.absolutePath,
|
||||
data: file.data as Data,
|
||||
};
|
||||
}
|
||||
|
||||
function fileToPage<Data = PageData>(
|
||||
file: PageFile,
|
||||
getUrl: UrlFn,
|
||||
locale?: string,
|
||||
): Page<Data> {
|
||||
return {
|
||||
absolutePath: file.absolutePath,
|
||||
path: file.path,
|
||||
url: getUrl(file.slugs, locale),
|
||||
slugs: file.slugs,
|
||||
data: file.data as Data,
|
||||
locale,
|
||||
};
|
||||
}
|
||||
|
||||
export type _ConfigUnion_<T extends Record<string, Source>> = {
|
||||
[K in keyof T]: T[K] extends Source<infer Config>
|
||||
? {
|
||||
pageData: Config['pageData'] & { type: K };
|
||||
metaData: Config['metaData'] & { type: K };
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- infer types
|
||||
export type InferPageType<Utils extends LoaderOutput<any>> =
|
||||
Utils extends LoaderOutput<infer Config>
|
||||
? Page<Config['source']['pageData']>
|
||||
: never;
|
||||
}[keyof T];
|
||||
|
||||
export function multiple<T extends Record<string, Source>>(sources: T) {
|
||||
const out: Source<_ConfigUnion_<T>> = { files: [] };
|
||||
|
||||
for (const [type, source] of Object.entries(sources)) {
|
||||
for (const file of source.files) {
|
||||
out.files.push({
|
||||
...file,
|
||||
data: {
|
||||
...file.data,
|
||||
type,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* map virtual files in source
|
||||
*/
|
||||
export function map<Config extends SourceConfig>(source: Source<Config>) {
|
||||
return {
|
||||
page<$Page extends PageData>(
|
||||
fn: (entry: VirtualPage<Config['pageData']>) => VirtualPage<$Page>,
|
||||
): Source<{
|
||||
pageData: $Page;
|
||||
metaData: Config['metaData'];
|
||||
}> {
|
||||
return {
|
||||
files: source.files.map((file) =>
|
||||
file.type === 'page' ? fn(file) : file,
|
||||
),
|
||||
};
|
||||
},
|
||||
meta<$Meta extends MetaData>(
|
||||
fn: (entry: VirtualMeta<Config['metaData']>) => VirtualMeta<$Meta>,
|
||||
): Source<{
|
||||
pageData: Config['pageData'];
|
||||
metaData: $Meta;
|
||||
}> {
|
||||
return {
|
||||
files: source.files.map((file) =>
|
||||
file.type === 'meta' ? fn(file) : file,
|
||||
),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- infer types
|
||||
export type InferMetaType<Utils extends LoaderOutput<any>> =
|
||||
Utils extends LoaderOutput<infer Config>
|
||||
? Meta<Config['source']['metaData']>
|
||||
: never;
|
||||
|
||||
@@ -1,48 +1,46 @@
|
||||
import type * as PageTree from '@/page-tree/definitions';
|
||||
import type { LoaderConfig, MetaData, PageData, UrlFn } from '@/source';
|
||||
import type { LoaderConfig, ResolvedLoaderConfig } from '@/source/loader';
|
||||
import type { ContentStorage } from '@/source/storage/content';
|
||||
import { basename, extname, joinPath } from '@/source/path';
|
||||
import { transformerFallback } from '@/source/page-tree/transformer-fallback';
|
||||
import type { LoaderPlugin } from '@/source/plugins';
|
||||
import type { SourceConfig } from '../source';
|
||||
|
||||
export interface PageTreeBuilderContext<
|
||||
Page extends PageData = PageData,
|
||||
Meta extends MetaData = MetaData,
|
||||
Config extends SourceConfig = SourceConfig,
|
||||
> {
|
||||
rootId: string;
|
||||
generateNodeId: () => string;
|
||||
options: PageTreeOptions;
|
||||
transformers: PageTreeTransformer<Page, Meta>[];
|
||||
transformers: PageTreeTransformer<Config>[];
|
||||
|
||||
builder: PageTreeBuilder;
|
||||
storage: ContentStorage<Page, Meta>;
|
||||
getUrl: UrlFn;
|
||||
storage: ContentStorage<Config>;
|
||||
getUrl: ResolvedLoaderConfig['url'];
|
||||
|
||||
storages?: Record<string, ContentStorage<Page, Meta>>;
|
||||
storages?: Record<string, ContentStorage<Config>>;
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
export interface PageTreeTransformer<
|
||||
Page extends PageData = PageData,
|
||||
Meta extends MetaData = MetaData,
|
||||
Config extends SourceConfig = SourceConfig,
|
||||
> {
|
||||
file?: (
|
||||
this: PageTreeBuilderContext<Page, Meta>,
|
||||
this: PageTreeBuilderContext<Config>,
|
||||
node: PageTree.Item,
|
||||
filePath?: string,
|
||||
) => PageTree.Item;
|
||||
folder?: (
|
||||
this: PageTreeBuilderContext<Page, Meta>,
|
||||
this: PageTreeBuilderContext<Config>,
|
||||
node: PageTree.Folder,
|
||||
folderPath: string,
|
||||
metaPath?: string,
|
||||
) => PageTree.Folder;
|
||||
separator?: (
|
||||
this: PageTreeBuilderContext<Page, Meta>,
|
||||
this: PageTreeBuilderContext<Config>,
|
||||
node: PageTree.Separator,
|
||||
) => PageTree.Separator;
|
||||
root?: (
|
||||
this: PageTreeBuilderContext<Page, Meta>,
|
||||
this: PageTreeBuilderContext<Config>,
|
||||
node: PageTree.Root,
|
||||
) => PageTree.Root;
|
||||
}
|
||||
@@ -65,10 +63,7 @@ export interface PageTreeOptions<Config extends LoaderConfig = LoaderConfig> {
|
||||
/**
|
||||
* Additional page tree transformers to apply
|
||||
*/
|
||||
transformers?: PageTreeTransformer<
|
||||
Config['source']['pageData'],
|
||||
Config['source']['metaData']
|
||||
>[];
|
||||
transformers?: PageTreeTransformer<Config['source']>[];
|
||||
}
|
||||
|
||||
export interface PageTreeBuilder {
|
||||
@@ -90,15 +85,16 @@ const extractPrefix = '...';
|
||||
const excludePrefix = '!';
|
||||
|
||||
export function createPageTreeBuilder(
|
||||
getUrl: UrlFn,
|
||||
plugins?: LoaderPlugin[],
|
||||
loaderConfig: ResolvedLoaderConfig,
|
||||
): PageTreeBuilder {
|
||||
const { plugins = [], url, pageTree: defaultOptions = {} } = loaderConfig;
|
||||
|
||||
return {
|
||||
build(storage, options) {
|
||||
build(storage, options = defaultOptions) {
|
||||
const key = '';
|
||||
return this.buildI18n({ [key]: storage }, options)[key];
|
||||
},
|
||||
buildI18n(storages, options = {}) {
|
||||
buildI18n(storages, options = defaultOptions) {
|
||||
let nextId = 0;
|
||||
const out: Record<string, PageTree.Root> = {};
|
||||
const transformers: PageTreeTransformer[] = [];
|
||||
@@ -107,7 +103,7 @@ export function createPageTreeBuilder(
|
||||
transformers.push(...options.transformers);
|
||||
}
|
||||
|
||||
for (const plugin of plugins ?? []) {
|
||||
for (const plugin of plugins) {
|
||||
if (plugin.transformPageTree)
|
||||
transformers.push(plugin.transformPageTree);
|
||||
}
|
||||
@@ -125,7 +121,7 @@ export function createPageTreeBuilder(
|
||||
transformers,
|
||||
builder: this,
|
||||
options,
|
||||
getUrl,
|
||||
getUrl: url,
|
||||
locale,
|
||||
storage,
|
||||
storages,
|
||||
|
||||
@@ -23,19 +23,13 @@ export interface LoaderPlugin<Config extends LoaderConfig = LoaderConfig> {
|
||||
* transform the storage after loading
|
||||
*/
|
||||
transformStorage?: (context: {
|
||||
storage: ContentStorage<
|
||||
Config['source']['pageData'],
|
||||
Config['source']['metaData']
|
||||
>;
|
||||
storage: ContentStorage<Config['source']>;
|
||||
}) => void;
|
||||
|
||||
/**
|
||||
* transform the generated page tree
|
||||
*/
|
||||
transformPageTree?: PageTreeTransformer<
|
||||
Config['source']['pageData'],
|
||||
Config['source']['metaData']
|
||||
>;
|
||||
transformPageTree?: PageTreeTransformer<Config['source']>;
|
||||
}
|
||||
|
||||
export type LoaderPluginOption<Config extends LoaderConfig = LoaderConfig> =
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
export interface Source<Config extends SourceConfig = SourceConfig> {
|
||||
files: VirtualFile<Config>[];
|
||||
}
|
||||
|
||||
export interface SourceConfig {
|
||||
pageData: PageData;
|
||||
metaData: MetaData;
|
||||
}
|
||||
|
||||
export interface MetaData {
|
||||
icon?: string | undefined;
|
||||
title?: string | undefined;
|
||||
root?: boolean | undefined;
|
||||
pages?: string[] | undefined;
|
||||
defaultOpen?: boolean | undefined;
|
||||
|
||||
description?: string | undefined;
|
||||
}
|
||||
|
||||
export interface PageData {
|
||||
icon?: string | undefined;
|
||||
title?: string;
|
||||
description?: string | undefined;
|
||||
}
|
||||
|
||||
export type VirtualFile<Config extends SourceConfig = SourceConfig> =
|
||||
| VirtualPage<Config['pageData']>
|
||||
| VirtualMeta<Config['metaData']>;
|
||||
|
||||
interface BaseVirtualFile {
|
||||
/**
|
||||
* Virtualized path (relative to content directory)
|
||||
*
|
||||
* @example `docs/page.mdx`
|
||||
*/
|
||||
path: string;
|
||||
|
||||
/**
|
||||
* Absolute path of the file
|
||||
*/
|
||||
absolutePath?: string;
|
||||
}
|
||||
|
||||
interface VirtualPage<Data extends PageData> extends BaseVirtualFile {
|
||||
type: 'page';
|
||||
/**
|
||||
* Specified Slugs for page
|
||||
*/
|
||||
slugs?: string[];
|
||||
data: Data;
|
||||
}
|
||||
|
||||
interface VirtualMeta<Data extends MetaData> extends BaseVirtualFile {
|
||||
type: 'meta';
|
||||
data: Data;
|
||||
}
|
||||
|
||||
export type _ConfigUnion_<T extends Record<string, Source>> = {
|
||||
[K in keyof T]: T[K] extends Source<infer Config>
|
||||
? {
|
||||
pageData: Config['pageData'] & { type: K };
|
||||
metaData: Config['metaData'] & { type: K };
|
||||
}
|
||||
: never;
|
||||
}[keyof T];
|
||||
|
||||
export function multiple<T extends Record<string, Source>>(sources: T) {
|
||||
const out: Source<_ConfigUnion_<T>> = { files: [] };
|
||||
|
||||
for (const [type, source] of Object.entries(sources)) {
|
||||
for (const file of source.files) {
|
||||
out.files.push({
|
||||
...file,
|
||||
data: {
|
||||
...file.data,
|
||||
type,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* map virtual files in source
|
||||
*/
|
||||
export function map<Config extends SourceConfig>(source: Source<Config>) {
|
||||
return {
|
||||
page<$Page extends PageData>(
|
||||
fn: (entry: VirtualPage<Config['pageData']>) => VirtualPage<$Page>,
|
||||
): Source<{
|
||||
pageData: $Page;
|
||||
metaData: Config['metaData'];
|
||||
}> {
|
||||
return {
|
||||
files: source.files.map((file) =>
|
||||
file.type === 'page' ? fn(file) : file,
|
||||
),
|
||||
};
|
||||
},
|
||||
meta<$Meta extends MetaData>(
|
||||
fn: (entry: VirtualMeta<Config['metaData']>) => VirtualMeta<$Meta>,
|
||||
): Source<{
|
||||
pageData: Config['pageData'];
|
||||
metaData: $Meta;
|
||||
}> {
|
||||
return {
|
||||
files: source.files.map((file) =>
|
||||
file.type === 'meta' ? fn(file) : file,
|
||||
),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,30 +1,27 @@
|
||||
import type { MetaData, PageData, VirtualFile } from '@/source/types';
|
||||
import { FileSystem } from '@/source/storage/file-system';
|
||||
import { basename, dirname, joinPath, slash, splitPath } from '@/source/path';
|
||||
import type { I18nConfig } from '@/i18n';
|
||||
import type { LoaderPlugin } from '@/source/plugins';
|
||||
import type { ResolvedLoaderConfig } from '../loader';
|
||||
import type { SourceConfig } from '../source';
|
||||
|
||||
export type ContentStorage<
|
||||
Page extends PageData = PageData,
|
||||
Meta extends MetaData = MetaData,
|
||||
> = FileSystem<MetaFile<Meta> | PageFile<Page>>;
|
||||
export type ContentStorage<Config extends SourceConfig = SourceConfig> =
|
||||
FileSystem<ContentStorageFile<Config>>;
|
||||
|
||||
export interface MetaFile<Data extends MetaData = MetaData> {
|
||||
path: string;
|
||||
absolutePath: string;
|
||||
export type ContentStorageFile<Config extends SourceConfig = SourceConfig> =
|
||||
| {
|
||||
path: string;
|
||||
absolutePath?: string;
|
||||
|
||||
format: 'meta';
|
||||
data: Data;
|
||||
}
|
||||
format: 'meta';
|
||||
data: Config['metaData'];
|
||||
}
|
||||
| {
|
||||
path: string;
|
||||
absolutePath?: string;
|
||||
|
||||
export interface PageFile<Data extends PageData = PageData> {
|
||||
path: string;
|
||||
absolutePath: string;
|
||||
|
||||
format: 'page';
|
||||
slugs: string[];
|
||||
data: Data;
|
||||
}
|
||||
format: 'page';
|
||||
slugs: string[];
|
||||
data: Config['pageData'];
|
||||
};
|
||||
|
||||
function isLocaleValid(locale: string) {
|
||||
return locale.length > 0 && !/\d+/.test(locale);
|
||||
@@ -56,24 +53,66 @@ const parsers = {
|
||||
};
|
||||
|
||||
/**
|
||||
* @param defaultLanguage - language to use when i18n is not configured.
|
||||
* @returns a map of locale and its content storage.
|
||||
*
|
||||
* in the storage, locale codes are removed from file paths, hence the same file will have same file paths in every storage.
|
||||
*/
|
||||
export function buildContentStorage(
|
||||
files: VirtualFile[],
|
||||
buildFile: (file: VirtualFile) => MetaFile | PageFile,
|
||||
plugins: LoaderPlugin[],
|
||||
i18n: I18nConfig,
|
||||
loaderConfig: ResolvedLoaderConfig,
|
||||
defaultLanguage: string,
|
||||
): Record<string, ContentStorage> {
|
||||
const {
|
||||
source,
|
||||
plugins = [],
|
||||
i18n = {
|
||||
defaultLanguage,
|
||||
parser: 'none',
|
||||
languages: [defaultLanguage],
|
||||
},
|
||||
} = loaderConfig;
|
||||
|
||||
const parser = parsers[i18n.parser ?? 'dot'];
|
||||
const storages: Record<string, ContentStorage> = {};
|
||||
const normalized = files.map((file) =>
|
||||
buildFile({
|
||||
...file,
|
||||
path: normalizePath(file.path),
|
||||
}),
|
||||
);
|
||||
const normalized = new Map<
|
||||
string,
|
||||
{
|
||||
pathWithoutLocale: string;
|
||||
file: ContentStorageFile;
|
||||
}[]
|
||||
>();
|
||||
|
||||
for (const inputFile of source.files) {
|
||||
let file: ContentStorageFile;
|
||||
if (inputFile.type === 'page') {
|
||||
file = {
|
||||
format: 'page',
|
||||
path: normalizePath(inputFile.path),
|
||||
// will be generated by the slugs plugin if unspecified
|
||||
slugs: inputFile.slugs as string[],
|
||||
data: inputFile.data,
|
||||
absolutePath: inputFile.absolutePath,
|
||||
};
|
||||
} else {
|
||||
file = {
|
||||
format: 'meta',
|
||||
path: normalizePath(inputFile.path),
|
||||
absolutePath: inputFile.absolutePath,
|
||||
data: inputFile.data,
|
||||
};
|
||||
}
|
||||
|
||||
const [pathWithoutLocale, locale = i18n.defaultLanguage] = parser(
|
||||
file.path,
|
||||
);
|
||||
const list = normalized.get(locale) ?? [];
|
||||
list.push({
|
||||
pathWithoutLocale,
|
||||
file,
|
||||
});
|
||||
normalized.set(locale, list);
|
||||
}
|
||||
|
||||
const fallbackLang =
|
||||
i18n.fallbackLanguage !== null
|
||||
? (i18n.fallbackLanguage ?? i18n.defaultLanguage)
|
||||
@@ -90,16 +129,13 @@ export function buildContentStorage(
|
||||
storage = new FileSystem();
|
||||
}
|
||||
|
||||
for (const item of normalized) {
|
||||
const [path, locale = i18n.defaultLanguage] = parser(item.path);
|
||||
|
||||
if (locale === lang) storage.write(path, item);
|
||||
for (const { pathWithoutLocale, file } of normalized.get(lang) ?? []) {
|
||||
storage.write(pathWithoutLocale, file);
|
||||
}
|
||||
|
||||
const context = {
|
||||
storage,
|
||||
};
|
||||
|
||||
for (const plugin of plugins) {
|
||||
plugin.transformStorage?.(context);
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import type { LoaderOutput, Meta, Page, SourceConfig } from './loader';
|
||||
|
||||
export interface MetaData {
|
||||
icon?: string | undefined;
|
||||
title?: string | undefined;
|
||||
root?: boolean | undefined;
|
||||
pages?: string[] | undefined;
|
||||
defaultOpen?: boolean | undefined;
|
||||
|
||||
description?: string | undefined;
|
||||
}
|
||||
|
||||
export interface PageData {
|
||||
icon?: string | undefined;
|
||||
title?: string;
|
||||
description?: string | undefined;
|
||||
}
|
||||
|
||||
interface BaseVirtualFile {
|
||||
/**
|
||||
* Virtualized path (relative to content directory)
|
||||
*
|
||||
* @example `docs/page.mdx`
|
||||
*/
|
||||
path: string;
|
||||
|
||||
/**
|
||||
* Absolute path of the file
|
||||
*/
|
||||
absolutePath?: string;
|
||||
}
|
||||
|
||||
export interface VirtualPage<Data extends PageData> extends BaseVirtualFile {
|
||||
type: 'page';
|
||||
/**
|
||||
* Specified Slugs for page
|
||||
*/
|
||||
slugs?: string[];
|
||||
data: Data;
|
||||
}
|
||||
|
||||
export interface VirtualMeta<Data extends MetaData> extends BaseVirtualFile {
|
||||
type: 'meta';
|
||||
data: Data;
|
||||
}
|
||||
|
||||
export type VirtualFile<Config extends SourceConfig = SourceConfig> =
|
||||
| VirtualPage<Config['pageData']>
|
||||
| VirtualMeta<Config['metaData']>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- infer types
|
||||
export type InferPageType<Utils extends LoaderOutput<any>> =
|
||||
Utils extends LoaderOutput<infer Config>
|
||||
? Page<Config['source']['pageData']>
|
||||
: never;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- infer types
|
||||
export type InferMetaType<Utils extends LoaderOutput<any>> =
|
||||
Utils extends LoaderOutput<infer Config>
|
||||
? Meta<Config['source']['metaData']>
|
||||
: never;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type UrlFn = (slugs: string[], locale?: string) => string;
|
||||
@@ -3,7 +3,6 @@
|
||||
"language": "cn",
|
||||
"pages": [
|
||||
{
|
||||
"absolutePath": "",
|
||||
"data": {
|
||||
"title": "Hello Chinese",
|
||||
},
|
||||
@@ -15,7 +14,6 @@
|
||||
"url": "/cn/test",
|
||||
},
|
||||
{
|
||||
"absolutePath": "",
|
||||
"data": {
|
||||
"title": "Nested Page Chinese",
|
||||
},
|
||||
@@ -33,7 +31,6 @@
|
||||
"language": "en",
|
||||
"pages": [
|
||||
{
|
||||
"absolutePath": "",
|
||||
"data": {
|
||||
"title": "Hello",
|
||||
},
|
||||
@@ -45,7 +42,6 @@
|
||||
"url": "/en/test",
|
||||
},
|
||||
{
|
||||
"absolutePath": "",
|
||||
"data": {
|
||||
"title": "Nested Page",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user