Browse Source

bug-fixes

imwald
Silberengel 2 weeks ago
parent
commit
3661df980f
  1. 8
      src/components/EmojiPicker/index.tsx
  2. 2
      src/components/EmojiPickerDialog/index.tsx
  3. 10
      src/components/PostEditor/PostContent.tsx
  4. 94
      src/components/PostEditor/PostTextarea/index.tsx
  5. 5
      src/components/PostEditor/index.tsx
  6. 1
      src/constants.ts

8
src/components/EmojiPicker/index.tsx

@ -59,6 +59,9 @@ export default function EmojiPicker({ @@ -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({ @@ -188,7 +191,10 @@ export default function EmojiPicker({
return (
<div className="flex w-full min-w-0 flex-col">
{ownEmojisRow}
<div ref={containerRef} className="min-h-0 w-full flex-1 overflow-hidden" />
<div
ref={containerRef}
className="h-[min(350px,50dvh)] min-h-[280px] w-full shrink-0 overflow-hidden"
/>
</div>
)
}

2
src/components/EmojiPickerDialog/index.tsx

@ -60,7 +60,7 @@ export default function EmojiPickerDialog({ @@ -60,7 +60,7 @@ export default function EmojiPickerDialog({
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
className="p-0 w-[min(100vw-1rem,350px)] max-w-[calc(100vw-1rem)] overflow-hidden"
className="p-0 w-[min(100vw-1rem,350px)] max-w-[calc(100vw-1rem)] overflow-hidden flex flex-col"
portalContainer={portalContainer}
>
<EmojiPicker

10
src/components/PostEditor/PostContent.tsx

@ -767,7 +767,15 @@ export default function PostContent({ @@ -767,7 +767,15 @@ export default function PostContent({
const wasOpen = prevComposerShellOpenRef.current
prevComposerShellOpenRef.current = open
if (!wasOpen && open && !advancedLabOpenRef.current) {
textareaRef.current?.syncFromPostCache()
// TipTap mounts async (immediatelyRender: false); retry after the editor exists.
const sync = () => textareaRef.current?.syncFromPostCache()
sync()
const raf = requestAnimationFrame(sync)
const tmr = window.setTimeout(sync, 0)
return () => {
cancelAnimationFrame(raf)
window.clearTimeout(tmr)
}
}
}, [open, getDeterminedKind, defaultContent, parentEvent])

94
src/components/PostEditor/PostTextarea/index.tsx

@ -131,36 +131,40 @@ const PostTextarea = forwardRef< @@ -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<Editor | null>(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< @@ -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< @@ -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< @@ -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 (
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-2">
@ -321,7 +348,16 @@ const PostTextarea = forwardRef< @@ -321,7 +348,16 @@ const PostTextarea = forwardRef<
forceMount
className="mt-0 data-[state=inactive]:hidden focus-visible:ring-0 focus-visible:ring-offset-0"
>
<EditorContent className="tiptap" editor={editor} />
{editor ? (
<EditorContent className="tiptap" editor={editor} />
) : (
<div
className={cn(editorShellClass, 'text-muted-foreground')}
aria-hidden
>
{placeholderText}
</div>
)}
</TabsContent>
<TabsContent
value="preview"

5
src/components/PostEditor/index.tsx

@ -144,7 +144,10 @@ export default function PostEditor({ @@ -144,7 +144,10 @@ export default function PostEditor({
}
}}
>
<ScrollArea className="px-4 h-full max-h-screen min-w-0" scrollBarClassName="opacity-100">
<ScrollArea
className="px-4 max-h-[min(90dvh,900px)] min-w-0"
scrollBarClassName="opacity-100"
>
<div className="space-y-4 px-2 pr-4 py-6 min-w-0">
<DialogHeader className="sr-only">
<DialogTitle>Post Editor</DialogTitle>

1
src/constants.ts

@ -504,6 +504,7 @@ export const SOCIAL_KIND_BLOCKED_RELAY_URLS = [ @@ -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

Loading…
Cancel
Save