From c01b525e3e6fbd586ff613da5325e9c2c07fcb11 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Jan 2026 10:48:17 +0000 Subject: [PATCH] refactor(post): replace intervals with debounced onChange handlers This commit improves the code quality and UX of the POST command: **Debounced onChange Pattern:** - Added onChange prop to RichEditor that fires on every content change - Replaced interval-based updates with proper debounced handlers - Draft saves debounced to 2000ms (unchanged behavior, cleaner code) - JSON updates debounced to 200ms (same responsiveness, event-driven) - Much cleaner React pattern - no weird intervals **JSON Preview Improvements:** - Removed "Event JSON" title header for cleaner look - Shows signed event JSON when published and setting is enabled - Shows draft (unsigned) JSON when composing - Always scrollable up to 400px height **Technical Improvements:** - Added onUpdate handler to TipTap editor config - Proper cleanup of debounce timeouts on unmount - Type-safe timeout refs using ReturnType --- src/components/PostViewer.tsx | 94 +++++++++++++++++++--------- src/components/editor/RichEditor.tsx | 5 ++ 2 files changed, 71 insertions(+), 28 deletions(-) diff --git a/src/components/PostViewer.tsx b/src/components/PostViewer.tsx index db80e7b..d507ed8 100644 --- a/src/components/PostViewer.tsx +++ b/src/components/PostViewer.tsx @@ -300,27 +300,50 @@ export function PostViewer({ windowId }: PostViewerProps = {}) { setDraftEventJson(draftEvent); }, [pubkey, settings.includeClientTag]); - // Debounced draft save (save every 2 seconds of inactivity) - useEffect(() => { - const timer = setInterval(() => { + // Debounced handlers for editor changes + const draftSaveTimeoutRef = useRef | null>( + null, + ); + const jsonUpdateTimeoutRef = useRef | null>( + null, + ); + + const handleEditorChange = useCallback(() => { + // Update empty state immediately + if (editorRef.current) { + setIsEditorEmpty(editorRef.current.isEmpty()); + } + + // Debounce draft save (2 seconds) + if (draftSaveTimeoutRef.current) { + clearTimeout(draftSaveTimeoutRef.current); + } + draftSaveTimeoutRef.current = setTimeout(() => { saveDraft(); - // Update empty state - if (editorRef.current) { - setIsEditorEmpty(editorRef.current.isEmpty()); - } }, 2000); - return () => clearInterval(timer); - }, [saveDraft]); - // Update JSON preview more frequently for responsive UI + // Debounce JSON update (200ms for responsive feel) + if (settings.showEventJson) { + if (jsonUpdateTimeoutRef.current) { + clearTimeout(jsonUpdateTimeoutRef.current); + } + jsonUpdateTimeoutRef.current = setTimeout(() => { + generateDraftEventJson(); + }, 200); + } + }, [saveDraft, settings.showEventJson, generateDraftEventJson]); + + // Cleanup timeouts on unmount useEffect(() => { - if (!settings.showEventJson) return; - - const timer = setInterval(() => { - generateDraftEventJson(); - }, 200); - return () => clearInterval(timer); - }, [settings.showEventJson, generateDraftEventJson]); + return () => { + if (draftSaveTimeoutRef.current) { + clearTimeout(draftSaveTimeoutRef.current); + } + if (jsonUpdateTimeoutRef.current) { + clearTimeout(jsonUpdateTimeoutRef.current); + } + }; + }, []); // Blossom upload for attachments const { open: openUpload, dialog: uploadDialog } = useBlossomUpload({ @@ -679,6 +702,7 @@ export function PostViewer({ windowId }: PostViewerProps = {}) { ref={editorRef} placeholder="What's on your mind?" onSubmit={handlePublish} + onChange={handleEditorChange} searchProfiles={searchProfiles} searchEmojis={searchEmojis} onFilePaste={handleFilePaste} @@ -897,17 +921,31 @@ export function PostViewer({ windowId }: PostViewerProps = {}) { {/* Event JSON Preview */} - {!showPublishedPreview && settings.showEventJson && draftEventJson && ( -
-
- Event JSON (Draft - Unsigned) -
-
- -
-
+ {settings.showEventJson && ( + <> + {showPublishedPreview && lastPublishedEvent ? ( +
+
+ +
+
+ ) : ( + draftEventJson && ( +
+
+ +
+
+ ) + )} + )} {/* Upload dialog */} diff --git a/src/components/editor/RichEditor.tsx b/src/components/editor/RichEditor.tsx index 359f415..f577a72 100644 --- a/src/components/editor/RichEditor.tsx +++ b/src/components/editor/RichEditor.tsx @@ -45,6 +45,7 @@ export interface RichEditorProps { eventRefs: string[], addressRefs: Array<{ kind: number; pubkey: string; identifier: string }>, ) => void; + onChange?: () => void; searchProfiles: (query: string) => Promise; searchEmojis?: (query: string) => Promise; onFilePaste?: (files: File[]) => void; @@ -230,6 +231,7 @@ export const RichEditor = forwardRef( { placeholder = "Write your note...", onSubmit, + onChange, searchProfiles, searchEmojis, onFilePaste, @@ -521,6 +523,9 @@ export const RichEditor = forwardRef( }, }, autofocus: autoFocus, + onUpdate: () => { + onChange?.(); + }, }); // Expose editor methods