mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
Core & UI: Generate a $id attribute to page tree nodes
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-core': patch
|
||||
---
|
||||
|
||||
Generate a `$id` attribute to page tree nodes
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { ReactElement, ReactNode } from 'react';
|
||||
|
||||
export interface Root {
|
||||
$id?: string;
|
||||
|
||||
name: ReactNode;
|
||||
children: Node[];
|
||||
}
|
||||
@@ -8,24 +10,28 @@ export interface Root {
|
||||
export type Node = Item | Separator | Folder;
|
||||
|
||||
export interface Item {
|
||||
$id?: string;
|
||||
$ref?: {
|
||||
file: string;
|
||||
};
|
||||
|
||||
type: 'page';
|
||||
name: ReactNode;
|
||||
url: string;
|
||||
external?: boolean;
|
||||
icon?: ReactElement;
|
||||
|
||||
$ref?: {
|
||||
file: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Separator {
|
||||
$id?: string;
|
||||
|
||||
type: 'separator';
|
||||
name: ReactNode;
|
||||
icon?: ReactElement;
|
||||
}
|
||||
|
||||
export interface Folder {
|
||||
$id?: string;
|
||||
$ref?: {
|
||||
metaFile?: string;
|
||||
};
|
||||
|
||||
@@ -131,20 +131,19 @@ function indexPages(
|
||||
// (locale.slugs -> page[])
|
||||
pages: Map<string, Page>;
|
||||
|
||||
pathToPage: Map<string, Page>;
|
||||
pathToMeta: Map<string, Meta>;
|
||||
pathToFile: Map<string, Page | Meta>;
|
||||
} {
|
||||
const defaultLanguage = i18n?.defaultLanguage ?? '';
|
||||
const map = new Map<string, Page>();
|
||||
const pages = new Map<string, Page>();
|
||||
const metas = new Map<string, Meta>();
|
||||
const pathToFile = new Map<string, Page | Meta>();
|
||||
|
||||
for (const item of storage.list()) {
|
||||
if (item.format === 'meta') metas.set(item.file.path, fileToMeta(item));
|
||||
if (item.format === 'meta')
|
||||
pathToFile.set(item.file.path, fileToMeta(item));
|
||||
|
||||
if (item.format === 'page') {
|
||||
const page = fileToPage(item, getUrl, item.file.locale);
|
||||
pages.set(item.file.path, page);
|
||||
pathToFile.set(item.file.path, page);
|
||||
|
||||
if (item.file.locale) continue;
|
||||
map.set(`${defaultLanguage}.${page.slugs.join('/')}`, page);
|
||||
@@ -164,8 +163,7 @@ function indexPages(
|
||||
|
||||
return {
|
||||
pages: map,
|
||||
pathToPage: pages,
|
||||
pathToMeta: metas,
|
||||
pathToFile,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -279,7 +277,7 @@ function createOutput(options: LoaderOptions): LoaderOutput<LoaderConfig> {
|
||||
getNodeMeta(node) {
|
||||
if (!node.$ref?.metaFile) return;
|
||||
|
||||
return walker.pathToMeta.get(node.$ref.metaFile);
|
||||
return walker.pathToFile.get(node.$ref.metaFile) as Meta;
|
||||
},
|
||||
getPageTree(locale) {
|
||||
if (options.i18n) {
|
||||
@@ -291,9 +289,10 @@ function createOutput(options: LoaderOptions): LoaderOutput<LoaderConfig> {
|
||||
return pageTree as PageTree.Root;
|
||||
},
|
||||
getNodePage(node) {
|
||||
if (!node.$ref?.file) return;
|
||||
const ref = node.$ref?.file ?? node.$id;
|
||||
if (!ref) return;
|
||||
|
||||
return walker.pathToPage.get(node.$ref.file);
|
||||
return walker.pathToFile.get(ref) as Page;
|
||||
},
|
||||
// @ts-expect-error -- ignore this
|
||||
generateParams(slug, lang) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ReactElement } from 'react';
|
||||
import type { I18nConfig } from '@/i18n';
|
||||
import { removeUndefined } from '@/utils/remove-undefined';
|
||||
import type * as PageTree from '../server/page-tree';
|
||||
import type { File, Folder, MetaFile, PageFile, Storage } from './file-system';
|
||||
import { resolvePath } from '@/utils/path';
|
||||
@@ -113,6 +112,7 @@ function resolveFolderItem(
|
||||
folder: Folder,
|
||||
item: string,
|
||||
ctx: PageTreeBuilderContext,
|
||||
idx: number,
|
||||
addedNodePaths: Set<string>,
|
||||
): PageTree.Node[] | typeof rest | typeof restReversed {
|
||||
if (item === rest || item === restReversed) return item;
|
||||
@@ -120,12 +120,13 @@ function resolveFolderItem(
|
||||
let match = separator.exec(item);
|
||||
if (match?.groups) {
|
||||
const node: PageTree.Separator = {
|
||||
$id: `${folder.file.path}#${idx}`,
|
||||
type: 'separator',
|
||||
icon: ctx.options.resolveIcon?.(match.groups.icon),
|
||||
name: match.groups.name,
|
||||
};
|
||||
|
||||
return [removeUndefined(ctx.options.attachSeparator?.(node) ?? node)];
|
||||
return [ctx.options.attachSeparator?.(node) ?? node];
|
||||
}
|
||||
|
||||
match = link.exec(item);
|
||||
@@ -142,7 +143,7 @@ function resolveFolderItem(
|
||||
external: !isRelative,
|
||||
};
|
||||
|
||||
return [removeUndefined(ctx.options.attachFile?.(node) ?? node)];
|
||||
return [ctx.options.attachFile?.(node) ?? node];
|
||||
}
|
||||
|
||||
const isExcept = item.startsWith(excludePrefix),
|
||||
@@ -187,20 +188,20 @@ function buildFolderNode(
|
||||
);
|
||||
|
||||
const metadata = meta?.data;
|
||||
const isRoot = metadata?.root ?? isGlobalRoot;
|
||||
const index = indexFile ? buildFileNode(indexFile, ctx) : undefined;
|
||||
|
||||
let children: PageTree.Node[];
|
||||
|
||||
if (!meta) {
|
||||
children = buildAll(folder.children, ctx, !isGlobalRoot);
|
||||
if (!metadata?.pages) {
|
||||
children = buildAll(folder.children, ctx, !isRoot);
|
||||
} else {
|
||||
const isRoot = metadata?.root ?? isGlobalRoot;
|
||||
const addedNodePaths = new Set<string>();
|
||||
|
||||
const resolved = metadata?.pages?.flatMap<
|
||||
PageTree.Node | typeof rest | typeof restReversed
|
||||
>((item) => {
|
||||
return resolveFolderItem(folder, item, ctx, addedNodePaths);
|
||||
>((item, i) => {
|
||||
return resolveFolderItem(folder, item, ctx, i, addedNodePaths);
|
||||
});
|
||||
|
||||
const restNodes = buildAll(
|
||||
@@ -235,6 +236,7 @@ function buildFolderNode(
|
||||
description: metadata?.description,
|
||||
index,
|
||||
children,
|
||||
$id: folder.file.path,
|
||||
$ref: !ctx.options.noRef
|
||||
? {
|
||||
metaFile: meta?.file.path,
|
||||
@@ -242,9 +244,7 @@ function buildFolderNode(
|
||||
: undefined,
|
||||
};
|
||||
|
||||
return removeUndefined(
|
||||
ctx.options.attachFolder?.(node, folder, meta) ?? node,
|
||||
);
|
||||
return ctx.options.attachFolder?.(node, folder, meta) ?? node;
|
||||
}
|
||||
|
||||
function buildFileNode(
|
||||
@@ -255,6 +255,7 @@ function buildFileNode(
|
||||
findLocalizedFile(file.file.flattenedPath, 'page', ctx) ?? file;
|
||||
|
||||
const item: PageTree.Item = {
|
||||
$id: localized.file.path,
|
||||
type: 'page',
|
||||
name: localized.data.data.title ?? pathToName(localized.file.name),
|
||||
icon: ctx.options.resolveIcon?.(localized.data.data.icon),
|
||||
@@ -266,7 +267,7 @@ function buildFileNode(
|
||||
: undefined,
|
||||
};
|
||||
|
||||
return removeUndefined(ctx.options.attachFile?.(item, file) ?? item);
|
||||
return ctx.options.attachFile?.(item, file) ?? item;
|
||||
}
|
||||
|
||||
function build(ctx: PageTreeBuilderContext): PageTree.Root {
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
export function removeUndefined<T extends object>(value: T): T {
|
||||
export function removeUndefined<T extends object>(value: T, deep = false): T {
|
||||
const obj = value as Record<string, unknown>;
|
||||
|
||||
for (const key of Object.keys(obj)) {
|
||||
if (obj[key] === undefined) delete obj[key];
|
||||
|
||||
if (deep && typeof obj[key] === 'object' && obj[key] !== null) {
|
||||
removeUndefined(obj[key], deep);
|
||||
} else if (deep && Array.isArray(obj[key])) {
|
||||
obj[key].forEach((v) => removeUndefined(v, deep));
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
|
||||
+230
-201
@@ -9,6 +9,7 @@ import {
|
||||
import { parseFilePath } from '@/source';
|
||||
import { PageTree } from '../dist/server';
|
||||
import type { ReactElement } from 'react';
|
||||
import { removeUndefined } from '@/utils/remove-undefined';
|
||||
|
||||
test('get slugs', () => {
|
||||
expect(getSlugs(parseFilePath('index.mdx'))).toStrictEqual([]);
|
||||
@@ -73,18 +74,20 @@ test('Loader: Simple', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.pageTree, 'Page Tree').toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/test",
|
||||
},
|
||||
],
|
||||
"name": "",
|
||||
}
|
||||
`);
|
||||
expect(removeUndefined(result.pageTree, true), 'Page Tree')
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"$id": "test.mdx",
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/test",
|
||||
},
|
||||
],
|
||||
"name": "",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(result.getPages().length).toBe(1);
|
||||
expect(result.getPage(['test'])).toBeDefined();
|
||||
@@ -144,53 +147,59 @@ test('Nested Directories', async () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.pageTree, 'Page Tree').toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/test",
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"name": "Route Group Page",
|
||||
"type": "page",
|
||||
"url": "/hello",
|
||||
},
|
||||
],
|
||||
"name": "Nested",
|
||||
"type": "folder",
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"name": "Nested Page",
|
||||
"type": "page",
|
||||
"url": "/nested/test",
|
||||
},
|
||||
],
|
||||
"name": "Nested",
|
||||
"type": "folder",
|
||||
},
|
||||
{
|
||||
"external": true,
|
||||
"name": "Text",
|
||||
"type": "page",
|
||||
"url": "https://google.com",
|
||||
},
|
||||
{
|
||||
"external": true,
|
||||
"icon": "Icon",
|
||||
"name": "Text",
|
||||
"type": "page",
|
||||
"url": "https://google.com",
|
||||
},
|
||||
],
|
||||
"name": "",
|
||||
}
|
||||
`);
|
||||
expect(removeUndefined(result.pageTree, true), 'Page Tree')
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"$id": "test.mdx",
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/test",
|
||||
},
|
||||
{
|
||||
"$id": "(nested)",
|
||||
"children": [
|
||||
{
|
||||
"$id": "(nested)/hello.mdx",
|
||||
"name": "Route Group Page",
|
||||
"type": "page",
|
||||
"url": "/hello",
|
||||
},
|
||||
],
|
||||
"name": "Nested",
|
||||
"type": "folder",
|
||||
},
|
||||
{
|
||||
"$id": "nested",
|
||||
"children": [
|
||||
{
|
||||
"$id": "nested/test.mdx",
|
||||
"name": "Nested Page",
|
||||
"type": "page",
|
||||
"url": "/nested/test",
|
||||
},
|
||||
],
|
||||
"name": "Nested",
|
||||
"type": "folder",
|
||||
},
|
||||
{
|
||||
"external": true,
|
||||
"name": "Text",
|
||||
"type": "page",
|
||||
"url": "https://google.com",
|
||||
},
|
||||
{
|
||||
"external": true,
|
||||
"icon": "Icon",
|
||||
"name": "Text",
|
||||
"type": "page",
|
||||
"url": "https://google.com",
|
||||
},
|
||||
],
|
||||
"name": "",
|
||||
}
|
||||
`);
|
||||
expect(result.getPages().length).toBe(4);
|
||||
// page in folder
|
||||
expect(result.getPage(['nested', 'test'])).toBeDefined();
|
||||
@@ -274,71 +283,77 @@ test('Internationalized Routing', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.pageTree['en'], 'Page Tree').toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"$ref": {
|
||||
"file": "test.mdx",
|
||||
},
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/en/test",
|
||||
},
|
||||
{
|
||||
"$ref": {
|
||||
"metaFile": undefined,
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"$ref": {
|
||||
"file": "nested/test.mdx",
|
||||
},
|
||||
"name": "Nested Page",
|
||||
"type": "page",
|
||||
"url": "/en/nested/test",
|
||||
expect(removeUndefined(result.pageTree['en'], true), 'Page Tree')
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"$id": "test.mdx",
|
||||
"$ref": {
|
||||
"file": "test.mdx",
|
||||
},
|
||||
],
|
||||
"name": "Nested",
|
||||
"type": "folder",
|
||||
},
|
||||
],
|
||||
"name": "",
|
||||
}
|
||||
`);
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/en/test",
|
||||
},
|
||||
{
|
||||
"$id": "nested",
|
||||
"$ref": {},
|
||||
"children": [
|
||||
{
|
||||
"$id": "nested/test.mdx",
|
||||
"$ref": {
|
||||
"file": "nested/test.mdx",
|
||||
},
|
||||
"name": "Nested Page",
|
||||
"type": "page",
|
||||
"url": "/en/nested/test",
|
||||
},
|
||||
],
|
||||
"name": "Nested",
|
||||
"type": "folder",
|
||||
},
|
||||
],
|
||||
"name": "",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(result.pageTree['cn'], 'Page Tree').toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"$ref": {
|
||||
"file": "test.cn.mdx",
|
||||
},
|
||||
"name": "Hello Chinese",
|
||||
"type": "page",
|
||||
"url": "/cn/test",
|
||||
},
|
||||
{
|
||||
"$ref": {
|
||||
"metaFile": "nested/meta.cn.json",
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"$ref": {
|
||||
"file": "nested/test.cn.mdx",
|
||||
},
|
||||
"name": "Nested Page Chinese",
|
||||
"type": "page",
|
||||
"url": "/cn/nested/test",
|
||||
expect(removeUndefined(result.pageTree['cn'], true), 'Page Tree')
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"$id": "test.cn.mdx",
|
||||
"$ref": {
|
||||
"file": "test.cn.mdx",
|
||||
},
|
||||
],
|
||||
"name": "Nested Chinese",
|
||||
"type": "folder",
|
||||
},
|
||||
],
|
||||
"name": "Docs Chinese",
|
||||
}
|
||||
`);
|
||||
"name": "Hello Chinese",
|
||||
"type": "page",
|
||||
"url": "/cn/test",
|
||||
},
|
||||
{
|
||||
"$id": "nested",
|
||||
"$ref": {
|
||||
"metaFile": "nested/meta.cn.json",
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"$id": "nested/test.cn.mdx",
|
||||
"$ref": {
|
||||
"file": "nested/test.cn.mdx",
|
||||
},
|
||||
"name": "Nested Page Chinese",
|
||||
"type": "page",
|
||||
"url": "/cn/nested/test",
|
||||
},
|
||||
],
|
||||
"name": "Nested Chinese",
|
||||
"type": "folder",
|
||||
},
|
||||
],
|
||||
"name": "Docs Chinese",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(result.getPages()).toMatchInlineSnapshot(`
|
||||
[
|
||||
@@ -481,53 +496,61 @@ test('Internationalized Routing: Hide Prefix', () => {
|
||||
source: i18nSource,
|
||||
});
|
||||
|
||||
expect(result.pageTree['en'], 'Page Tree').toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/test",
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"name": "Nested Page",
|
||||
"type": "page",
|
||||
"url": "/nested/test",
|
||||
},
|
||||
],
|
||||
"name": "Nested",
|
||||
"type": "folder",
|
||||
},
|
||||
],
|
||||
"name": "",
|
||||
}
|
||||
`);
|
||||
expect(removeUndefined(result.pageTree['en'], true), 'Page Tree')
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"$id": "test.mdx",
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/test",
|
||||
},
|
||||
{
|
||||
"$id": "nested",
|
||||
"children": [
|
||||
{
|
||||
"$id": "nested/test.mdx",
|
||||
"name": "Nested Page",
|
||||
"type": "page",
|
||||
"url": "/nested/test",
|
||||
},
|
||||
],
|
||||
"name": "Nested",
|
||||
"type": "folder",
|
||||
},
|
||||
],
|
||||
"name": "",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(result.pageTree['cn'], 'Page Tree').toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"name": "Hello Chinese",
|
||||
"type": "page",
|
||||
"url": "/cn/test",
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"name": "Nested Page Chinese",
|
||||
"type": "page",
|
||||
"url": "/cn/nested/test",
|
||||
},
|
||||
],
|
||||
"name": "Nested Chinese",
|
||||
"type": "folder",
|
||||
},
|
||||
],
|
||||
"name": "Docs Chinese",
|
||||
}
|
||||
`);
|
||||
expect(removeUndefined(result.pageTree['cn'], true), 'Page Tree')
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"$id": "test.cn.mdx",
|
||||
"name": "Hello Chinese",
|
||||
"type": "page",
|
||||
"url": "/cn/test",
|
||||
},
|
||||
{
|
||||
"$id": "nested",
|
||||
"children": [
|
||||
{
|
||||
"$id": "nested/test.cn.mdx",
|
||||
"name": "Nested Page Chinese",
|
||||
"type": "page",
|
||||
"url": "/cn/nested/test",
|
||||
},
|
||||
],
|
||||
"name": "Nested Chinese",
|
||||
"type": "folder",
|
||||
},
|
||||
],
|
||||
"name": "Docs Chinese",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(result.getPages().length).toBe(2);
|
||||
expect(result.getPage(['test'])?.url).toBe('/test');
|
||||
@@ -560,23 +583,26 @@ test('Loader: Without meta.json', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.pageTree, 'Page Tree').toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/hello",
|
||||
},
|
||||
{
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/test",
|
||||
},
|
||||
],
|
||||
"name": "",
|
||||
}
|
||||
`);
|
||||
expect(removeUndefined(result.pageTree, true), 'Page Tree')
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"$id": "hello/index.mdx",
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/hello",
|
||||
},
|
||||
{
|
||||
"$id": "test.mdx",
|
||||
"name": "Hello",
|
||||
"type": "page",
|
||||
"url": "/test",
|
||||
},
|
||||
],
|
||||
"name": "",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('Loader: Rest operator', () => {
|
||||
@@ -612,21 +638,24 @@ test('Loader: Rest operator', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.pageTree, 'Page Tree').toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"name": "2.2",
|
||||
"type": "page",
|
||||
"url": "/2-2",
|
||||
},
|
||||
{
|
||||
"name": "1.2",
|
||||
"type": "page",
|
||||
"url": "/1-2",
|
||||
},
|
||||
],
|
||||
"name": "",
|
||||
}
|
||||
`);
|
||||
expect(removeUndefined(result.pageTree, true), 'Page Tree')
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"$id": "2-2.mdx",
|
||||
"name": "2.2",
|
||||
"type": "page",
|
||||
"url": "/2-2",
|
||||
},
|
||||
{
|
||||
"$id": "1-2.mdx",
|
||||
"name": "1.2",
|
||||
"type": "page",
|
||||
"url": "/1-2",
|
||||
},
|
||||
],
|
||||
"name": "",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
'use client';
|
||||
import type { PageTree } from 'fumadocs-core/server';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { createContext, useContext, type ReactNode, useMemo } from 'react';
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
type ReactNode,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { searchPath } from 'fumadocs-core/breadcrumb';
|
||||
|
||||
type MakeRequired<O, K extends keyof O> = Omit<O, K> & Pick<Required<O>, K>;
|
||||
|
||||
interface TreeContextType {
|
||||
root: PageTree.Root | PageTree.Folder;
|
||||
root: MakeRequired<PageTree.Root | PageTree.Folder, '$id'>;
|
||||
}
|
||||
|
||||
const TreeContext = createContext<TreeContextType | null>(null);
|
||||
@@ -17,18 +25,22 @@ export function TreeContextProvider({
|
||||
}: {
|
||||
tree: PageTree.Root;
|
||||
children: ReactNode;
|
||||
}): ReactNode {
|
||||
}) {
|
||||
const nextIdRef = useRef(0);
|
||||
const pathname = usePathname();
|
||||
const path = useMemo(
|
||||
() => searchPath(tree.children, pathname) ?? [],
|
||||
[pathname, tree],
|
||||
);
|
||||
|
||||
const root = (path.findLast((item) => item.type === 'folder' && item.root) ??
|
||||
tree) as PageTree.Root;
|
||||
const root =
|
||||
path.findLast((item) => item.type === 'folder' && item.root) ?? tree;
|
||||
root.$id ??= String(nextIdRef.current++);
|
||||
|
||||
return (
|
||||
<TreeContext.Provider value={useMemo(() => ({ root }), [root])}>
|
||||
<TreeContext.Provider
|
||||
value={useMemo(() => ({ root: root as TreeContextType['root'] }), [root])}
|
||||
>
|
||||
<PathContext.Provider value={path}>{children}</PathContext.Provider>
|
||||
</TreeContext.Provider>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { usePathname } from 'next/navigation';
|
||||
import {
|
||||
type ButtonHTMLAttributes,
|
||||
createContext,
|
||||
Fragment,
|
||||
type HTMLAttributes,
|
||||
type PointerEventHandler,
|
||||
type ReactNode,
|
||||
@@ -462,8 +463,10 @@ export function SidebarPageTree(props: {
|
||||
});
|
||||
}
|
||||
|
||||
return renderSidebarList(root.children, 1);
|
||||
}, [props.components, root.children]);
|
||||
return (
|
||||
<Fragment key={root.$id}>{renderSidebarList(root.children, 1)}</Fragment>
|
||||
);
|
||||
}, [props.components, root.$id]);
|
||||
}
|
||||
|
||||
function PageTreeFolder({
|
||||
|
||||
@@ -52,7 +52,7 @@ const DefaultSearchDialog = dynamic(
|
||||
|
||||
export function RootProvider({
|
||||
children,
|
||||
dir,
|
||||
dir = 'ltr',
|
||||
theme: { enabled = true, ...theme } = {},
|
||||
search,
|
||||
}: RootProviderProps): React.ReactElement {
|
||||
@@ -79,7 +79,7 @@ export function RootProvider({
|
||||
);
|
||||
|
||||
return (
|
||||
<DirectionProvider dir={dir ?? 'ltr'}>
|
||||
<DirectionProvider dir={dir}>
|
||||
<SidebarProvider>{body}</SidebarProvider>
|
||||
</DirectionProvider>
|
||||
);
|
||||
|
||||
@@ -424,7 +424,7 @@ export const DEFAULT: Config = {
|
||||
|
||||
'a:not([data-card])': {
|
||||
color: 'var(--tw-prose-links)',
|
||||
transition: 'text-decoration .2s',
|
||||
transition: 'opacity .2s',
|
||||
fontWeight: '500',
|
||||
textDecoration: 'underline',
|
||||
textUnderlineOffset: '3.5px',
|
||||
@@ -432,7 +432,7 @@ export const DEFAULT: Config = {
|
||||
textDecorationThickness: '1.5px',
|
||||
},
|
||||
'a:not([data-card]):hover': {
|
||||
textDecorationThickness: '3px',
|
||||
opacity: '80%',
|
||||
},
|
||||
},
|
||||
colors,
|
||||
|
||||
Reference in New Issue
Block a user