Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions app/[docs_id]/dynamicMdContext.tsx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot ページを切り替えた際に前のページのdynamicMdContentが残ってしまっています。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in f090192. Added cleanup effect that clears context when PageContent unmounts.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"use client";

import { createContext, ReactNode, useContext, useState } from "react";
import { DynamicMarkdownSection } from "./pageContent";

export interface ISidebarMdContext {
sidebarMdContent: DynamicMarkdownSection[];
setSidebarMdContent: React.Dispatch<React.SetStateAction<DynamicMarkdownSection[]>>;
}

const SidebarMdContext = createContext<ISidebarMdContext | null>(null);

export function useSidebarMdContext() {
const context = useContext(SidebarMdContext);
if (!context) {
throw new Error(
"useSidebarMdContext must be used within a SidebarMdProvider"
);
}
return context;
}

export function useSidebarMdContextOptional() {
return useContext(SidebarMdContext);
}

export function SidebarMdProvider({
children,
}: {
children: ReactNode;
}) {
const [sidebarMdContent, setSidebarMdContent] =
useState<DynamicMarkdownSection[]>([]);

return (
<SidebarMdContext.Provider value={{ sidebarMdContent, setSidebarMdContent }}>
{children}
</SidebarMdContext.Provider>
);
}
39 changes: 26 additions & 13 deletions app/[docs_id]/pageContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { MarkdownSection } from "./splitMarkdown";
import { ChatForm } from "./chatForm";
import { Heading, StyledMarkdown } from "./markdown";
import { useChatHistoryContext } from "./chatHistory";
import { useSidebarMdContext } from "./dynamicMdContext";
import clsx from "clsx";

// MarkdownSectionに追加で、ユーザーが今そのセクションを読んでいるかどうか、などの動的な情報を持たせる
Expand All @@ -19,26 +20,34 @@ interface PageContentProps {
docs_id: string;
}
export function PageContent(props: PageContentProps) {
const { setSidebarMdContent } = useSidebarMdContext();

// SSR用のローカルstate
const [dynamicMdContent, setDynamicMdContent] = useState<
DynamicMarkdownSection[]
>(
// useEffectで更新するのとは別に、SSRのための初期値
props.splitMdContent.map((section, i) => ({
...section,
inView: false,
sectionId: `${props.docs_id}-${i}`,
}))
);

useEffect(() => {
// props.splitMdContentが変わったときにdynamicMdContentを更新
setDynamicMdContent(
props.splitMdContent.map((section, i) => ({
...section,
inView: false,
sectionId: `${props.docs_id}-${i}`,
}))
);
}, [props.splitMdContent, props.docs_id]);
// props.splitMdContentが変わったときにローカルstateとcontextの両方を更新
const newContent = props.splitMdContent.map((section, i) => ({
...section,
inView: false,
sectionId: `${props.docs_id}-${i}`,
}));
setDynamicMdContent(newContent);
setSidebarMdContent(newContent);

// クリーンアップ:コンポーネントがアンマウントされたらcontextをクリア
return () => {
setSidebarMdContent([]);
};
}, [props.splitMdContent, props.docs_id, setSidebarMdContent]);

