seemore 1.12.0 → 1.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/app/layout/Sidebar.tsx +54 -9
- package/src/app/lib/tree.ts +32 -2
package/package.json
CHANGED
|
@@ -7,9 +7,13 @@ import {
|
|
|
7
7
|
SidebarItem as BaseItem,
|
|
8
8
|
SidebarSeparator as BaseSeparator,
|
|
9
9
|
SidebarViewport,
|
|
10
|
+
useFolderDepth,
|
|
10
11
|
useSidebar,
|
|
11
12
|
} from 'fumadocs-ui/components/sidebar/base';
|
|
12
13
|
import { createPageTreeRenderer } from 'fumadocs-ui/components/sidebar/page-tree';
|
|
14
|
+
import { useTreePath } from 'fumadocs-ui/contexts/tree';
|
|
15
|
+
import { usePathname } from 'fumadocs-core/framework';
|
|
16
|
+
import type { Folder as FolderNode } from 'fumadocs-core/page-tree';
|
|
13
17
|
import { feature } from '../lib/features.js';
|
|
14
18
|
import { useRouteUrl } from '../router.js';
|
|
15
19
|
|
|
@@ -43,6 +47,55 @@ const styled = {
|
|
|
43
47
|
|
|
44
48
|
const renderPageTree = createPageTreeRenderer(styled);
|
|
45
49
|
|
|
50
|
+
/** fumadocs' own active check, which it does not export: trailing slashes don't count. */
|
|
51
|
+
function isActive(href: string, pathname: string): boolean {
|
|
52
|
+
const normalise = (url: string) => (url.length > 1 && url.endsWith('/') ? url.slice(0, -1) : url);
|
|
53
|
+
return normalise(href) === normalise(pathname);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* `navigation.sections`: top-level folders read as headed groups rather than collapsible
|
|
58
|
+
* folders. A custom `Folder` replaces fumadocs' rendering at every depth, so anything deeper
|
|
59
|
+
* is rebuilt here the way fumadocs renders it — otherwise every nested folder would become an
|
|
60
|
+
* uncollapsible heading too, and a large one would push the rest of the sidebar away.
|
|
61
|
+
*/
|
|
62
|
+
function SectionFolder({ item, children }: { item: FolderNode; children: ReactNode }) {
|
|
63
|
+
// Depth of the folder this one sits in; 0 means it is at the top of the tree.
|
|
64
|
+
const depth = useFolderDepth();
|
|
65
|
+
const path = useTreePath();
|
|
66
|
+
const pathname = usePathname();
|
|
67
|
+
|
|
68
|
+
if (depth === 0) {
|
|
69
|
+
return (
|
|
70
|
+
<BaseFolder collapsible={false} defaultOpen className="seemore-sidebar-folder">
|
|
71
|
+
<BaseSeparator className="seemore-sidebar-separator">{item.name}</BaseSeparator>
|
|
72
|
+
<BaseFolderContent className="seemore-sidebar-folder-content">{children}</BaseFolderContent>
|
|
73
|
+
</BaseFolder>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<styled.SidebarFolder collapsible={item.collapsible} active={path.includes(item)} defaultOpen={item.defaultOpen}>
|
|
79
|
+
{item.index ? (
|
|
80
|
+
<styled.SidebarFolderLink
|
|
81
|
+
href={item.index.url}
|
|
82
|
+
active={isActive(item.index.url, pathname)}
|
|
83
|
+
external={item.index.external}
|
|
84
|
+
>
|
|
85
|
+
{item.icon}
|
|
86
|
+
{item.name}
|
|
87
|
+
</styled.SidebarFolderLink>
|
|
88
|
+
) : (
|
|
89
|
+
<styled.SidebarFolderTrigger>
|
|
90
|
+
{item.icon}
|
|
91
|
+
{item.name}
|
|
92
|
+
</styled.SidebarFolderTrigger>
|
|
93
|
+
)}
|
|
94
|
+
<styled.SidebarFolderContent>{children}</styled.SidebarFolderContent>
|
|
95
|
+
</styled.SidebarFolder>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
46
99
|
const COLLAPSE_KEY = 'seemore:sidebar-collapsed';
|
|
47
100
|
const COLLAPSE_ATTR = 'sidebarCollapsed';
|
|
48
101
|
|
|
@@ -118,15 +171,7 @@ export function Sidebar({ children }: { children?: ReactNode }) {
|
|
|
118
171
|
// Called during render, never inside `useMemo`: the renderer reads the tree context and
|
|
119
172
|
// calls hooks of its own.
|
|
120
173
|
const rendered = renderPageTree({
|
|
121
|
-
Folder: feature('navigation.sections')
|
|
122
|
-
? ({ item, children }) => (
|
|
123
|
-
// Top-level entries read as headed groups rather than collapsible folders.
|
|
124
|
-
<BaseFolder collapsible={false} defaultOpen className="seemore-sidebar-folder">
|
|
125
|
-
<BaseSeparator className="seemore-sidebar-separator">{item.name}</BaseSeparator>
|
|
126
|
-
<BaseFolderContent className="seemore-sidebar-folder-content">{children}</BaseFolderContent>
|
|
127
|
-
</BaseFolder>
|
|
128
|
-
)
|
|
129
|
-
: undefined,
|
|
174
|
+
Folder: feature('navigation.sections') ? SectionFolder : undefined,
|
|
130
175
|
});
|
|
131
176
|
|
|
132
177
|
return (
|
package/src/app/lib/tree.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useSyncExternalStore } from 'react';
|
|
2
|
-
import { deserializePageTree } from 'fumadocs-core/source/client';
|
|
2
|
+
import { deserializePageTree, type SerializedPageTree } from 'fumadocs-core/source/client';
|
|
3
3
|
import type * as PageTree from 'fumadocs-core/page-tree';
|
|
4
4
|
import { getTree, subscribeTree } from 'virtual:seemore/tree';
|
|
5
5
|
|
|
@@ -12,7 +12,37 @@ import { getTree, subscribeTree } from 'virtual:seemore/tree';
|
|
|
12
12
|
*/
|
|
13
13
|
export function usePageTree(): PageTree.Root {
|
|
14
14
|
const serialized = useSyncExternalStore(subscribeTree, getTree, getTree);
|
|
15
|
-
return
|
|
15
|
+
return resolveTree(serialized);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let current: { serialized: SerializedPageTree; content: string; tree: PageTree.Root } | undefined;
|
|
19
|
+
let revision = 0;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The deserialized tree for a payload, with a root `$id` that moves whenever the tree does.
|
|
23
|
+
*
|
|
24
|
+
* fumadocs' `TreeContextProvider` memoises its tree on `root.$id`, and the loader always calls
|
|
25
|
+
* the root "root" — so without a fresh id a created, renamed or retitled page reaches the
|
|
26
|
+
* store and never the sidebar. The id only moves when the content differs: the store is
|
|
27
|
+
* replaced on body edits too, and the sidebar list is keyed on that id, so bumping it for
|
|
28
|
+
* nothing would remount the list and close every folder the reader opened, on every save.
|
|
29
|
+
* The first tree keeps its id, so prerendered HTML and hydration agree. Exported for the
|
|
30
|
+
* tree store test.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveTree(serialized: SerializedPageTree): PageTree.Root {
|
|
33
|
+
if (current?.serialized === serialized) return current.tree;
|
|
34
|
+
|
|
35
|
+
// Measured before deserialising: fumadocs rewrites names and icons into elements in place.
|
|
36
|
+
const content = JSON.stringify(serialized);
|
|
37
|
+
if (current?.content === content) {
|
|
38
|
+
current = { ...current, serialized };
|
|
39
|
+
return current.tree;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const tree = deserializePageTree(serialized);
|
|
43
|
+
if (current !== undefined) tree.$id = `${tree.$id ?? 'root'}:${++revision}`;
|
|
44
|
+
current = { serialized, content, tree };
|
|
45
|
+
return tree;
|
|
16
46
|
}
|
|
17
47
|
|
|
18
48
|
/**
|