Skip to content

Commit 2ee88b7

Browse files
committed
ページ読み込み時のサイドバーの動作を修正
1 parent 01196a8 commit 2ee88b7

File tree

4 files changed

+133
-76
lines changed

4 files changed

+133
-76
lines changed

app/[docs_id]/dynamicMdContext.tsx

Lines changed: 0 additions & 40 deletions
This file was deleted.

app/[docs_id]/pageContent.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { MarkdownSection } from "./splitMarkdown";
55
import { ChatForm } from "./chatForm";
66
import { Heading, StyledMarkdown } from "./markdown";
77
import { useChatHistoryContext } from "./chatHistory";
8-
import { useSidebarMdContext } from "./dynamicMdContext";
8+
import { useSidebarMdContext } from "../sidebar";
99
import clsx from "clsx";
1010

1111
// MarkdownSectionに追加で、ユーザーが今そのセクションを読んでいるかどうか、などの動的な情報を持たせる
@@ -41,12 +41,7 @@ export function PageContent(props: PageContentProps) {
4141
sectionId: `${props.docs_id}-${i}`,
4242
}));
4343
setDynamicMdContent(newContent);
44-
setSidebarMdContent(newContent);
45-
46-
// クリーンアップ:コンポーネントがアンマウントされたらcontextをクリア
47-
return () => {
48-
setSidebarMdContent([]);
49-
};
44+
setSidebarMdContent(props.docs_id, newContent);
5045
}, [props.splitMdContent, props.docs_id, setSidebarMdContent]);
5146

5247
const sectionRefs = useRef<Array<HTMLDivElement | null>>([]);
@@ -71,14 +66,14 @@ export function PageContent(props: PageContentProps) {
7166

7267
// ローカルstateとcontextの両方を更新
7368
setDynamicMdContent(updateContent);
74-
setSidebarMdContent(updateContent);
69+
setSidebarMdContent(props.docs_id, updateContent);
7570
};
7671
window.addEventListener("scroll", handleScroll);
7772
handleScroll();
7873
return () => {
7974
window.removeEventListener("scroll", handleScroll);
8075
};
81-
}, [setSidebarMdContent]);
76+
}, [setSidebarMdContent, props.docs_id]);
8277

8378
const [isFormVisible, setIsFormVisible] = useState(false);
8479

app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Sidebar } from "./sidebar";
77
import { ReactNode } from "react";
88
import { EmbedContextProvider } from "./terminal/embedContext";
99
import { AutoAnonymousLogin } from "./accountMenu";
10-
import { SidebarMdProvider } from "./[docs_id]/dynamicMdContext";
10+
import { SidebarMdProvider } from "./sidebar";
1111
import { RuntimeProvider } from "./terminal/runtime";
1212

