|
1 | 1 | "use client"; |
2 | 2 |
|
3 | | -import { Fragment, useCallback, useEffect, useRef, useState } from "react"; |
| 3 | +import { Fragment, useEffect, useMemo, useRef, useState } from "react"; |
4 | 4 | import { ChatForm } from "./chatForm"; |
5 | 5 | import { StyledMarkdown } from "@/markdown/markdown"; |
6 | 6 | import { useSidebarMdContext } from "@/sidebar"; |
@@ -32,11 +32,41 @@ export function PageContent(props: PageContentProps) { |
32 | 32 | const { setSidebarMdContent } = useSidebarMdContext(); |
33 | 33 | const { splitMdContent, pageEntry, path, chatHistories } = props; |
34 | 34 |
|
35 | | - const initDynamicMdContent = useCallback(() => { |
| 35 | + const [sectionInView, setSectionInView] = useState<boolean[]>([]); |
| 36 | + const sectionRefs = useRef<Array<HTMLDivElement | null>>([]); |
| 37 | + useEffect(() => { |
| 38 | + const handleScroll = () => { |
| 39 | + setSectionInView((sectionInView) => { |
| 40 | + sectionInView = sectionInView.slice(); // Reactの変更検知のために新しい配列を作成 |
| 41 | + for ( |
| 42 | + let i = 0; |
| 43 | + i < sectionRefs.current.length || i < sectionInView.length; |
| 44 | + i++ |
| 45 | + ) { |
| 46 | + if (sectionRefs.current.at(i)) { |
| 47 | + const rect = sectionRefs.current.at(i)!.getBoundingClientRect(); |
| 48 | + sectionInView[i] = |
| 49 | + rect.top < window.innerHeight * 0.9 && |
| 50 | + rect.bottom >= window.innerHeight * 0.1; |
| 51 | + } else { |
| 52 | + sectionInView[i] = false; |
| 53 | + } |
| 54 | + } |
| 55 | + return sectionInView; |
| 56 | + }); |
| 57 | + }; |
| 58 | + window.addEventListener("scroll", handleScroll); |
| 59 | + handleScroll(); |
| 60 | + return () => { |
| 61 | + window.removeEventListener("scroll", handleScroll); |
| 62 | + }; |
| 63 | + }, []); |
| 64 | + |
| 65 | + const dynamicMdContent = useMemo(() => { |
36 | 66 | const newContent: DynamicMarkdownSection[] = splitMdContent.map( |
37 | | - (section) => ({ |
| 67 | + (section, i) => ({ |
38 | 68 | ...section, |
39 | | - inView: false, |
| 69 | + inView: sectionInView[i], |
40 | 70 | replacedContent: section.rawContent, |
41 | 71 | replacedRange: [], |
42 | 72 | }) |
@@ -94,54 +124,13 @@ export function PageContent(props: PageContentProps) { |
94 | 124 | } |
95 | 125 |
|
96 | 126 | return newContent; |
97 | | - }, [splitMdContent, chatHistories]); |
98 | | - |
99 | | - // SSR用のローカルstate |
100 | | - const [dynamicMdContent, setDynamicMdContent] = useState< |
101 | | - DynamicMarkdownSection[] |
102 | | - >(() => initDynamicMdContent()); |
| 127 | + }, [splitMdContent, chatHistories, sectionInView]); |
103 | 128 |
|
104 | 129 | useEffect(() => { |
105 | 130 | // props.splitMdContentが変わったとき, チャットのdiffが変わった時に |
106 | | - // ローカルstateとcontextの両方を更新 |
107 | | - const newContent = initDynamicMdContent(); |
108 | | - setDynamicMdContent(newContent); |
109 | | - setSidebarMdContent(path, newContent); |
110 | | - }, [initDynamicMdContent, path, setSidebarMdContent]); |
111 | | - |
112 | | - const sectionRefs = useRef<Array<HTMLDivElement | null>>([]); |
113 | | - // sectionRefsの長さをsplitMdContentに合わせる |
114 | | - while (sectionRefs.current.length < props.splitMdContent.length) { |
115 | | - sectionRefs.current.push(null); |
116 | | - } |
117 | | - |
118 | | - useEffect(() => { |
119 | | - const handleScroll = () => { |
120 | | - const updateContent = ( |
121 | | - prevDynamicMdContent: DynamicMarkdownSection[] |
122 | | - ) => { |
123 | | - const dynMdContent = prevDynamicMdContent.slice(); // Reactの変更検知のために新しい配列を作成 |
124 | | - for (let i = 0; i < sectionRefs.current.length; i++) { |
125 | | - if (sectionRefs.current.at(i) && dynMdContent.at(i)) { |
126 | | - const rect = sectionRefs.current.at(i)!.getBoundingClientRect(); |
127 | | - dynMdContent.at(i)!.inView = |
128 | | - rect.top < window.innerHeight * 0.9 && |
129 | | - rect.bottom >= window.innerHeight * 0.1; |
130 | | - } |
131 | | - } |
132 | | - return dynMdContent; |
133 | | - }; |
134 | | - |
135 | | - // ローカルstateとcontextの両方を更新 |
136 | | - setDynamicMdContent(updateContent); |
137 | | - setSidebarMdContent(path, updateContent); |
138 | | - }; |
139 | | - window.addEventListener("scroll", handleScroll); |
140 | | - handleScroll(); |
141 | | - return () => { |
142 | | - window.removeEventListener("scroll", handleScroll); |
143 | | - }; |
144 | | - }, [setSidebarMdContent, path]); |
| 131 | + // sidebarのcontextを更新 |
| 132 | + setSidebarMdContent(path, dynamicMdContent); |
| 133 | + }, [dynamicMdContent, path, setSidebarMdContent]); |
145 | 134 |
|
146 | 135 | const [isFormVisible, setIsFormVisible] = useState(false); |
147 | 136 |
|
|
0 commit comments