Skip to content

Commit 8fcfe2c

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

File tree

4 files changed

+135
-76
lines changed

4 files changed

+135
-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: 130 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,107 @@ 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+
// 表示しているグループが変わったときに現在のグループのdetailsを開く
96+
if (currentGroupIndex !== -1) {
97+
setDetailsOpen((detailsOpen) => {
98+
const newDetailsOpen = [...detailsOpen];
99+
while (newDetailsOpen.length <= currentGroupIndex) {
100+
newDetailsOpen.push(false);
101+
}
102+
newDetailsOpen[currentGroupIndex] = true;
103+
return newDetailsOpen;
104+
});
105+
}
106+
}, [currentGroupIndex]);
107+
18108
return (
19109
<div className="bg-base-200 h-full w-80 overflow-y-auto">
20110
{/* todo: 背景色ほんとにこれでいい? */}
@@ -51,9 +141,19 @@ export function Sidebar() {
51141
</span>
52142

53143
<ul className="menu w-full">
54-
{pagesList.map((group) => (
144+
{pagesList.map((group, i) => (
55145
<li key={group.id}>
56-
<details open={docs_id.startsWith(`${group.id}-`)}>
146+
<details
147+
open={!!detailsOpen.at(i)}
148+
onToggle={(e) => {
149+
const newDetailsOpen = [...detailsOpen];
150+
while (newDetailsOpen.length <= i) {
151+
newDetailsOpen.push(false);
152+
}
153+
newDetailsOpen[i] = e.currentTarget.open;
154+
setDetailsOpen(newDetailsOpen);
155+
}}
156+
>
57157
<summary>{group.lang}</summary>
58158
<ul>
59159
{group.pages.map((page) => (
@@ -62,27 +162,31 @@ export function Sidebar() {
62162
<span className="mr-0">{page.id}.</span>
63163
{page.title}
64164
</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" : ""}
165+
{`${group.id}-${page.id}` === currentDocsId &&
166+
sidebarMdContent.length > 0 && (
167+
<ul className="ml-4 text-sm">
168+
{sidebarMdContent.slice(1).map((section, idx) => {
169+
// idx + 1 は実際のsectionIndexに対応(slice(1)で最初を除外しているため)
170+
const isCurrentSection =
171+
idx + 1 === currentSectionIndex;
172+
return (
173+
<li
174+
key={idx}
175+
style={{ marginLeft: section.level - 2 + "em" }}
78176
>
79-
{section.title}
80-
</Link>
81-
</li>
82-
);
83-
})}
84-
</ul>
85-
)}
177+
<Link
178+
href={`#${idx + 1}`}
179+
className={
180+
isCurrentSection ? "font-bold" : ""
181+
}
182+
>
183+
{section.title}
184+
</Link>
185+
</li>
186+
);
187+
})}
188+
</ul>
189+
)}
86190
</li>
87191
))}
88192
</ul>

0 commit comments

Comments
 (0)