From 2333b054d6d61363037183fb0c8158cae722785f Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sat, 18 Apr 2026 11:47:21 +0200 Subject: [PATCH] fix quotes --- src/components/PostEditor/PostContent.tsx | 12 ++++++++++ .../PostEditor/PostTextarea/index.tsx | 12 ++++++++++ src/components/PostEditor/index.tsx | 2 ++ .../post-editor-cache.service.test.ts | 23 +++++++++++++++++++ src/services/post-editor-cache.service.ts | 22 +++++++++++++++++- 5 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/services/post-editor-cache.service.test.ts diff --git a/src/components/PostEditor/PostContent.tsx b/src/components/PostEditor/PostContent.tsx index 48709035..b949535d 100644 --- a/src/components/PostEditor/PostContent.tsx +++ b/src/components/PostEditor/PostContent.tsx @@ -156,6 +156,7 @@ function formatMarkupImageAtCursor(url: string, asciidoc: boolean): string { } export default function PostContent({ + open, defaultContent = '', parentEvent, close, @@ -165,6 +166,8 @@ export default function PostContent({ onPublishSuccess, discussionDynamicTopics }: { + /** When false, the post shell is closed (e.g. dialog). Used to re-sync the TipTap body when reopened. */ + open: boolean defaultContent?: string parentEvent?: Event close: () => void @@ -717,6 +720,15 @@ export default function PostContent({ ) }, [getDeterminedKind, defaultContent, parentEvent, isNsfw, isPoll, pollCreateData, addClientTag]) + const prevComposerShellOpenRef = useRef(open) + useEffect(() => { + const wasOpen = prevComposerShellOpenRef.current + prevComposerShellOpenRef.current = open + if (!wasOpen && open && !advancedLabOpenRef.current) { + textareaRef.current?.syncFromPostCache() + } + }, [open, getDeterminedKind, defaultContent, parentEvent]) + const rssReplyExtraPreviewTags = useMemo((): string[][] | undefined => { if (!parentEvent || parentEvent.kind !== ExtendedKind.RSS_THREAD_ROOT) return undefined const raw = diff --git a/src/components/PostEditor/PostTextarea/index.tsx b/src/components/PostEditor/PostTextarea/index.tsx index a62d9906..bdad13ef 100644 --- a/src/components/PostEditor/PostTextarea/index.tsx +++ b/src/components/PostEditor/PostTextarea/index.tsx @@ -43,6 +43,8 @@ export type TPostTextareaHandle = { insertText: (text: string) => void insertEmoji: (emoji: string | TEmoji) => void clear: () => void + /** Re-read `postEditorCache` / `defaultContent` into TipTap (dialog reopened; initial `content` only runs once). */ + syncFromPostCache: () => void getText: () => string /** Replace editor from plain `content` (e.g. advanced lab). Syncs TipTap JSON cache and parent `text`. */ setDocumentFromPlainText: (plain: string) => void @@ -308,6 +310,16 @@ const PostTextarea = forwardRef< setText('') } }, + syncFromPostCache: () => { + const editor = editorRef.current + if (!editor) return + const next = postEditorCache.getPostContentCache({ kind, defaultContent, parentEvent }) + if (next === undefined) return + editor.chain().setContent(next).run() + const json = editor.getJSON() + setText(parseEditorJsonToText(json)) + postEditorCache.setPostContentCache({ kind, defaultContent, parentEvent }, json) + }, getText: () => { const editor = editorRef.current if (editor) { diff --git a/src/components/PostEditor/index.tsx b/src/components/PostEditor/index.tsx index 44b6fb11..ff57d492 100644 --- a/src/components/PostEditor/index.tsx +++ b/src/components/PostEditor/index.tsx @@ -60,6 +60,7 @@ export default function PostEditor({ const content = useMemo(() => { return ( setOpen(false)} @@ -71,6 +72,7 @@ export default function PostEditor({ /> ) }, [ + open, effectiveDefaultContent, parentEvent, openFrom, diff --git a/src/services/post-editor-cache.service.test.ts b/src/services/post-editor-cache.service.test.ts new file mode 100644 index 00000000..eeaf56b5 --- /dev/null +++ b/src/services/post-editor-cache.service.test.ts @@ -0,0 +1,23 @@ +import { afterEach, describe, expect, it } from 'vitest' +import postEditorCache from '@/services/post-editor-cache.service' +import { plainTextToTipTapDoc } from '@/lib/tiptap' + +describe('PostEditorCacheService — quote / defaultContent', () => { + afterEach(() => { + postEditorCache.clearAllPostCaches() + }) + + it('getPostContentCache ignores an empty cached doc when defaultContent is set (re-seed quote)', () => { + const params = { kind: 1 as const, defaultContent: '\nnostr:nevent1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' } + postEditorCache.setPostContentCache(params, plainTextToTipTapDoc('')) + const got = postEditorCache.getPostContentCache(params) + expect(typeof got).toBe('string') + expect(got).toContain('nostr:') + }) + + it('setPostContentCache does not persist an empty body when defaultContent is set', () => { + const params = { kind: 1 as const, defaultContent: '\nnostr:nevent1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' } + postEditorCache.setPostContentCache(params, plainTextToTipTapDoc('')) + expect(postEditorCache.getPostContentCache(params)).toContain('nostr:') + }) +}) diff --git a/src/services/post-editor-cache.service.ts b/src/services/post-editor-cache.service.ts index 679abbb8..f61705d3 100644 --- a/src/services/post-editor-cache.service.ts +++ b/src/services/post-editor-cache.service.ts @@ -184,7 +184,20 @@ class PostEditorCacheService { this.restoreFromStorageIfNeeded() const cacheKey = this.generateCacheKey({ kind, defaultContent, parentEvent }) const cached = this.postContentCache.get(cacheKey) - if (cached !== undefined) return cached + if (cached !== undefined) { + const cachedText = ( + typeof cached === 'string' ? cached : parseEditorJsonToText(cached ?? undefined) + ).trim() + // Seeded composers (e.g. Quote): an empty cached doc must not hide `defaultContent` on reopen. + if ( + cachedText === '' && + defaultContent !== undefined && + defaultContent.trim() !== '' + ) { + return this.escapeAmpersandsForHtml(defaultContent) + } + return cached + } if (defaultContent !== undefined && defaultContent !== '') { return this.escapeAmpersandsForHtml(defaultContent) } @@ -208,6 +221,13 @@ class PostEditorCacheService { ) { return } + if (incomingText === '' && defaultContent !== undefined && defaultContent.trim() !== '') { + this.keysRestoredThisSession.delete(cacheKey) + if (this.postContentCache.delete(cacheKey)) { + this.schedulePersist() + } + return + } this.keysRestoredThisSession.delete(cacheKey) this.postContentCache.set(cacheKey, content) this.schedulePersist()