mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 06:31:49 +00:00
Core: Simplify Source API storage management
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-core': patch
|
||||
---
|
||||
|
||||
Simplify Source API storage management
|
||||
@@ -8,8 +8,18 @@ export class FileSystem<File> {
|
||||
files = new Map<string, File>();
|
||||
folders = new Map<string, string[]>();
|
||||
|
||||
constructor() {
|
||||
this.folders.set('', []);
|
||||
constructor(inherit?: FileSystem<File>) {
|
||||
if (inherit) {
|
||||
for (const [k, v] of inherit.folders) {
|
||||
this.folders.set(k, v);
|
||||
}
|
||||
|
||||
for (const [k, v] of inherit.files) {
|
||||
this.files.set(k, v);
|
||||
}
|
||||
} else {
|
||||
this.folders.set('', []);
|
||||
}
|
||||
}
|
||||
|
||||
read(path: string): File | undefined {
|
||||
@@ -24,13 +34,25 @@ export class FileSystem<File> {
|
||||
}
|
||||
|
||||
write(path: string, file: File): void {
|
||||
const dir = dirname(path);
|
||||
if (this.files.has(path)) {
|
||||
this.files.set(path, file);
|
||||
return;
|
||||
}
|
||||
|
||||
const dir = dirname(path);
|
||||
this.makeDir(dir);
|
||||
this.readDir(dir)?.push(path);
|
||||
this.files.set(path, file);
|
||||
}
|
||||
|
||||
delete(path: string) {
|
||||
return this.files.delete(path);
|
||||
}
|
||||
|
||||
deleteDir(path: string) {
|
||||
return this.folders.delete(path);
|
||||
}
|
||||
|
||||
getFiles(): string[] {
|
||||
return Array.from(this.files.keys());
|
||||
}
|
||||
|
||||
@@ -90,14 +90,13 @@ export function loadFiles(
|
||||
}),
|
||||
);
|
||||
|
||||
for (const lang of i18n.languages) {
|
||||
const storage: ContentStorage = new FileSystem();
|
||||
function scan(lang: string, fallback?: ContentStorage): ContentStorage {
|
||||
const storage: ContentStorage = new FileSystem(fallback);
|
||||
|
||||
for (const item of normalized) {
|
||||
const [path, locale = i18n.defaultLanguage] = parser(item.path);
|
||||
|
||||
if (locale !== lang) continue;
|
||||
storage.write(path, item);
|
||||
if (locale === lang) storage.write(path, item);
|
||||
}
|
||||
|
||||
for (const transformer of transformers) {
|
||||
@@ -107,7 +106,13 @@ export function loadFiles(
|
||||
});
|
||||
}
|
||||
|
||||
storages[lang] = storage;
|
||||
return storage;
|
||||
}
|
||||
|
||||
storages[i18n.defaultLanguage] = scan(i18n.defaultLanguage);
|
||||
|
||||
for (const lang of i18n.languages) {
|
||||
storages[lang] ??= scan(lang, storages[i18n.defaultLanguage]);
|
||||
}
|
||||
|
||||
return storages;
|
||||
|
||||
@@ -189,11 +189,7 @@ export interface LoaderOutput<Config extends LoaderConfig> {
|
||||
) => (Record<TSlug, string[]> & Record<TLang, string>)[];
|
||||
}
|
||||
|
||||
function indexPages(
|
||||
storages: Record<string, ContentStorage>,
|
||||
getUrl: UrlFn,
|
||||
i18n?: I18nConfig,
|
||||
) {
|
||||
function indexPages(storages: Record<string, ContentStorage>, getUrl: UrlFn) {
|
||||
const result = {
|
||||
// (locale.slugs -> page)
|
||||
pages: new Map<string, Page>(),
|
||||
@@ -203,41 +199,19 @@ function indexPages(
|
||||
pathToPage: new Map<string, Page>(),
|
||||
};
|
||||
|
||||
const defaultLanguage = i18n?.defaultLanguage ?? '';
|
||||
for (const [lang, storage] of Object.entries(storages)) {
|
||||
for (const filePath of storage.getFiles()) {
|
||||
const item = storage.read(filePath)!;
|
||||
const path = `${lang}.${filePath}`;
|
||||
|
||||
for (const filePath of storages[defaultLanguage].getFiles()) {
|
||||
const item = storages[defaultLanguage].read(filePath)!;
|
||||
const path = `${defaultLanguage}.${filePath}`;
|
||||
|
||||
if (item.format === 'meta') {
|
||||
result.pathToMeta.set(path, fileToMeta(item));
|
||||
}
|
||||
|
||||
if (item.format === 'page') {
|
||||
const page = fileToPage(item, getUrl, defaultLanguage);
|
||||
result.pathToPage.set(path, page);
|
||||
result.pages.set(`${defaultLanguage}.${page.slugs.join('/')}`, page);
|
||||
|
||||
if (!i18n) continue;
|
||||
|
||||
for (const lang of i18n.languages) {
|
||||
if (lang === defaultLanguage) continue;
|
||||
const localizedItem = storages[lang].read(filePath);
|
||||
const localizedPage = fileToPage(
|
||||
localizedItem?.format === 'page' ? localizedItem : item,
|
||||
getUrl,
|
||||
lang,
|
||||
);
|
||||
|
||||
if (localizedItem) {
|
||||
result.pathToPage.set(`${lang}.${filePath}`, localizedPage);
|
||||
}
|
||||
|
||||
result.pages.set(
|
||||
`${lang}.${localizedPage.slugs.join('/')}`,
|
||||
localizedPage,
|
||||
);
|
||||
if (item.format === 'meta') {
|
||||
result.pathToMeta.set(path, fileToMeta(item));
|
||||
continue;
|
||||
}
|
||||
|
||||
const page = fileToPage(item, getUrl, lang);
|
||||
result.pathToPage.set(path, page);
|
||||
result.pages.set(`${lang}.${page.slugs.join('/')}`, page);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,32 +336,31 @@ function createOutput(options: LoaderOptions): LoaderOutput<LoaderConfig> {
|
||||
},
|
||||
);
|
||||
|
||||
const walker = indexPages(storages, getUrl, i18n);
|
||||
const walker = indexPages(storages, getUrl);
|
||||
const builder = createPageTreeBuilder(getUrl);
|
||||
let pageTree: LoaderOutput<LoaderConfig>['pageTree'] | undefined;
|
||||
let pageTree: Record<string, PageTree.Root> | undefined;
|
||||
|
||||
return {
|
||||
_i18n: i18n,
|
||||
get pageTree() {
|
||||
if (i18n) {
|
||||
pageTree ??= builder.buildI18n({
|
||||
storages,
|
||||
resolveIcon: options.icon,
|
||||
i18n: i18n,
|
||||
...options.pageTree,
|
||||
}) as unknown as LoaderOutput<LoaderConfig>['pageTree'];
|
||||
} else {
|
||||
pageTree ??= builder.build({
|
||||
storage: storages[''],
|
||||
resolveIcon: options.icon,
|
||||
...options.pageTree,
|
||||
});
|
||||
}
|
||||
pageTree ??= builder.buildI18n({
|
||||
storages,
|
||||
resolveIcon: options.icon,
|
||||
...options.pageTree,
|
||||
});
|
||||
|
||||
return pageTree;
|
||||
return i18n
|
||||
? (pageTree as unknown as LoaderOutput<LoaderConfig>['pageTree'])
|
||||
: pageTree[defaultLanguage];
|
||||
},
|
||||
set pageTree(v) {
|
||||
pageTree = v;
|
||||
if (i18n) {
|
||||
pageTree = v as unknown as Record<string, PageTree.Root>;
|
||||
} else {
|
||||
pageTree = {
|
||||
defaultLanguage: v,
|
||||
};
|
||||
}
|
||||
},
|
||||
getPageByHref(href, { dir = '', language } = {}) {
|
||||
const [value, hash] = href.split('#', 2);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { ReactElement } from 'react';
|
||||
import type { I18nConfig } from '@/i18n';
|
||||
import type * as PageTree from './definitions';
|
||||
import { joinPath } from '@/utils/path';
|
||||
import type { MetaData, PageData, UrlFn } from '../types';
|
||||
@@ -24,10 +23,10 @@ export interface PageTreeBuilderContext<
|
||||
|
||||
builder: PageTreeBuilder<Page, Meta>;
|
||||
storage: ContentStorage<Page, Meta>;
|
||||
|
||||
locale?: string;
|
||||
localeStorage?: ContentStorage<Page, Meta>;
|
||||
getUrl: UrlFn;
|
||||
|
||||
storages?: Record<string, ContentStorage<Page, Meta>>;
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
export interface PageTreeTransformer<
|
||||
@@ -99,7 +98,6 @@ export interface PageTreeBuilder<
|
||||
options: BaseOptions<Page, Meta> & {
|
||||
id?: string;
|
||||
storages: Record<string, ContentStorage<Page, Meta>>;
|
||||
i18n: I18nConfig;
|
||||
},
|
||||
) => Record<string, PageTree.Root>;
|
||||
}
|
||||
@@ -217,14 +215,14 @@ function buildFolderNode(
|
||||
isGlobalRoot: boolean,
|
||||
ctx: PageTreeBuilderContext,
|
||||
): PageTree.Folder | undefined {
|
||||
const { storage, localeStorage, options, resolveName, transformers } = ctx;
|
||||
const { storage, options, resolveName, transformers } = ctx;
|
||||
const files = storage.readDir(folderPath);
|
||||
if (!files) return;
|
||||
|
||||
const metaPath = resolveName(joinPath(folderPath, 'meta'), 'meta');
|
||||
const indexPath = resolveName(joinPath(folderPath, 'index'), 'page');
|
||||
|
||||
let meta = localeStorage?.read(metaPath) ?? storage.read(metaPath);
|
||||
let meta = storage.read(metaPath);
|
||||
if (meta?.format !== 'meta') {
|
||||
meta = undefined;
|
||||
}
|
||||
@@ -305,8 +303,8 @@ function buildFileNode(
|
||||
path: string,
|
||||
ctx: PageTreeBuilderContext,
|
||||
): PageTree.Item | undefined {
|
||||
const { options, getUrl, storage, localeStorage, locale, transformers } = ctx;
|
||||
const page = localeStorage?.read(path) ?? storage.read(path);
|
||||
const { options, getUrl, storage, locale, transformers } = ctx;
|
||||
const page = storage.read(path);
|
||||
if (page?.format !== 'page') return;
|
||||
|
||||
const { title, description, icon } = page.data;
|
||||
@@ -393,29 +391,28 @@ export function createPageTreeBuilder(getUrl: UrlFn): PageTreeBuilder {
|
||||
},
|
||||
});
|
||||
},
|
||||
buildI18n({ id, i18n, ...options }) {
|
||||
const storage = options.storages[i18n.defaultLanguage];
|
||||
const resolve = createFlattenPathResolver(storage);
|
||||
buildI18n({ id, storages, ...options }) {
|
||||
const transformers = getTransformers(options);
|
||||
const out: Record<string, PageTree.Root> = {};
|
||||
|
||||
const entries = i18n.languages.map<[string, PageTree.Root]>((lang) => {
|
||||
const tree = build(id ?? lang ?? 'root', {
|
||||
for (const [locale, storage] of Object.entries(storages)) {
|
||||
const resolve = createFlattenPathResolver(storage);
|
||||
|
||||
out[locale] = build(id ?? (locale.length === 0 ? 'root' : locale), {
|
||||
transformers,
|
||||
builder: this,
|
||||
options,
|
||||
getUrl,
|
||||
locale: lang,
|
||||
locale,
|
||||
storage,
|
||||
localeStorage: options.storages[lang],
|
||||
storages,
|
||||
resolveName(name, format) {
|
||||
return resolve(name, format) ?? name;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return [lang, tree];
|
||||
});
|
||||
|
||||
return Object.fromEntries(entries);
|
||||
return out;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,9 +37,7 @@ export function legacyTransformer<Page extends PageData, Meta extends MetaData>(
|
||||
return {
|
||||
file(node, file) {
|
||||
if (!transformer.attachFile) return node;
|
||||
const content = file
|
||||
? (this.localeStorage?.read(file) ?? this.storage.read(file))
|
||||
: undefined;
|
||||
const content = file ? this.storage.read(file) : undefined;
|
||||
|
||||
return transformer.attachFile(
|
||||
node,
|
||||
@@ -50,17 +48,12 @@ export function legacyTransformer<Page extends PageData, Meta extends MetaData>(
|
||||
if (!transformer.attachFolder) return node;
|
||||
|
||||
const files = this.storage.readDir(folderPath) ?? [];
|
||||
const meta = metaPath
|
||||
? (this.localeStorage?.read(metaPath) ?? this.storage.read(metaPath))
|
||||
: undefined;
|
||||
const meta = metaPath ? this.storage.read(metaPath) : undefined;
|
||||
|
||||
return transformer.attachFolder(
|
||||
node,
|
||||
{
|
||||
children: files.flatMap(
|
||||
(file) =>
|
||||
this.localeStorage?.read(file) ?? this.storage.read(file) ?? [],
|
||||
),
|
||||
children: files.flatMap((file) => this.storage.read(file) ?? []),
|
||||
},
|
||||
meta?.format === 'meta' ? meta : undefined,
|
||||
);
|
||||
|
||||
@@ -12,10 +12,8 @@ export function transformerFallback(): PageTreeTransformer {
|
||||
for (const file of this.storage.getFiles()) {
|
||||
if (addedFiles.has(file)) continue;
|
||||
|
||||
const content =
|
||||
this.localeStorage?.read(file) ?? this.storage.read(file);
|
||||
if (!content) continue;
|
||||
isolatedStorage.write(file, content);
|
||||
const content = this.storage.read(file);
|
||||
if (content) isolatedStorage.write(file, content);
|
||||
}
|
||||
|
||||
if (isolatedStorage.getFiles().length === 0) return root;
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
},
|
||||
{
|
||||
"$id": "nested",
|
||||
"$ref": {
|
||||
"metaFile": "nested/meta.json",
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"$id": "nested/test.mdx",
|
||||
@@ -24,7 +27,7 @@
|
||||
"url": "/cn/nested/test",
|
||||
},
|
||||
],
|
||||
"name": "Nested",
|
||||
"name": "Nested Chinese",
|
||||
"type": "folder",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"url": "/cn/nested/test",
|
||||
},
|
||||
],
|
||||
"name": "Nested",
|
||||
"name": "Nested Chinese",
|
||||
"type": "folder",
|
||||
},
|
||||
],
|
||||
|
||||
+4
-1
@@ -13,6 +13,9 @@
|
||||
},
|
||||
{
|
||||
"$id": "nested",
|
||||
"$ref": {
|
||||
"metaFile": "nested/meta.json",
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"$id": "nested/test.mdx",
|
||||
@@ -24,7 +27,7 @@
|
||||
"url": "/cn/nested/test",
|
||||
},
|
||||
],
|
||||
"name": "Nested",
|
||||
"name": "Nested Chinese",
|
||||
"type": "folder",
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user