1313
export const metadata: Metadata = {

app/sidebar.tsx

Lines changed: 128 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,105 @@ import { usePathname } from "next/navigation";
44
import { pagesList } from "./pagesList";
55
import { AccountMenu } from "./accountMenu";
66
import { ThemeToggle } from "./[docs_id]/themeToggle";
7-
import { useSidebarMdContext } from "./[docs_id]/dynamicMdContext";
7+
import {
8+
createContext,
9+
ReactNode,
10+
useCallback,
11+
useContext,
12+
useEffect,
13+
useState,
14+
} from "react";
15+
import { DynamicMarkdownSection } from "./[docs_id]/pageContent";
16+
17+
export interface ISidebarMdContext {
18+
loadedDocsId: string;
19+
sidebarMdContent: DynamicMarkdownSection[];
20+
setSidebarMdContent: (
21+
docsId: string,
22+
content:
23+
| DynamicMarkdownSection[]
24+
| ((prev: DynamicMarkdownSection[]) => DynamicMarkdownSection[])
25+
) => void;
26+
}
27+
28+
const SidebarMdContext = createContext<ISidebarMdContext | null>(null);
29+
30+
export function useSidebarMdContext() {
31+
const context = useContext(SidebarMdContext);
32+
if (!context) {
33+
throw new Error(
34+
"useSidebarMdContext must be used within a SidebarMdProvider"
35+
);
36+
}
37+
return context;
38+
}
39+
40+
export function useSidebarMdContextOptional() {
41+
return useContext(SidebarMdContext);
42+
}
43+
44+
export function SidebarMdProvider({ children }: { children: ReactNode }) {
45+
const [sidebarMdContent, setSidebarMdContent_] = useState<
46+
DynamicMarkdownSection[]
47+
>([]);
48+
const [loadedDocsId, setLoadedDocsId] = useState<string>("");
49+
const setSidebarMdContent = useCallback(
50+
(
51+
docsId: string,
52+
content:
53+
| DynamicMarkdownSection[]
54+
| ((prev: DynamicMarkdownSection[]) => DynamicMarkdownSection[])
55+
) => {
56+
setLoadedDocsId(docsId);
57+
setSidebarMdContent_(content);
58+
},
59+
[]
60+
);
61+
return (
62+
<SidebarMdContext.Provider
63+
value={{
64+
loadedDocsId,
65+
sidebarMdContent,
66+
setSidebarMdContent,
67+
}}
68+
>
69+
{children}
70+
</SidebarMdContext.Provider>
71+
);
72+
}
873

974
export function Sidebar() {
1075
const pathname = usePathname();
11-
const docs_id = pathname.replace(/^\//, "");
12-
const { sidebarMdContent } = useSidebarMdContext();
13-
76+
const currentDocsId = pathname.replace(/^\//, ""); // ちょっと遅延がある
77+
const sidebarContext = useSidebarMdContext();
78+
// sidebarMdContextの情報が古かったら使わない
79+
const sidebarMdContent =
80+
sidebarContext.loadedDocsId === currentDocsId
81+
? sidebarContext.sidebarMdContent
82+
: [];
83+
1484
// 現在表示中のセクション(最初にinViewがtrueのもの)を見つける
1585
const currentSectionIndex = sidebarMdContent.findIndex(
1686
(section) => section.inView
1787
);
88+
89+
// 目次の開閉状態
90+
const [detailsOpen, setDetailsOpen] = useState<boolean[]>([]);
91+
const currentGroupIndex = pagesList.findIndex((group) =>
92+
currentDocsId.startsWith(`${group.id}-`)
93+
);
94+
useEffect(() => {
95+
if (currentGroupIndex !== -1 && !detailsOpen[currentGroupIndex]) {
96+
const newDetailsOpen = [...detailsOpen];
97+
// 現在のグループのdetailsは開いておく
98+
while (newDetailsOpen.length <= currentGroupIndex) {
99+
newDetailsOpen.push(false);
100+
}
101+
newDetailsOpen[currentGroupIndex] = true;
102+
setDetailsOpen(newDetailsOpen);
103+
}
104+
}, [currentGroupIndex, detailsOpen]);
105+
18106
return (
19107
<div className="bg-base-200 h-full w-80 overflow-y-auto">
20108
{/* todo: 背景色ほんとにこれでいい? */}
@@ -51,9 +139,19 @@ export function Sidebar() {
51139
</span>
52140

53141
<ul className="menu w-full">
54-
{pagesList.map((group) => (
142+
{pagesList.map((group, i) => (
55143
<li key={group.id}>
56-
<details open={docs_id.startsWith(`${group.id}-`)}>
144+
<details
145+
open={!!detailsOpen.at(i)}
146+
onToggle={(e) => {
147+
const newDetailsOpen = [...detailsOpen];
148+
while (newDetailsOpen.length <= i) {
149+
newDetailsOpen.push(false);
150+
}
151+
newDetailsOpen[i] = e.currentTarget.open;
152+
setDetailsOpen(newDetailsOpen);
153+
}}
154+
>
57155
<summary>{group.lang}</summary>
58156
<ul>
59157
{group.pages.map((page) => (
@@ -62,27 +160,31 @@ export function Sidebar() {
62160
<span className="mr-0">{page.id}.</span>
63161
{page.title}
64162
</Link>
65-
{`${group.id}-${page.id}` === docs_id && sidebarMdContent.length > 0 && (
66-
<ul className="ml-4 text-sm">
67-
{sidebarMdContent.slice(1).map((section, idx) => {
68-
// idx + 1 は実際のsectionIndexに対応(slice(1)で最初を除外しているため)
69-
const isCurrentSection = idx + 1 === currentSectionIndex;
70-
return (
71-
<li
72-
key={idx}
73-
style={{ marginLeft: section.level - 2 + "em" }}
74-
>
75-
<Link
76-
href={`#${idx + 1}`}
77-
className={isCurrentSection ? "font-bold" : ""}
163+
{`${group.id}-${page.id}` === currentDocsId &&
164+
sidebarMdContent.length > 0 && (
165+
<ul className="ml-4 text-sm">
166+
{sidebarMdContent.slice(1).map((section, idx) => {
167+
// idx + 1 は実際のsectionIndexに対応(slice(1)で最初を除外しているため)
168+
const isCurrentSection =
169+
idx + 1 === currentSectionIndex;
170+
return (
171+
<li
172+
key={idx}
173+
style={{ marginLeft: section.level - 2 + "em" }}
78174
>
79-
{section.title}
80-
</Link>
81-
</li>
82-
);
83-
})}
84-
</ul>
85-
)}
175+
<Link
176+
href={`#${idx + 1}`}
177+
className={
178+
isCurrentSection ? "font-bold" : ""
179+
}
180+
>
181+
{section.title}
182+
</Link>
183+
</li>
184+
);
185+
})}
186+
</ul>
187+
)}
86188
</li>
87189
))}
88190
</ul>

0 commit comments

Comments
 (0)