Kaynağa Gözat

feat:editor: sync scroll position in editor/preview

Adam Jafarov 3 hafta önce
ebeveyn
işleme
6b032a7f86
3 değiştirilmiş dosya ile 59 ekleme ve 4 silme
  1. 1 1
      public/posts
  2. 45 2
      src/components/PostEditor.jsx
  3. 13 1
      src/utils/markdownParser.js

+ 1 - 1
public/posts

@@ -1 +1 @@
-Subproject commit aa31352e956941d12f9ac14a7b50d571d7790bec
+Subproject commit 3434e83782a318ac05d0dfeb100f7ef07b78cb0d

+ 45 - 2
src/components/PostEditor.jsx

@@ -24,6 +24,7 @@ function PostEditor() {
         tags: "",
         hidden: false,
     });
+    const [cursorLine, setCursorLine] = useState(0);
 
     const [loading, setLoading] = useState(isEditing);
     const [saving, setSaving] = useState(false);
@@ -81,8 +82,27 @@ function PostEditor() {
         }
     };
 
-    const handleInputChange = (field, value) => {
+    const handleInputChange = (field, value, event) => {
         setFormData((prev) => ({ ...prev, [field]: value }));
+
+        // Track cursor line for sync
+        if (field === "content" && event) {
+            // MDEditor's onChange passes the value, but we need the event or access to the textarea
+            // However, uiw/react-md-editor's onChange only gives value.
+            // We need to use onSelect or capture it from the underlying textarea.
+        }
+    };
+
+    // Improved cursor tracker
+    const handleEditorChange = (value, event, state) => {
+        handleInputChange("content", value || "");
+
+        // Calculate line number from selection start
+        // MDEditor expose state with selection
+        if (state && state.selection && state.selection.start) {
+            const lines = value.substr(0, state.selection.start).split("\n");
+            setCursorLine(lines.length);
+        }
     };
 
     const insertTextAtCursor = (text) => {
@@ -229,6 +249,29 @@ function PostEditor() {
         }
     }, [formData.title]);
 
+    // Scroll Sync Effect
+    useEffect(() => {
+        if (activeTab === "preview") {
+            // Find the element with the closest data-line attribute
+            // We search for elements with data-line <= cursorLine, picking the largest one
+            const elements = document.querySelectorAll('[data-line]');
+            let target = null;
+            let maxLine = -1;
+
+            elements.forEach(el => {
+                const line = parseInt(el.getAttribute('data-line'), 10);
+                if (line <= cursorLine && line > maxLine) {
+                    maxLine = line;
+                    target = el;
+                }
+            });
+
+            if (target) {
+                target.scrollIntoView({ behavior: 'smooth', block: 'center' });
+            }
+        }
+    }, [activeTab]);
+
     // ... existing interactions ...
 
     return (
@@ -341,7 +384,7 @@ function PostEditor() {
                             <div className="prose-editor-wrapper -mx-4 md:mx-0">
                                 <MDEditor
                                     value={formData.content}
-                                    onChange={(value) => handleInputChange("content", value || "")}
+                                    onChange={handleEditorChange}
                                     commands={[...commands.getCommands(), ...customCommands]}
                                     height={window.innerHeight * 0.7} // Increased height to 70% of viewport
                                     minHeight={500}

+ 13 - 1
src/utils/markdownParser.js

@@ -172,6 +172,17 @@ const imageComparisonPlugin = (md) => {
     };
 };
 
+// Plugin to inject line numbers for scroll sync
+const injectLineNumbers = (md) => {
+    md.core.ruler.push('inject_line_numbers', (state) => {
+        state.tokens.forEach((token) => {
+            if (token.map && token.level === 0) {
+                token.attrSet('data-line', token.map[0]);
+            }
+        });
+    });
+};
+
 import hljs from "highlight.js";
 
 // ... existing imports ...
@@ -207,7 +218,8 @@ export const createMarkdownParser = () => {
         .use(emoji)
         .use(footnote)
         .use(imageResizePlugin)
-        .use(imageComparisonPlugin);
+        .use(imageComparisonPlugin)
+        .use(injectLineNumbers);
 
     return md;
 };