From cc2b6453a6eaba042a15dcb4aefcfab75e3dddb4 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Jan 2026 11:01:30 +0000 Subject: [PATCH] refactor(post): remove JSON preview and ensure proper content serialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Removed JSON preview feature:** - Removed showEventJson setting and all related UI - Removed draftEventJson state and generation logic - Removed "Show event JSON" checkbox from settings - Simplified PostViewer code by ~100 lines - Cleaner, production-ready codebase **Ensured proper content serialization:** - Added renderText() to Mention extension to serialize as nostr:npub URIs - Added renderText() to BlobAttachmentRichNode to serialize URLs - NostrEventPreviewRichNode already had renderText() for nostr:note/nevent/naddr - Editor now properly converts all rich content to plain text: - @mentions → nostr:npub... - Event references → nostr:note/nevent... - Address references → nostr:naddr... - Blob attachments → URLs - Custom emojis → :shortcode: **Result:** - Cleaner, simplified code ready for production - All editor elements properly serialize to content string - JSON preview can be re-added later with better implementation --- src/components/PostViewer.tsx | 138 +----------------- src/components/editor/RichEditor.tsx | 13 +- .../editor/extensions/blob-attachment-rich.ts | 5 + 3 files changed, 20 insertions(+), 136 deletions(-) diff --git a/src/components/PostViewer.tsx b/src/components/PostViewer.tsx index d507ed8..c79dd38 100644 --- a/src/components/PostViewer.tsx +++ b/src/components/PostViewer.tsx @@ -30,7 +30,6 @@ import { RichEditor, type RichEditorHandle } from "./editor/RichEditor"; import type { BlobAttachment, EmojiTag } from "./editor/MentionEditor"; import { RelayLink } from "./nostr/RelayLink"; import { Kind1Renderer } from "./nostr/kinds"; -import { CopyableJsonViewer } from "./JsonViewer"; import pool from "@/services/relay-pool"; import eventStore from "@/services/event-store"; import { EventFactory } from "applesauce-core/event-factory"; @@ -55,12 +54,10 @@ const SETTINGS_STORAGE_KEY = "grimoire-post-settings"; interface PostSettings { includeClientTag: boolean; - showEventJson: boolean; } const DEFAULT_SETTINGS: PostSettings = { includeClientTag: true, - showEventJson: false, }; interface PostViewerProps { @@ -85,7 +82,6 @@ export function PostViewer({ windowId }: PostViewerProps = {}) { const [lastPublishedEvent, setLastPublishedEvent] = useState(null); const [showPublishedPreview, setShowPublishedPreview] = useState(false); const [newRelayInput, setNewRelayInput] = useState(""); - const [draftEventJson, setDraftEventJson] = useState(null); // Load settings from localStorage const [settings, setSettings] = useState(() => { @@ -227,86 +223,10 @@ export function PostViewer({ windowId }: PostViewerProps = {}) { } }, [pubkey, windowId, selectedRelays, relayStates, writeRelays]); - // Generate draft event JSON for preview - const generateDraftEventJson = useCallback(() => { - if (!pubkey || !editorRef.current) { - setDraftEventJson(null); - return; - } - - const serialized = editorRef.current.getSerializedContent(); - const content = serialized.text.trim(); - - if (!content) { - setDraftEventJson(null); - return; - } - - // Build tags array - const tags: string[][] = []; - - // Add p tags for mentions - for (const mention of serialized.mentions) { - tags.push(["p", mention]); - } - - // Add e tags for event references - for (const eventRef of serialized.eventRefs) { - tags.push(["e", eventRef]); - } - - // Add a tags for address references - for (const addrRef of serialized.addressRefs) { - tags.push([ - "a", - `${addrRef.kind}:${addrRef.pubkey}:${addrRef.identifier}`, - ]); - } - - // Add client tag (if enabled) - if (settings.includeClientTag) { - tags.push(["client", "grimoire"]); - } - - // Add emoji tags - for (const emoji of serialized.emojiTags) { - tags.push(["emoji", emoji.shortcode, emoji.url]); - } - - // Add blob attachment tags (imeta) - for (const blob of serialized.blobAttachments) { - const imetaTag = [ - "imeta", - `url ${blob.url}`, - `m ${blob.mimeType}`, - `x ${blob.sha256}`, - `size ${blob.size}`, - ]; - if (blob.server) { - imetaTag.push(`server ${blob.server}`); - } - tags.push(imetaTag); - } - - // Create draft event structure (unsigned) - const draftEvent = { - kind: 1, - pubkey, - created_at: Math.floor(Date.now() / 1000), - tags, - content, - }; - - setDraftEventJson(draftEvent); - }, [pubkey, settings.includeClientTag]); - - // Debounced handlers for editor changes + // Debounced draft save on editor changes const draftSaveTimeoutRef = useRef | null>( null, ); - const jsonUpdateTimeoutRef = useRef | null>( - null, - ); const handleEditorChange = useCallback(() => { // Update empty state immediately @@ -321,27 +241,14 @@ export function PostViewer({ windowId }: PostViewerProps = {}) { draftSaveTimeoutRef.current = setTimeout(() => { saveDraft(); }, 2000); + }, [saveDraft]); - // 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 + // Cleanup timeout on unmount useEffect(() => { return () => { if (draftSaveTimeoutRef.current) { clearTimeout(draftSaveTimeoutRef.current); } - if (jsonUpdateTimeoutRef.current) { - clearTimeout(jsonUpdateTimeoutRef.current); - } }; }, []); @@ -746,17 +653,6 @@ export function PostViewer({ windowId }: PostViewerProps = {}) { > Include client tag - { - updateSetting("showEventJson", checked); - if (checked) { - generateDraftEventJson(); - } - }} - > - Show event JSON - @@ -920,34 +816,6 @@ export function PostViewer({ windowId }: PostViewerProps = {}) { )} - {/* Event JSON Preview */} - {settings.showEventJson && ( - <> - {showPublishedPreview && lastPublishedEvent ? ( -
-
- -
-
- ) : ( - draftEventJson && ( -
-
- -
-
- ) - )} - - )} - {/* Upload dialog */} {uploadDialog} diff --git a/src/components/editor/RichEditor.tsx b/src/components/editor/RichEditor.tsx index f577a72..6a3c8e4 100644 --- a/src/components/editor/RichEditor.tsx +++ b/src/components/editor/RichEditor.tsx @@ -25,6 +25,7 @@ import { } from "./EmojiSuggestionList"; import type { ProfileSearchResult } from "@/services/profile-search"; import type { EmojiSearchResult } from "@/services/emoji-search"; +import { nip19 } from "nostr-tools"; import { NostrPasteHandler } from "./extensions/nostr-paste-handler"; import { FilePasteHandler } from "./extensions/file-paste-handler"; import { BlobAttachmentRichNode } from "./extensions/blob-attachment-rich"; @@ -435,7 +436,17 @@ export const RichEditor = forwardRef( keepMarks: false, }, }), - Mention.configure({ + Mention.extend({ + renderText({ node }) { + // Serialize to nostr: URI for plain text export + try { + return `nostr:${nip19.npubEncode(node.attrs.id)}`; + } catch (err) { + console.error("[Mention] Failed to encode pubkey:", err); + return `@${node.attrs.label}`; + } + }, + }).configure({ HTMLAttributes: { class: "mention", }, diff --git a/src/components/editor/extensions/blob-attachment-rich.ts b/src/components/editor/extensions/blob-attachment-rich.ts index a6f5a43..f193979 100644 --- a/src/components/editor/extensions/blob-attachment-rich.ts +++ b/src/components/editor/extensions/blob-attachment-rich.ts @@ -38,6 +38,11 @@ export const BlobAttachmentRichNode = Node.create({ ]; }, + renderText({ node }) { + // Serialize to URL for plain text export + return node.attrs.url || ""; + }, + addNodeView() { return ReactNodeViewRenderer(BlobAttachmentRich); },