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 (
)
}
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