mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-12 08:27:27 +02:00
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
}
This commit is contained in:
@@ -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 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm font-medium">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Relays ({selectedRelays.size} selected)
|
||||
</Label>
|
||||
</span>
|
||||
{writeRelays.length > 0 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
@@ -64,6 +64,10 @@ export interface RichEditorHandle {
|
||||
insertText: (text: string) => void;
|
||||
/** Insert a blob attachment with rich preview */
|
||||
insertBlob: (blob: BlobAttachment) => void;
|
||||
/** Get editor state as JSON (for persistence) */
|
||||
getJSON: () => any;
|
||||
/** Set editor content from JSON (for restoration) */
|
||||
setContent: (json: any) => void;
|
||||
}
|
||||
|
||||
// Create emoji extension by extending Mention with a different name and custom node view
|
||||
@@ -500,6 +504,14 @@ export const RichEditor = forwardRef<RichEditorHandle, RichEditorProps>(
|
||||
attrs: blob,
|
||||
});
|
||||
},
|
||||
getJSON: () => {
|
||||
return editor?.getJSON() || null;
|
||||
},
|
||||
setContent: (json: any) => {
|
||||
if (editor && json) {
|
||||
editor.commands.setContent(json);
|
||||
}
|
||||
},
|
||||
}),
|
||||
[editor, handleSubmit],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user