From bb6764036c004fec2de5e57f45aff5edf3d02a3e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Jan 2026 21:47:13 +0000 Subject: [PATCH] feat(post): enhance draft storage with full JSON state persistence Draft persistence improvements: - Store complete editor state as JSON (preserves blobs, emojis, mentions, formatting) - Save selected relays array in draft - Restore full editor content using setContent() method - Maintain draft per-user with pubkey-based key RichEditor enhancements: - Add getJSON() method to export editor state - Add setContent() method to restore from JSON - Enables lossless draft save/restore UI improvements: - Change "Relays" label to plain text without Label component - Keep unselected relays visible during/after publish - Only update status for selected relays during publish Draft storage format: { editorState: {...}, // Full TipTap JSON state selectedRelays: [...], // Array of selected relay URLs timestamp: 1234567890 } --- src/components/PostViewer.tsx | 40 +++++++++++++++++----------- src/components/editor/RichEditor.tsx | 12 +++++++++ 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/components/PostViewer.tsx b/src/components/PostViewer.tsx index 285a9bd..b7958b6 100644 --- a/src/components/PostViewer.tsx +++ b/src/components/PostViewer.tsx @@ -3,7 +3,6 @@ import { Paperclip, Send, Loader2, Check, X, RotateCcw } from "lucide-react"; import { toast } from "sonner"; import { Button } from "./ui/button"; import { Checkbox } from "./ui/checkbox"; -import { Label } from "./ui/label"; import { useAccount } from "@/hooks/useAccount"; import { useProfileSearch } from "@/hooks/useProfileSearch"; import { useEmojiSearch } from "@/hooks/useEmojiSearch"; @@ -76,16 +75,21 @@ export function PostViewer() { if (savedDraft) { try { const draft = JSON.parse(savedDraft); - // We'll set content via editor commands after editor is ready - // Store in ref for initial load - if (editorRef.current && draft.content) { + + // Restore editor content + if (editorRef.current && draft.editorState) { // Use setTimeout to ensure editor is fully mounted setTimeout(() => { - if (editorRef.current) { - editorRef.current.insertText(draft.content); + if (editorRef.current && draft.editorState) { + editorRef.current.setContent(draft.editorState); } }, 100); } + + // Restore selected relays + if (draft.selectedRelays && Array.isArray(draft.selectedRelays)) { + setSelectedRelays(new Set(draft.selectedRelays)); + } } catch (err) { console.error("Failed to load draft:", err); } @@ -97,6 +101,8 @@ export function PostViewer() { if (!pubkey || !editorRef.current) return; const content = editorRef.current.getContent(); + const editorState = editorRef.current.getJSON(); + if (!content.trim()) { // Clear draft if empty const draftKey = `${DRAFT_STORAGE_KEY}-${pubkey}`; @@ -106,7 +112,8 @@ export function PostViewer() { const draftKey = `${DRAFT_STORAGE_KEY}-${pubkey}`; const draft = { - content, + editorState, // Full editor JSON state (preserves blobs, emojis, formatting) + selectedRelays: Array.from(selectedRelays), // Selected relay URLs timestamp: Date.now(), }; @@ -115,7 +122,7 @@ export function PostViewer() { } catch (err) { console.error("Failed to save draft:", err); } - }, [pubkey]); + }, [pubkey, selectedRelays]); // Debounced draft save (save every 2 seconds of inactivity) useEffect(() => { @@ -211,12 +218,13 @@ export function PostViewer() { const draft = await factory.build({ kind: 1, content, tags }); const event = await factory.sign(draft); - // Initialize relay states - setRelayStates( - selected.map((url) => ({ - url, - status: "publishing" as RelayStatus, - })), + // Update relay states - set selected to publishing, keep others as pending + setRelayStates((prev) => + prev.map((r) => + selected.includes(r.url) + ? { ...r, status: "publishing" as RelayStatus } + : r, + ), ); // Publish to each relay individually to track status @@ -366,9 +374,9 @@ export function PostViewer() { {/* Relay selection */}
- + {writeRelays.length > 0 && (