Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 IDynamicMdContext {
dynamicMdContent: DynamicMarkdownSection[];
setDynamicMdContent: React.Dispatch<React.SetStateAction<DynamicMarkdownSection[]>>;
}

const DynamicMdContext = createContext<IDynamicMdContext | null>(null);

export function useDynamicMdContext() {
const context = useContext(DynamicMdContext);
if (!context) {
throw new Error(
"useDynamicMdContext must be used within a DynamicMdProvider"
);
}
return context;
}

export function useDynamicMdContextOptional() {
return useContext(DynamicMdContext);
}

export function DynamicMdProvider({
children,
}: {
children: ReactNode;
}) {
const [dynamicMdContent, setDynamicMdContent] =
useState<DynamicMarkdownSection[]>([]);

return (
<DynamicMdContext.Provider value={{ dynamicMdContent, setDynamicMdContent }}>
{children}
</DynamicMdContext.Provider>
);
}
40 changes: 24 additions & 16 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 { useDynamicMdContext } from "./dynamicMdContext";
import clsx from "clsx";

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

// SSR用のローカルstate
const [localDynamicMdContent, setLocalDynamicMdContent] = 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}`,
}));
setLocalDynamicMdContent(newContent);
setDynamicMdContent(newContent);
}, [props.splitMdContent, props.docs_id, setDynamicMdContent]);

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

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

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

Expand All @@ -78,7 +86,7 @@ export function PageContent(props: PageContentProps) {
gridTemplateColumns: `1fr auto`,
}}
>
{dynamicMdContent.map((section, index) => (
{localDynamicMdContent.map((section, index) => (
<>
<div
className="max-w-200"
Expand Down Expand Up @@ -134,7 +142,7 @@ export function PageContent(props: PageContentProps) {
<div className="fixed bottom-4 right-4 left-4 lg:left-84 z-20">
<ChatForm
documentContent={props.documentContent}
sectionContent={dynamicMdContent}
sectionContent={localDynamicMdContent}
docs_id={props.docs_id}
close={() => setIsFormVisible(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 { DynamicMdProvider } 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>
<DynamicMdProvider>
<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>
</DynamicMdProvider>
</body>
</html>
);
Expand Down
42 changes: 32 additions & 10 deletions app/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,31 @@ import { splitMarkdown } from "./[docs_id]/splitMarkdown";
import { pagesList } from "./pagesList";
import { AccountMenu } from "./accountMenu";
import { ThemeToggle } from "./[docs_id]/themeToggle";
import { useDynamicMdContext } from "./[docs_id]/dynamicMdContext";

const fetcher: Fetcher<string, string> = (url) =>
fetch(url).then((r) => r.text());

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

// コンテキストが空の場合(まだページがロードされていない場合)はフェッチする
const { data, error, isLoading } = useSWR(
dynamicMdContent.length === 0 ? `/docs/${docs_id}.md` : null,
fetcher
);
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot ページがロードされる時間とこれをfetchする時間はほぼ変わらないので、このfetchは要らないです

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. Removed the SWR fetch - sidebar now only uses context data.


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

const splitmdcontent = splitMarkdown(data ?? "");
// コンテキストがある場合はそれを使用、ない場合はフェッチしたデータを使用
const splitmdcontent = dynamicMdContent.length > 0 ? dynamicMdContent : splitMarkdown(data ?? "");

// 現在表示中のセクション(最初にinViewがtrueのもの)を見つける
const currentSectionIndex = dynamicMdContent.findIndex(
(section) => section.inView
);
return (
<div className="bg-base-200 h-full w-80 overflow-y-auto">
{/* todo: 背景色ほんとにこれでいい? */}
Expand Down Expand Up @@ -67,14 +80,23 @@ export function Sidebar() {
</Link>
{`${group.id}-${page.id}` === docs_id && !isLoading && (
<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>
))}
{splitmdcontent.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