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); },