mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
Core: Fix node IDs are not unique across different locales
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-core': patch
|
||||
---
|
||||
|
||||
[Page Tree Builder] Fix node IDs are not unique across different locales
|
||||
@@ -1,8 +1,13 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface Root {
|
||||
interface INode {
|
||||
/**
|
||||
* ID for the node, unique in all page trees (even across different locales)
|
||||
*/
|
||||
$id?: string;
|
||||
}
|
||||
|
||||
export interface Root extends INode {
|
||||
name: ReactNode;
|
||||
children: Node[];
|
||||
/**
|
||||
@@ -13,8 +18,7 @@ export interface Root {
|
||||
|
||||
export type Node = Item | Separator | Folder;
|
||||
|
||||
export interface Item {
|
||||
$id?: string;
|
||||
export interface Item extends INode {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@@ -36,16 +40,13 @@ export interface Item {
|
||||
icon?: ReactNode;
|
||||
}
|
||||
|
||||
export interface Separator {
|
||||
$id?: string;
|
||||
|
||||
export interface Separator extends INode {
|
||||
type: 'separator';
|
||||
name?: ReactNode;
|
||||
icon?: ReactNode;
|
||||
}
|
||||
|
||||
export interface Folder {
|
||||
$id?: string;
|
||||
export interface Folder extends INode {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -9,10 +9,8 @@ export interface PageTreeBuilderContext<
|
||||
Page extends PageData = PageData,
|
||||
Meta extends MetaData = MetaData,
|
||||
> {
|
||||
/**
|
||||
* @internal resolve paths without extensions
|
||||
*/
|
||||
resolveName: (name: string, format: 'meta' | 'page') => string;
|
||||
rootId: string;
|
||||
generateNodeId: () => string;
|
||||
options: PageTreeOptions;
|
||||
transformers: PageTreeTransformer<Page, Meta>[];
|
||||
|
||||
@@ -22,7 +20,6 @@ export interface PageTreeBuilderContext<
|
||||
|
||||
storages?: Record<string, ContentStorage<Page, Meta>>;
|
||||
locale?: string;
|
||||
visitedPaths: Set<string>;
|
||||
}
|
||||
|
||||
export interface PageTreeTransformer<
|
||||
@@ -92,295 +89,39 @@ const restReversed = 'z...a' as const;
|
||||
const extractPrefix = '...';
|
||||
const excludePrefix = '!';
|
||||
|
||||
function buildAll(
|
||||
paths: string[],
|
||||
ctx: PageTreeBuilderContext,
|
||||
reversed = false,
|
||||
): PageTree.Node[] {
|
||||
const items: PageTree.Item[] = [];
|
||||
const folders: PageTree.Folder[] = [];
|
||||
const sortedPaths = paths.sort(
|
||||
(a, b) => a.localeCompare(b) * (reversed ? -1 : 1),
|
||||
);
|
||||
|
||||
for (const path of sortedPaths) {
|
||||
ctx.visitedPaths.add(path);
|
||||
|
||||
const fileNode = buildFileNode(path, ctx);
|
||||
if (fileNode) {
|
||||
if (basename(path, extname(path)) === 'index') items.unshift(fileNode);
|
||||
else items.push(fileNode);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const dirNode = buildFolderNode(path, false, ctx);
|
||||
if (dirNode) folders.push(dirNode);
|
||||
}
|
||||
|
||||
return [...items, ...folders];
|
||||
}
|
||||
|
||||
function resolveFolderItem(
|
||||
folderPath: string,
|
||||
item: string,
|
||||
ctx: PageTreeBuilderContext,
|
||||
idx: number,
|
||||
): PageTree.Node[] | '...' | 'z...a' {
|
||||
if (item === rest || item === restReversed) return item;
|
||||
const { resolveName } = ctx;
|
||||
|
||||
let match = separator.exec(item);
|
||||
if (match?.groups) {
|
||||
let node: PageTree.Separator = {
|
||||
$id: `${folderPath}#${idx}`,
|
||||
type: 'separator',
|
||||
icon: match.groups.icon,
|
||||
name: match.groups.name,
|
||||
};
|
||||
|
||||
for (const transformer of ctx.transformers) {
|
||||
if (!transformer.separator) continue;
|
||||
node = transformer.separator.call(ctx, node);
|
||||
}
|
||||
|
||||
return [node];
|
||||
}
|
||||
|
||||
match = link.exec(item);
|
||||
if (match?.groups) {
|
||||
const { icon, url, name, external } = match.groups;
|
||||
|
||||
let node: PageTree.Item = {
|
||||
type: 'page',
|
||||
icon,
|
||||
name,
|
||||
url,
|
||||
external: external ? true : undefined,
|
||||
};
|
||||
|
||||
for (const transformer of ctx.transformers) {
|
||||
if (!transformer.file) continue;
|
||||
node = transformer.file.call(ctx, node);
|
||||
}
|
||||
|
||||
return [node];
|
||||
}
|
||||
|
||||
const isExcept = item.startsWith(excludePrefix);
|
||||
const isExtract = !isExcept && item.startsWith(extractPrefix);
|
||||
|
||||
let filename = item;
|
||||
if (isExcept) {
|
||||
filename = item.slice(excludePrefix.length);
|
||||
} else if (isExtract) {
|
||||
filename = item.slice(extractPrefix.length);
|
||||
}
|
||||
|
||||
const path = resolveName(joinPath(folderPath, filename), 'page');
|
||||
ctx.visitedPaths.add(path);
|
||||
|
||||
if (isExcept) return [];
|
||||
|
||||
const dirNode = buildFolderNode(path, false, ctx);
|
||||
if (dirNode) {
|
||||
return isExtract ? dirNode.children : [dirNode];
|
||||
}
|
||||
|
||||
const fileNode = buildFileNode(path, ctx);
|
||||
return fileNode ? [fileNode] : [];
|
||||
}
|
||||
|
||||
function buildFolderNode(
|
||||
folderPath: string,
|
||||
isGlobalRoot: boolean,
|
||||
ctx: PageTreeBuilderContext,
|
||||
): PageTree.Folder | undefined {
|
||||
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 = storage.read(metaPath);
|
||||
if (meta?.format !== 'meta') {
|
||||
meta = undefined;
|
||||
}
|
||||
|
||||
const isRoot = meta?.data.root ?? isGlobalRoot;
|
||||
let index: PageTree.Item | undefined;
|
||||
let children: PageTree.Node[];
|
||||
|
||||
function setIndexIfUnused() {
|
||||
if (isRoot || ctx.visitedPaths.has(indexPath)) return;
|
||||
ctx.visitedPaths.add(indexPath);
|
||||
index = buildFileNode(indexPath, ctx);
|
||||
}
|
||||
|
||||
if (meta && meta.data.pages) {
|
||||
const resolved = meta.data.pages.flatMap<
|
||||
PageTree.Node | typeof rest | typeof restReversed
|
||||
>((item, i) => resolveFolderItem(folderPath, item, ctx, i));
|
||||
setIndexIfUnused();
|
||||
|
||||
for (let i = 0; i < resolved.length; i++) {
|
||||
const item = resolved[i];
|
||||
if (item !== rest && item !== restReversed) continue;
|
||||
|
||||
const items = buildAll(
|
||||
files.filter((file) => !ctx.visitedPaths.has(file)),
|
||||
ctx,
|
||||
item === restReversed,
|
||||
);
|
||||
|
||||
resolved.splice(i, 1, ...items);
|
||||
break;
|
||||
}
|
||||
|
||||
children = resolved as PageTree.Node[];
|
||||
} else {
|
||||
setIndexIfUnused();
|
||||
children = buildAll(
|
||||
files.filter((file) => !ctx.visitedPaths.has(file)),
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
|
||||
let name = meta?.data.title ?? index?.name;
|
||||
if (!name) {
|
||||
const folderName = basename(folderPath);
|
||||
name = pathToName(group.exec(folderName)?.[1] ?? folderName);
|
||||
}
|
||||
|
||||
let node: PageTree.Folder = {
|
||||
type: 'folder',
|
||||
name,
|
||||
icon: meta?.data.icon ?? index?.icon,
|
||||
root: meta?.data.root,
|
||||
defaultOpen: meta?.data.defaultOpen,
|
||||
description: meta?.data.description,
|
||||
index,
|
||||
children,
|
||||
$id: folderPath,
|
||||
$ref:
|
||||
!options.noRef && meta
|
||||
? {
|
||||
metaFile: metaPath,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
for (const transformer of transformers) {
|
||||
if (!transformer.folder) continue;
|
||||
node = transformer.folder.call(ctx, node, folderPath, metaPath);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function buildFileNode(
|
||||
path: string,
|
||||
ctx: PageTreeBuilderContext,
|
||||
): PageTree.Item | undefined {
|
||||
const { options, getUrl, storage, locale, transformers } = ctx;
|
||||
|
||||
const page = storage.read(path);
|
||||
if (page?.format !== 'page') return;
|
||||
|
||||
const { title, description, icon } = page.data;
|
||||
let item: PageTree.Item = {
|
||||
$id: path,
|
||||
type: 'page',
|
||||
name: title ?? pathToName(basename(path, extname(path))),
|
||||
description,
|
||||
icon,
|
||||
url: getUrl(page.slugs, locale),
|
||||
$ref: !options.noRef
|
||||
? {
|
||||
file: path,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
for (const transformer of transformers) {
|
||||
if (!transformer.file) continue;
|
||||
item = transformer.file.call(ctx, item, path);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
function build(id: string, ctx: PageTreeBuilderContext): PageTree.Root {
|
||||
const folder = buildFolderNode('', true, ctx)!;
|
||||
let root: PageTree.Root = {
|
||||
$id: id,
|
||||
name: folder.name || 'Docs',
|
||||
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,
|
||||
plugins?: LoaderPlugin[],
|
||||
): PageTreeBuilder {
|
||||
function getTransformers({
|
||||
generateFallback = true,
|
||||
...options
|
||||
}: PageTreeOptions) {
|
||||
const transformers: PageTreeTransformer[] = [];
|
||||
|
||||
if (options.transformers) {
|
||||
transformers.push(...options.transformers);
|
||||
}
|
||||
|
||||
for (const plugin of plugins ?? []) {
|
||||
if (plugin.transformPageTree) transformers.push(plugin.transformPageTree);
|
||||
}
|
||||
|
||||
if (generateFallback) {
|
||||
transformers.push(transformerFallback());
|
||||
}
|
||||
|
||||
return transformers;
|
||||
}
|
||||
|
||||
function createFlattenPathResolver(storage: ContentStorage) {
|
||||
const map = new Map<string, string>();
|
||||
const files = storage.getFiles();
|
||||
for (const file of files) {
|
||||
const content = storage.read(file)!;
|
||||
const flattenPath = file.substring(0, file.length - extname(file).length);
|
||||
|
||||
map.set(flattenPath + '.' + content.format, file);
|
||||
}
|
||||
|
||||
return (name: string, format: string) => {
|
||||
return map.get(name + '.' + format);
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
build(storage, options) {
|
||||
const key = '';
|
||||
return this.buildI18n({ [key]: storage }, options)[key];
|
||||
},
|
||||
buildI18n(storages, options = {}) {
|
||||
const transformers = getTransformers(options);
|
||||
let nextId = 0;
|
||||
const out: Record<string, PageTree.Root> = {};
|
||||
const transformers: PageTreeTransformer[] = [];
|
||||
|
||||
if (options.transformers) {
|
||||
transformers.push(...options.transformers);
|
||||
}
|
||||
|
||||
for (const plugin of plugins ?? []) {
|
||||
if (plugin.transformPageTree)
|
||||
transformers.push(plugin.transformPageTree);
|
||||
}
|
||||
|
||||
if (options.generateFallback ?? true) {
|
||||
transformers.push(transformerFallback());
|
||||
}
|
||||
|
||||
for (const [locale, storage] of Object.entries(storages)) {
|
||||
const resolve = createFlattenPathResolver(storage);
|
||||
const branch = locale.length === 0 ? 'root' : locale;
|
||||
let rootId = locale.length === 0 ? 'root' : locale;
|
||||
if (options.id) rootId = `${options.id}-${rootId}`;
|
||||
|
||||
out[locale] = build(options.id ? `${options.id}-${branch}` : branch, {
|
||||
out[locale] = createPageTreeBuilderUtils({
|
||||
rootId,
|
||||
transformers,
|
||||
builder: this,
|
||||
options,
|
||||
@@ -388,11 +129,10 @@ export function createPageTreeBuilder(
|
||||
locale,
|
||||
storage,
|
||||
storages,
|
||||
visitedPaths: new Set<string>(),
|
||||
resolveName(name, format) {
|
||||
return resolve(name, format) ?? name;
|
||||
generateNodeId() {
|
||||
return '_' + nextId++;
|
||||
},
|
||||
});
|
||||
}).root();
|
||||
}
|
||||
|
||||
return out;
|
||||
@@ -400,6 +140,257 @@ export function createPageTreeBuilder(
|
||||
};
|
||||
}
|
||||
|
||||
function createFlattenPathResolver(storage: ContentStorage) {
|
||||
const map = new Map<string, string>();
|
||||
const files = storage.getFiles();
|
||||
for (const file of files) {
|
||||
const content = storage.read(file)!;
|
||||
const flattenPath = file.substring(0, file.length - extname(file).length);
|
||||
|
||||
map.set(flattenPath + '.' + content.format, file);
|
||||
}
|
||||
|
||||
return (name: string, format: string) => {
|
||||
return map.get(name + '.' + format) ?? name;
|
||||
};
|
||||
}
|
||||
|
||||
function createPageTreeBuilderUtils(ctx: PageTreeBuilderContext) {
|
||||
const resolveFlattenPath = createFlattenPathResolver(ctx.storage);
|
||||
const visitedPaths = new Set<string>();
|
||||
|
||||
function nextNodeId(localId = ctx.generateNodeId()) {
|
||||
return `${ctx.rootId}:${localId}`;
|
||||
}
|
||||
|
||||
return {
|
||||
buildPaths(paths: string[], reversed = false): PageTree.Node[] {
|
||||
const items: PageTree.Item[] = [];
|
||||
const folders: PageTree.Folder[] = [];
|
||||
const sortedPaths = paths.sort(
|
||||
(a, b) => a.localeCompare(b) * (reversed ? -1 : 1),
|
||||
);
|
||||
|
||||
for (const path of sortedPaths) {
|
||||
const fileNode = this.file(path);
|
||||
if (fileNode) {
|
||||
if (basename(path, extname(path)) === 'index')
|
||||
items.unshift(fileNode);
|
||||
else items.push(fileNode);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const dirNode = this.folder(path, false);
|
||||
if (dirNode) folders.push(dirNode);
|
||||
}
|
||||
|
||||
return [...items, ...folders];
|
||||
},
|
||||
resolveFolderItem(
|
||||
folderPath: string,
|
||||
item: string,
|
||||
): PageTree.Node[] | '...' | 'z...a' {
|
||||
if (item === rest || item === restReversed) return item;
|
||||
|
||||
let match = separator.exec(item);
|
||||
if (match?.groups) {
|
||||
let node: PageTree.Separator = {
|
||||
$id: nextNodeId(),
|
||||
type: 'separator',
|
||||
icon: match.groups.icon,
|
||||
name: match.groups.name,
|
||||
};
|
||||
|
||||
for (const transformer of ctx.transformers) {
|
||||
if (!transformer.separator) continue;
|
||||
node = transformer.separator.call(ctx, node);
|
||||
}
|
||||
|
||||
return [node];
|
||||
}
|
||||
|
||||
match = link.exec(item);
|
||||
if (match?.groups) {
|
||||
const { icon, url, name, external } = match.groups;
|
||||
|
||||
let node: PageTree.Item = {
|
||||
$id: nextNodeId(),
|
||||
type: 'page',
|
||||
icon,
|
||||
name,
|
||||
url,
|
||||
external: external ? true : undefined,
|
||||
};
|
||||
|
||||
for (const transformer of ctx.transformers) {
|
||||
if (!transformer.file) continue;
|
||||
node = transformer.file.call(ctx, node);
|
||||
}
|
||||
|
||||
return [node];
|
||||
}
|
||||
|
||||
const isExcept = item.startsWith(excludePrefix);
|
||||
const isExtract = !isExcept && item.startsWith(extractPrefix);
|
||||
|
||||
let filename = item;
|
||||
if (isExcept) {
|
||||
filename = item.slice(excludePrefix.length);
|
||||
} else if (isExtract) {
|
||||
filename = item.slice(extractPrefix.length);
|
||||
}
|
||||
|
||||
const path = resolveFlattenPath(joinPath(folderPath, filename), 'page');
|
||||
|
||||
if (isExcept) {
|
||||
visitedPaths.add(path);
|
||||
return [];
|
||||
}
|
||||
|
||||
const dirNode = this.folder(path, false);
|
||||
if (dirNode) {
|
||||
return isExtract ? dirNode.children : [dirNode];
|
||||
}
|
||||
|
||||
const fileNode = this.file(path);
|
||||
return fileNode ? [fileNode] : [];
|
||||
},
|
||||
folder(
|
||||
folderPath: string,
|
||||
isGlobalRoot: boolean,
|
||||
): PageTree.Folder | undefined {
|
||||
const { storage, options, transformers } = ctx;
|
||||
const files = storage.readDir(folderPath);
|
||||
if (!files) return;
|
||||
|
||||
const metaPath = resolveFlattenPath(joinPath(folderPath, 'meta'), 'meta');
|
||||
const indexPath = resolveFlattenPath(
|
||||
joinPath(folderPath, 'index'),
|
||||
'page',
|
||||
);
|
||||
|
||||
let meta = storage.read(metaPath);
|
||||
if (meta?.format !== 'meta') {
|
||||
meta = undefined;
|
||||
}
|
||||
|
||||
const isRoot = meta?.data.root ?? isGlobalRoot;
|
||||
let index: PageTree.Item | undefined;
|
||||
let children: PageTree.Node[];
|
||||
|
||||
if (meta && meta.data.pages) {
|
||||
const resolved = meta.data.pages.flatMap<
|
||||
PageTree.Node | typeof rest | typeof restReversed
|
||||
>((item) => this.resolveFolderItem(folderPath, item));
|
||||
|
||||
if (!isRoot && !visitedPaths.has(indexPath)) {
|
||||
index = this.file(indexPath);
|
||||
}
|
||||
|
||||
for (let i = 0; i < resolved.length; i++) {
|
||||
const item = resolved[i];
|
||||
if (item !== rest && item !== restReversed) continue;
|
||||
|
||||
const items = this.buildPaths(
|
||||
files.filter((file) => !visitedPaths.has(file)),
|
||||
item === restReversed,
|
||||
);
|
||||
|
||||
resolved.splice(i, 1, ...items);
|
||||
break;
|
||||
}
|
||||
|
||||
children = resolved as PageTree.Node[];
|
||||
} else {
|
||||
if (!isRoot && !visitedPaths.has(indexPath)) {
|
||||
index = this.file(indexPath);
|
||||
}
|
||||
|
||||
children = this.buildPaths(
|
||||
files.filter((file) => !visitedPaths.has(file)),
|
||||
);
|
||||
}
|
||||
|
||||
let name = meta?.data.title ?? index?.name;
|
||||
if (!name) {
|
||||
const folderName = basename(folderPath);
|
||||
name = pathToName(group.exec(folderName)?.[1] ?? folderName);
|
||||
}
|
||||
|
||||
let node: PageTree.Folder = {
|
||||
type: 'folder',
|
||||
name,
|
||||
icon: meta?.data.icon ?? index?.icon,
|
||||
root: meta?.data.root,
|
||||
defaultOpen: meta?.data.defaultOpen,
|
||||
description: meta?.data.description,
|
||||
index,
|
||||
children,
|
||||
$id: nextNodeId(folderPath),
|
||||
$ref:
|
||||
!options.noRef && meta
|
||||
? {
|
||||
metaFile: metaPath,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
visitedPaths.add(folderPath);
|
||||
for (const transformer of transformers) {
|
||||
if (!transformer.folder) continue;
|
||||
node = transformer.folder.call(ctx, node, folderPath, metaPath);
|
||||
}
|
||||
|
||||
return node;
|
||||
},
|
||||
file(path: string): PageTree.Item | undefined {
|
||||
const { options, getUrl, storage, locale, transformers } = ctx;
|
||||
|
||||
const page = storage.read(path);
|
||||
if (page?.format !== 'page') return;
|
||||
|
||||
const { title, description, icon } = page.data;
|
||||
let item: PageTree.Item = {
|
||||
$id: nextNodeId(path),
|
||||
type: 'page',
|
||||
name: title ?? pathToName(basename(path, extname(path))),
|
||||
description,
|
||||
icon,
|
||||
url: getUrl(page.slugs, locale),
|
||||
$ref: !options.noRef
|
||||
? {
|
||||
file: path,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
visitedPaths.add(path);
|
||||
for (const transformer of transformers) {
|
||||
if (!transformer.file) continue;
|
||||
item = transformer.file.call(ctx, item, path);
|
||||
}
|
||||
|
||||
return item;
|
||||
},
|
||||
root(): PageTree.Root {
|
||||
const folder = this.folder('', true)!;
|
||||
let root: PageTree.Root = {
|
||||
$id: ctx.rootId,
|
||||
name: folder.name || 'Docs',
|
||||
children: folder.children,
|
||||
};
|
||||
|
||||
for (const transformer of ctx.transformers) {
|
||||
if (!transformer.root) continue;
|
||||
root = transformer.root.call(ctx, root);
|
||||
}
|
||||
|
||||
return root;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item name from file name
|
||||
*
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"$id": "root",
|
||||
"children": [
|
||||
{
|
||||
"$id": "test.mdx",
|
||||
"$id": "root:test.mdx",
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/test",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"$id": "cn",
|
||||
"children": [
|
||||
{
|
||||
"$id": "test.mdx",
|
||||
"$id": "cn:test.mdx",
|
||||
"$ref": {
|
||||
"file": "test.mdx",
|
||||
},
|
||||
@@ -12,13 +12,13 @@
|
||||
"url": "/cn/test",
|
||||
},
|
||||
{
|
||||
"$id": "nested",
|
||||
"$id": "cn:nested",
|
||||
"$ref": {
|
||||
"metaFile": "nested/meta.json",
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"$id": "nested/test.mdx",
|
||||
"$id": "cn:nested/test.mdx",
|
||||
"$ref": {
|
||||
"file": "nested/test.mdx",
|
||||
},
|
||||
@@ -37,7 +37,7 @@
|
||||
"$id": "en",
|
||||
"children": [
|
||||
{
|
||||
"$id": "test.mdx",
|
||||
"$id": "en:test.mdx",
|
||||
"$ref": {
|
||||
"file": "test.mdx",
|
||||
},
|
||||
@@ -46,10 +46,10 @@
|
||||
"url": "/en/test",
|
||||
},
|
||||
{
|
||||
"$id": "nested",
|
||||
"$id": "en:nested",
|
||||
"children": [
|
||||
{
|
||||
"$id": "nested/test.mdx",
|
||||
"$id": "en:nested/test.mdx",
|
||||
"$ref": {
|
||||
"file": "nested/test.mdx",
|
||||
},
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
"$id": "cn",
|
||||
"children": [
|
||||
{
|
||||
"$id": "test.mdx",
|
||||
"$id": "cn:test.mdx",
|
||||
"name": "Hello Chinese",
|
||||
"type": "page",
|
||||
"url": "/cn/test",
|
||||
},
|
||||
{
|
||||
"$id": "nested",
|
||||
"$id": "cn:nested",
|
||||
"children": [
|
||||
{
|
||||
"$id": "nested/test.mdx",
|
||||
"$id": "cn:nested/test.mdx",
|
||||
"name": "Nested Page Chinese",
|
||||
"type": "page",
|
||||
"url": "/cn/nested/test",
|
||||
@@ -28,16 +28,16 @@
|
||||
"$id": "en",
|
||||
"children": [
|
||||
{
|
||||
"$id": "test.mdx",
|
||||
"$id": "en:test.mdx",
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/test",
|
||||
},
|
||||
{
|
||||
"$id": "nested",
|
||||
"$id": "en:nested",
|
||||
"children": [
|
||||
{
|
||||
"$id": "nested/test.mdx",
|
||||
"$id": "en:nested/test.mdx",
|
||||
"name": "Nested Page",
|
||||
"type": "page",
|
||||
"url": "/nested/test",
|
||||
|
||||
+6
-6
@@ -3,7 +3,7 @@
|
||||
"$id": "cn",
|
||||
"children": [
|
||||
{
|
||||
"$id": "test.mdx",
|
||||
"$id": "cn:test.mdx",
|
||||
"$ref": {
|
||||
"file": "test.mdx",
|
||||
},
|
||||
@@ -12,13 +12,13 @@
|
||||
"url": "/cn/test",
|
||||
},
|
||||
{
|
||||
"$id": "nested",
|
||||
"$id": "cn:nested",
|
||||
"$ref": {
|
||||
"metaFile": "nested/meta.json",
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"$id": "nested/test.mdx",
|
||||
"$id": "cn:nested/test.mdx",
|
||||
"$ref": {
|
||||
"file": "nested/test.mdx",
|
||||
},
|
||||
@@ -37,7 +37,7 @@
|
||||
"$id": "en",
|
||||
"children": [
|
||||
{
|
||||
"$id": "test.mdx",
|
||||
"$id": "en:test.mdx",
|
||||
"$ref": {
|
||||
"file": "test.mdx",
|
||||
},
|
||||
@@ -46,10 +46,10 @@
|
||||
"url": "/en/test",
|
||||
},
|
||||
{
|
||||
"$id": "nested",
|
||||
"$id": "en:nested",
|
||||
"children": [
|
||||
{
|
||||
"$id": "nested/test.mdx",
|
||||
"$id": "en:nested/test.mdx",
|
||||
"$ref": {
|
||||
"file": "nested/test.mdx",
|
||||
},
|
||||
|
||||
+11
-8
@@ -2,16 +2,16 @@
|
||||
"$id": "root",
|
||||
"children": [
|
||||
{
|
||||
"$id": "test.mdx",
|
||||
"$id": "root:test.mdx",
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/test",
|
||||
},
|
||||
{
|
||||
"$id": "(nested)",
|
||||
"$id": "root:(nested)",
|
||||
"children": [
|
||||
{
|
||||
"$id": "(nested)/hello.mdx",
|
||||
"$id": "root:(nested)/hello.mdx",
|
||||
"name": "Route Group Page",
|
||||
"type": "page",
|
||||
"url": "/hello",
|
||||
@@ -21,10 +21,10 @@
|
||||
"type": "folder",
|
||||
},
|
||||
{
|
||||
"$id": "hello",
|
||||
"$id": "root:hello",
|
||||
"children": [],
|
||||
"index": {
|
||||
"$id": "hello/index.mdx",
|
||||
"$id": "root:hello/index.mdx",
|
||||
"name": "Another hello page",
|
||||
"type": "page",
|
||||
"url": "/hello/index",
|
||||
@@ -33,10 +33,10 @@
|
||||
"type": "folder",
|
||||
},
|
||||
{
|
||||
"$id": "nested",
|
||||
"$id": "root:nested",
|
||||
"children": [
|
||||
{
|
||||
"$id": "nested/test.mdx",
|
||||
"$id": "root:nested/test.mdx",
|
||||
"name": "Nested Page",
|
||||
"type": "page",
|
||||
"url": "/nested/test",
|
||||
@@ -46,17 +46,20 @@
|
||||
"type": "folder",
|
||||
},
|
||||
{
|
||||
"$id": "root:_0",
|
||||
"name": "Text",
|
||||
"type": "page",
|
||||
"url": "https://google.com",
|
||||
},
|
||||
{
|
||||
"$id": "root:_1",
|
||||
"icon": "Icon",
|
||||
"name": "Text",
|
||||
"type": "page",
|
||||
"url": "https://google.com",
|
||||
},
|
||||
{
|
||||
"$id": "root:_2",
|
||||
"external": true,
|
||||
"name": "AI",
|
||||
"type": "page",
|
||||
@@ -67,7 +70,7 @@
|
||||
"$id": "fallback-root-root",
|
||||
"children": [
|
||||
{
|
||||
"$id": "hidden.mdx",
|
||||
"$id": "fallback-root-root:hidden.mdx",
|
||||
"name": "Hidden",
|
||||
"type": "page",
|
||||
"url": "/hidden",
|
||||
|
||||
@@ -170,16 +170,16 @@ test('Loader: Without meta.json', () => {
|
||||
"$id": "root",
|
||||
"children": [
|
||||
{
|
||||
"$id": "test.mdx",
|
||||
"$id": "root:test.mdx",
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/test",
|
||||
},
|
||||
{
|
||||
"$id": "hello",
|
||||
"$id": "root:hello",
|
||||
"children": [],
|
||||
"index": {
|
||||
"$id": "hello/index.mdx",
|
||||
"$id": "root:hello/index.mdx",
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/hello",
|
||||
@@ -232,13 +232,13 @@ test('Loader: Rest operator', () => {
|
||||
"$id": "root",
|
||||
"children": [
|
||||
{
|
||||
"$id": "2-2.mdx",
|
||||
"$id": "root:2-2.mdx",
|
||||
"name": "2.2",
|
||||
"type": "page",
|
||||
"url": "/2-2",
|
||||
},
|
||||
{
|
||||
"$id": "1-2.mdx",
|
||||
"$id": "root:1-2.mdx",
|
||||
"name": "1.2",
|
||||
"type": "page",
|
||||
"url": "/1-2",
|
||||
@@ -284,9 +284,9 @@ test('Loader: Allow duplicate pages when explicitly referenced twice', () => {
|
||||
|
||||
const treeChildren = result.pageTree.children;
|
||||
expect(treeChildren.length).toBe(3);
|
||||
expect(treeChildren[0].$id).toBe('page1.mdx');
|
||||
expect(treeChildren[1].$id).toBe('page1.mdx');
|
||||
expect(treeChildren[2].$id).toBe('page2.mdx');
|
||||
expect(treeChildren[0].$id).toBe('root:page1.mdx');
|
||||
expect(treeChildren[1].$id).toBe('root:page1.mdx');
|
||||
expect(treeChildren[2].$id).toBe('root:page2.mdx');
|
||||
});
|
||||
|
||||
test('Loader: No duplicate pages when referencing subfolder items and folder', () => {
|
||||
@@ -376,34 +376,34 @@ test('Loader: No duplicate pages when referencing subfolder items and folder', (
|
||||
"$id": "root",
|
||||
"children": [
|
||||
{
|
||||
"$id": "index.mdx",
|
||||
"$id": "root:index.mdx",
|
||||
"name": "Home",
|
||||
"type": "page",
|
||||
"url": "/",
|
||||
},
|
||||
{
|
||||
"$id": "subfolder/page1.mdx",
|
||||
"$id": "root:subfolder/page1.mdx",
|
||||
"name": "Subfolder Page 1",
|
||||
"type": "page",
|
||||
"url": "/subfolder/page1",
|
||||
},
|
||||
{
|
||||
"$id": "subfolder/page2.mdx",
|
||||
"$id": "root:subfolder/page2.mdx",
|
||||
"name": "Subfolder Page 2",
|
||||
"type": "page",
|
||||
"url": "/subfolder/page2",
|
||||
},
|
||||
{
|
||||
"$id": "other-page.mdx",
|
||||
"$id": "root:other-page.mdx",
|
||||
"name": "Other Page",
|
||||
"type": "page",
|
||||
"url": "/other-page",
|
||||
},
|
||||
{
|
||||
"$id": "subfolder",
|
||||
"$id": "root:subfolder",
|
||||
"children": [
|
||||
{
|
||||
"$id": "subfolder/page3.mdx",
|
||||
"$id": "root:subfolder/page3.mdx",
|
||||
"name": "Subfolder Page 3",
|
||||
"type": "page",
|
||||
"url": "/subfolder/page3",
|
||||
|
||||
Reference in New Issue
Block a user