mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-12 08:27:27 +02:00
refactor(post): remove JSON preview and ensure proper content serialization
**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
This commit is contained in:
@@ -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<any>(null);
|
||||
const [showPublishedPreview, setShowPublishedPreview] = useState(false);
|
||||
const [newRelayInput, setNewRelayInput] = useState("");
|
||||
const [draftEventJson, setDraftEventJson] = useState<any>(null);
|
||||
|
||||
// Load settings from localStorage
|
||||
const [settings, setSettings] = useState<PostSettings>(() => {
|
||||
@@ -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<ReturnType<typeof setTimeout> | null>(
|
||||
null,
|
||||
);
|
||||
const jsonUpdateTimeoutRef = useRef<ReturnType<typeof setTimeout> | 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
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem
|
||||
checked={settings.showEventJson}
|
||||
onCheckedChange={(checked) => {
|
||||
updateSetting("showEventJson", checked);
|
||||
if (checked) {
|
||||
generateDraftEventJson();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Show event JSON
|
||||
</DropdownMenuCheckboxItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -920,34 +816,6 @@ export function PostViewer({ windowId }: PostViewerProps = {}) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Event JSON Preview */}
|
||||
{settings.showEventJson && (
|
||||
<>
|
||||
{showPublishedPreview && lastPublishedEvent ? (
|
||||
<div className="rounded-lg border border-border overflow-hidden">
|
||||
<div className="overflow-y-auto" style={{ maxHeight: "400px" }}>
|
||||
<CopyableJsonViewer
|
||||
json={JSON.stringify(lastPublishedEvent, null, 2)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
draftEventJson && (
|
||||
<div className="rounded-lg border border-border overflow-hidden">
|
||||
<div
|
||||
className="overflow-y-auto"
|
||||
style={{ maxHeight: "400px" }}
|
||||
>
|
||||
<CopyableJsonViewer
|
||||
json={JSON.stringify(draftEventJson, null, 2)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Upload dialog */}
|
||||
{uploadDialog}
|
||||
</div>
|
||||
|
||||
@@ -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<RichEditorHandle, RichEditorProps>(
|
||||
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",
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user