const sectionRefs = useRef<Array<HTMLDivElement | null>>([]);
// sectionRefsの長さをsplitMdContentに合わせる
Expand All @@ -48,7 +57,7 @@ export function PageContent(props: PageContentProps) {

useEffect(() => {
const handleScroll = () => {
setDynamicMdContent((prevDynamicMdContent) => {
const updateContent = (prevDynamicMdContent: DynamicMarkdownSection[]) => {
const dynMdContent = prevDynamicMdContent.slice(); // Reactの変更検知のために新しい配列を作成
for (let i = 0; i < sectionRefs.current.length; i++) {
if (sectionRefs.current.at(i) && dynMdContent.at(i)) {
Expand All @@ -58,14 +67,18 @@ export function PageContent(props: PageContentProps) {
}
}
return dynMdContent;
});
};

// ローカルstateとcontextの両方を更新
setDynamicMdContent(updateContent);
setSidebarMdContent(updateContent);
};
window.addEventListener("scroll", handleScroll);
handleScroll();
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
}, [setSidebarMdContent]);

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

Expand Down
39 changes: 21 additions & 18 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { PyodideProvider } from "./terminal/python/pyodide";
import { WandboxProvider } from "./terminal/wandbox/wandbox";
import { EmbedContextProvider } from "./terminal/embedContext";
import { AutoAnonymousLogin } from "./accountMenu";
import { SidebarMdProvider } from "./[docs_id]/dynamicMdContext";

export const metadata: Metadata = {
title: "Create Next App",
Expand All @@ -22,25 +23,27 @@ export default function RootLayout({
<html lang="ja">
<body className="w-screen h-screen">
<AutoAnonymousLogin />
<div className="drawer lg:drawer-open">
<input id="drawer-toggle" type="checkbox" className="drawer-toggle" />
<div className="drawer-content flex flex-col">
<Navbar />
<EmbedContextProvider>
<PyodideProvider>
<WandboxProvider>{children}</WandboxProvider>
</PyodideProvider>
</EmbedContextProvider>
<SidebarMdProvider>
<div className="drawer lg:drawer-open">
<input id="drawer-toggle" type="checkbox" className="drawer-toggle" />
<div className="drawer-content flex flex-col">
<Navbar />
<EmbedContextProvider>
<PyodideProvider>
<WandboxProvider>{children}</WandboxProvider>
</PyodideProvider>
</EmbedContextProvider>
</div>
<div className="drawer-side shadow-md z-50">
<label
htmlFor="drawer-toggle"
aria-label="close sidebar"
className="drawer-overlay"
/>
<Sidebar />
</div>
</div>
<div className="drawer-side shadow-md z-50">
<label
htmlFor="drawer-toggle"
aria-label="close sidebar"
className="drawer-overlay"
/>
<Sidebar />
</div>
</div>
</SidebarMdProvider>
</body>
</html>
);
Expand Down
44 changes: 25 additions & 19 deletions app/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import useSWR, { Fetcher } from "swr";
import { splitMarkdown } from "./[docs_id]/splitMarkdown";
import { pagesList } from "./pagesList";
import { AccountMenu } from "./accountMenu";
import { ThemeToggle } from "./[docs_id]/themeToggle";

const fetcher: Fetcher<string, string> = (url) =>
fetch(url).then((r) => r.text());
import { useSidebarMdContext } from "./[docs_id]/dynamicMdContext";

export function Sidebar() {
const pathname = usePathname();
const docs_id = pathname.replace(/^\//, "");
const { data, error, isLoading } = useSWR(`/docs/${docs_id}.md`, fetcher);

if (error) console.error("Sidebar fetch error:", error);

const splitmdcontent = splitMarkdown(data ?? "");
const { sidebarMdContent } = useSidebarMdContext();

// 現在表示中のセクション(最初にinViewがtrueのもの)を見つける
const currentSectionIndex = sidebarMdContent.findIndex(
(section) => section.inView
);
return (
<div className="bg-base-200 h-full w-80 overflow-y-auto">
{/* todo: 背景色ほんとにこれでいい? */}
Expand Down Expand Up @@ -65,16 +62,25 @@ export function Sidebar() {
<span className="mr-0">{page.id}.</span>
{page.title}
</Link>
{`${group.id}-${page.id}` === docs_id && !isLoading && (
{`${group.id}-${page.id}` === docs_id && sidebarMdContent.length > 0 && (
<ul className="ml-4 text-sm">
{splitmdcontent.slice(1).map((section, idx) => (
<li
key={idx}
style={{ marginLeft: section.level - 2 + "em" }}
>
<Link href={`#${idx + 1}`}>{section.title}</Link>
</li>
))}
{sidebarMdContent.slice(1).map((section, idx) => {
// idx + 1 は実際のsectionIndexに対応(slice(1)で最初を除外しているため)
const isCurrentSection = idx + 1 === currentSectionIndex;
return (
<li
key={idx}
style={{ marginLeft: section.level - 2 + "em" }}
>
<Link
href={`#${idx + 1}`}
className={isCurrentSection ? "font-bold" : ""}
>
{section.title}
</Link>
</li>
);
})}
</ul>
)}
</li>
Expand Down