diff --git a/src/components/EmojiPicker/index.tsx b/src/components/EmojiPicker/index.tsx index eebef265..8fb28a5b 100644 --- a/src/components/EmojiPicker/index.tsx +++ b/src/components/EmojiPicker/index.tsx @@ -59,6 +59,9 @@ export default function EmojiPicker({ } picker.style.width = '100%' + picker.style.height = 'min(350px, 50dvh)' + picker.style.minHeight = '280px' + picker.style.display = 'block' picker.style.setProperty('--num-columns', '8') const handleClick = (e: Event) => { @@ -188,7 +191,10 @@ export default function EmojiPicker({ return (
{ownEmojisRow} -
+
) } diff --git a/src/components/EmojiPickerDialog/index.tsx b/src/components/EmojiPickerDialog/index.tsx index b5339155..184b2e4b 100644 --- a/src/components/EmojiPickerDialog/index.tsx +++ b/src/components/EmojiPickerDialog/index.tsx @@ -60,7 +60,7 @@ export default function EmojiPickerDialog({ {children} textareaRef.current?.syncFromPostCache() + sync() + const raf = requestAnimationFrame(sync) + const tmr = window.setTimeout(sync, 0) + return () => { + cancelAnimationFrame(raf) + window.clearTimeout(tmr) + } } }, [open, getDeterminedKind, defaultContent, parentEvent]) diff --git a/src/components/PostEditor/PostTextarea/index.tsx b/src/components/PostEditor/PostTextarea/index.tsx index 933ef541..59dd0b66 100644 --- a/src/components/PostEditor/PostTextarea/index.tsx +++ b/src/components/PostEditor/PostTextarea/index.tsx @@ -131,36 +131,40 @@ const PostTextarea = forwardRef< onUploadCompressPhaseRef.current = onUploadCompressPhase const onUploadCompressProgressRef = useRef(onUploadCompressProgress) onUploadCompressProgressRef.current = onUploadCompressProgress + const onUploadStartRef = useRef(onUploadStart) + onUploadStartRef.current = onUploadStart + const onUploadEndRef = useRef(onUploadEnd) + onUploadEndRef.current = onUploadEnd + const onUploadProgressRef = useRef(onUploadProgress) + onUploadProgressRef.current = onUploadProgress + const onSubmitRef = useRef(onSubmit) + onSubmitRef.current = onSubmit const [activeTab, setActiveTab] = useState('edit') const editorRef = useRef(null) const kindDescription = useMemo(() => getKindDescription(kind), [kind]) - const editor = useEditor({ - // TipTap + Radix Dialog/Tabs: defer init so React 18 does not warn about flushSync in a lifecycle. - immediatelyRender: false, - extensions: [ + const placeholderText = useMemo( + () => t('Write something...') + ' (' + t('Paste or drop media files to upload') + ')', + [t] + ) + + // Extension instances must be stable — recreating them each render makes useEditor destroy/recreate + // the editor (blank composer in reply dialog). + const extensions = useMemo( + () => [ Document, Paragraph, Text, History, HardBreak, - Placeholder.configure({ - placeholder: - t('Write something...') + ' (' + t('Paste or drop media files to upload') + ')' - }), - Emoji.configure({ - suggestion: emojiSuggestion - }), - Mention.configure({ - suggestion: mentionSuggestion - }), + Placeholder.configure({ placeholder: placeholderText }), + Emoji.configure({ suggestion: emojiSuggestion }), + Mention.configure({ suggestion: mentionSuggestion }), ClipboardAndDropHandler.configure({ - onUploadStart: (file, cancel) => { - onUploadStart?.(file, cancel) - }, - onUploadEnd: (file) => onUploadEnd?.(file), - onUploadProgress: (file, p) => onUploadProgress?.(file, p), + onUploadStart: (file, cancel) => onUploadStartRef.current?.(file, cancel), + onUploadEnd: (file) => onUploadEndRef.current?.(file), + onUploadProgress: (file, p) => onUploadProgressRef.current?.(file, p), onUploadSuccess: (result) => onUploadSuccessRef.current?.(result), onUploadCompressPhase: (file, phase) => onUploadCompressPhaseRef.current?.(file, phase), @@ -168,18 +172,30 @@ const PostTextarea = forwardRef< onUploadCompressProgressRef.current?.(file, pct) }) ], + [placeholderText] + ) + + const editorSurfaceClass = useMemo( + () => + cn( + 'border rounded-lg p-3 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring', + className + ), + [className] + ) + + const editor = useEditor({ + // TipTap + Radix Dialog/Tabs: defer init so React 18 does not warn about flushSync in a lifecycle. + immediatelyRender: false, + extensions, editorProps: { attributes: { - class: cn( - 'border rounded-lg p-3 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring', - className - ) + class: editorSurfaceClass }, handleKeyDown: (_view, event) => { - // Handle Ctrl+Enter or Cmd+Enter for submit if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') { event.preventDefault() - onSubmit?.() + onSubmitRef.current?.() return true } return false @@ -200,6 +216,16 @@ const PostTextarea = forwardRef< editorRef.current = editor + useEffect(() => { + if (!editor) return + editor.setOptions({ + editorProps: { + ...editor.options.editorProps, + attributes: { class: editorSurfaceClass } + } + }) + }, [editor, editorSurfaceClass]) + useEffect(() => { if (!editor || !isSmallScreen) return const scrollEditorIntoView = () => { @@ -295,9 +321,10 @@ const PostTextarea = forwardRef< } })) - if (!editor) { - return null - } + const editorShellClass = cn( + 'border rounded-lg p-3 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring', + className + ) return ( @@ -321,7 +348,16 @@ const PostTextarea = forwardRef< forceMount className="mt-0 data-[state=inactive]:hidden focus-visible:ring-0 focus-visible:ring-offset-0" > - + {editor ? ( + + ) : ( +
+ {placeholderText} +
+ )} - +
Post Editor diff --git a/src/constants.ts b/src/constants.ts index 46114638..7066edad 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -504,6 +504,7 @@ export const SOCIAL_KIND_BLOCKED_RELAY_URLS = [ 'wss://relay.wikifreedia.xyz', 'wss://relay.gifbuddy.lol', 'wss://hist.nostr.land', + 'wss://essayist.decentnewsroom.com' ] // Optimized relay list for read operations