Selaa lähdekoodia

feat: add synchronized scrolling to post editor

Adam Jafarov 3 viikkoa sitten
vanhempi
sitoutus
93356461a4
1 muutettua tiedostoa jossa 36 lisäystä ja 35 poistoa
  1. 36 35
      src/components/PostEditor.jsx

+ 36 - 35
src/components/PostEditor.jsx

@@ -313,67 +313,69 @@ function PostEditor() {
     const [settingsOpen, setSettingsOpen] = useState(false);
 
     const previewRef = useRef(null);
+    const editorWrapperRef = useRef(null);
     const isScrollingRef = useRef(false);
 
-    // Scroll Sync: Editor -> Preview
-    const handleEditorScroll = (e) => {
-        if (!previewRef.current || viewMode !== "split") return;
+    // Scroll Sync: Editor -> Preview (percentage-based)
+    const syncEditorToPreview = () => {
+        if (!previewRef.current || !editorWrapperRef.current || viewMode !== "split") return;
         if (isScrollingRef.current) return;
 
+        const scrollContainer = editorWrapperRef.current.querySelector('.w-md-editor-area');
+        if (!scrollContainer) return;
+
         isScrollingRef.current = true;
-        const textarea = e.target;
-        const { scrollTop, scrollHeight, clientHeight } = textarea;
+        const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
+        const scrollPercent = scrollHeight > clientHeight ? scrollTop / (scrollHeight - clientHeight) : 0;
 
-        const scrollPercent = scrollTop / (scrollHeight - clientHeight);
         const previewNode = previewRef.current;
-
-        if (previewNode) {
+        if (previewNode.scrollHeight > previewNode.clientHeight) {
             previewNode.scrollTop = scrollPercent * (previewNode.scrollHeight - previewNode.clientHeight);
         }
 
         setTimeout(() => { isScrollingRef.current = false; }, 50);
     };
 
-    // Scroll Sync: Preview -> Editor
-    const handlePreviewScroll = (e) => {
-        if (viewMode !== "split") return;
+    // Scroll Sync: Preview -> Editor (percentage-based)
+    const syncPreviewToEditor = () => {
+        if (!previewRef.current || !editorWrapperRef.current || viewMode !== "split") return;
         if (isScrollingRef.current) return;
 
         isScrollingRef.current = true;
-        const previewNode = e.target;
+        const previewNode = previewRef.current;
         const { scrollTop, scrollHeight, clientHeight } = previewNode;
+        const scrollPercent = scrollHeight > clientHeight ? scrollTop / (scrollHeight - clientHeight) : 0;
 
-        const scrollPercent = scrollTop / (scrollHeight - clientHeight);
-
-        if (editorWrapperRef.current) {
-            const textarea = editorWrapperRef.current.querySelector('textarea');
-            if (textarea) {
-                textarea.scrollTop = scrollPercent * (textarea.scrollHeight - textarea.clientHeight);
-            }
+        const scrollContainer = editorWrapperRef.current.querySelector('.w-md-editor-area');
+        if (scrollContainer && scrollContainer.scrollHeight > scrollContainer.clientHeight) {
+            scrollContainer.scrollTop = scrollPercent * (scrollContainer.scrollHeight - scrollContainer.clientHeight);
         }
 
         setTimeout(() => { isScrollingRef.current = false; }, 50);
     };
 
-    // Handle Scroll Sync using capture on wrapper since MDEditor might consume scroll on textarea
-    // or better, ensure we target the right element.
-    // The previous textareaProps onScroll should have worked if `MDEditor` passes it.
-    // Let's try attaching it to the editor wrapper with capture to be safe.
-    const editorWrapperRef = useRef(null);
-
+    // Attach scroll listeners to both containers
     useEffect(() => {
+        if (viewMode !== "split") return;
+
         const wrapper = editorWrapperRef.current;
-        if (!wrapper) return;
+        const preview = previewRef.current;
+        if (!wrapper || !preview) return;
 
-        // Find the textarea inside the wrapper
-        const textarea = wrapper.querySelector('textarea');
-        if (!textarea) return;
+        const scrollContainer = wrapper.querySelector('.w-md-editor-area');
+        if (!scrollContainer) return;
 
-        // Attach scroll listener manually to ensure it's registered
-        const handleScroll = (e) => handleEditorScroll(e);
-        textarea.addEventListener('scroll', handleScroll);
-        return () => textarea.removeEventListener('scroll', handleScroll);
-    }, [viewMode]); // Re-attach if viewMode changes (though refs persist)
+        const handleEditorScroll = () => syncEditorToPreview();
+        const handlePreviewScroll = () => syncPreviewToEditor();
+
+        scrollContainer.addEventListener('scroll', handleEditorScroll);
+        preview.addEventListener('scroll', handlePreviewScroll);
+
+        return () => {
+            scrollContainer.removeEventListener('scroll', handleEditorScroll);
+            preview.removeEventListener('scroll', handlePreviewScroll);
+        };
+    }, [viewMode]);
 
     // Auto-resize title textarea
     const titleRef = React.useRef(null);
@@ -568,7 +570,6 @@ function PostEditor() {
                             {/* Preview Column - Keep always mounted but hidden if needed */}
                             <article
                                 ref={previewRef}
-                                onScroll={handlePreviewScroll}
                                 className={`markdown-content prose dark:prose-invert max-w-none prose-lg ${viewMode === "split" ? "border-l pl-6 border-gray-200 dark:border-gray-800" : ""} ${activeTab !== "preview" && viewMode !== "split" ? "hidden" : ""}`}
                                 style={{
                                     height: viewMode === "split" ? window.innerHeight * 0.8 : 'auto',