From ae0882240ac7a50e24178634a63029cdcbdf8edf Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sun, 31 May 2026 11:19:12 +0200 Subject: [PATCH] hide more advanced options on editors --- .../ActiveRelaysDropdownSection.tsx | 42 +--- .../ConnectedRelays/ActiveRelaysIconGrid.tsx | 106 ++++++++++ .../ConnectedRelaysSidebarStrip.tsx | 117 ----------- .../ConnectedRelays/active-relays-display.ts | 13 ++ src/components/HelpAndAccountMenu.tsx | 7 +- src/components/PostEditor/Mentions.tsx | 19 +- src/components/PostEditor/PollEditor.tsx | 23 +-- src/components/PostEditor/PostContent.tsx | 122 +++++------ .../PostEditor/PostEditorAdvancedPanel.tsx | 189 ++++++++++++++++++ .../PostEditor/PostEditorFormatToolbar.tsx | 30 +-- src/components/PostEditor/PostOptions.tsx | 84 -------- .../PostEditor/PostTextarea/index.tsx | 2 +- src/components/Sidebar/index.tsx | 2 - src/i18n/locales/de.ts | 9 +- src/i18n/locales/en.ts | 4 + 15 files changed, 423 insertions(+), 346 deletions(-) create mode 100644 src/components/ConnectedRelays/ActiveRelaysIconGrid.tsx delete mode 100644 src/components/ConnectedRelays/ConnectedRelaysSidebarStrip.tsx create mode 100644 src/components/ConnectedRelays/active-relays-display.ts create mode 100644 src/components/PostEditor/PostEditorAdvancedPanel.tsx delete mode 100644 src/components/PostEditor/PostOptions.tsx diff --git a/src/components/ConnectedRelays/ActiveRelaysDropdownSection.tsx b/src/components/ConnectedRelays/ActiveRelaysDropdownSection.tsx index 55836eb0..3e7a0ce3 100644 --- a/src/components/ConnectedRelays/ActiveRelaysDropdownSection.tsx +++ b/src/components/ConnectedRelays/ActiveRelaysDropdownSection.tsx @@ -1,34 +1,14 @@ -import { useSmartRelayNavigation } from '@/PageManager' import { - DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator } from '@/components/ui/dropdown-menu' import { useRelayConnectionRows } from '@/hooks/useRelayConnectionRows' -import { toRelay } from '@/lib/link' -import { simplifyUrl } from '@/lib/url' -import { cn } from '@/lib/utils' import { useTranslation } from 'react-i18next' -import RelayIcon from '../RelayIcon' +import { ActiveRelaysIconGrid } from './ActiveRelaysIconGrid' -function rowMuted(connected: boolean) { - return !connected -} - -function rowTitle(url: string, connected: boolean, t: (k: string) => string) { - const base = simplifyUrl(url) - if (!connected) return `${base} — ${t('Not connected')}` - return base -} - -function rowClass(connected: boolean) { - return cn(rowMuted(connected) && 'opacity-45 text-muted-foreground') -} - -/** Relay list block for account (or similar) dropdown menus. */ +/** Compact active-relay icons in the account (user badge) dropdown. */ export function ActiveRelaysDropdownSection() { const { t } = useTranslation() - const { navigateToRelay } = useSmartRelayNavigation() const { rows, connectedCount } = useRelayConnectionRows() if (rows.length === 0) return null @@ -42,17 +22,13 @@ export function ActiveRelaysDropdownSection() { {t('Active relays')} {countSummary} - {rows.map(({ url, connected }) => ( - navigateToRelay(toRelay(url))} - className={cn('min-w-52 gap-2', rowClass(connected))} - > - - {simplifyUrl(url)} - - ))} +
e.stopPropagation()} + onPointerDown={(e) => e.stopPropagation()} + > + +
) } diff --git a/src/components/ConnectedRelays/ActiveRelaysIconGrid.tsx b/src/components/ConnectedRelays/ActiveRelaysIconGrid.tsx new file mode 100644 index 00000000..49e3324f --- /dev/null +++ b/src/components/ConnectedRelays/ActiveRelaysIconGrid.tsx @@ -0,0 +1,106 @@ +import { useSmartRelayNavigation } from '@/PageManager' +import { Button } from '@/components/ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger +} from '@/components/ui/dropdown-menu' +import { useRelayConnectionRows } from '@/hooks/useRelayConnectionRows' +import { toRelay } from '@/lib/link' +import { cn } from '@/lib/utils' +import { useTranslation } from 'react-i18next' +import RelayIcon from '../RelayIcon' +import { + ACTIVE_RELAYS_MAX_ICONS, + activeRelayRowMuted, + activeRelayRowTitle +} from './active-relays-display' + +/** + * Compact relay status: icon buttons only (no hostname labels). + */ +export function ActiveRelaysIconGrid({ className }: { className?: string }) { + const { t } = useTranslation() + const { navigateToRelay } = useSmartRelayNavigation() + const { rows } = useRelayConnectionRows() + const shown = rows.slice(0, ACTIVE_RELAYS_MAX_ICONS) + const overflowRows = rows.slice(ACTIVE_RELAYS_MAX_ICONS) + const overflow = overflowRows.length + + if (rows.length === 0) { + return ( +

+ — +

+ ) + } + + return ( +
+ {shown.map(({ url, connected }) => ( + + ))} + {overflow > 0 ? ( + + + + + + + {t('More relays', { count: overflow })} + + +
+ {overflowRows.map(({ url, connected }) => ( + + ))} +
+
+
+ ) : null} +
+ ) +} diff --git a/src/components/ConnectedRelays/ConnectedRelaysSidebarStrip.tsx b/src/components/ConnectedRelays/ConnectedRelaysSidebarStrip.tsx deleted file mode 100644 index cd6fb01c..00000000 --- a/src/components/ConnectedRelays/ConnectedRelaysSidebarStrip.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { useSmartRelayNavigation } from '@/PageManager' -import { Button } from '@/components/ui/button' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger -} from '@/components/ui/dropdown-menu' -import { useRelayConnectionRows } from '@/hooks/useRelayConnectionRows' -import { toRelay } from '@/lib/link' -import { simplifyUrl } from '@/lib/url' -import { cn } from '@/lib/utils' -import { useTranslation } from 'react-i18next' -import RelayIcon from '../RelayIcon' - -const MAX_ICONS = 14 - -function rowMuted(connected: boolean) { - return !connected -} - -function rowMenuClass(connected: boolean) { - return cn(rowMuted(connected) && 'opacity-50 text-muted-foreground') -} - -function rowTitle(url: string, connected: boolean, t: (k: string) => string) { - const base = simplifyUrl(url) - if (!connected) return `${base} — ${t('Not connected')}` - return base -} - -/** - * Desktop sidebar: relay avatars for relays with an open WebSocket in the pool. - */ -export function ConnectedRelaysSidebarStrip({ className }: { className?: string }) { - const { t } = useTranslation() - const { navigateToRelay } = useSmartRelayNavigation() - const { rows } = useRelayConnectionRows() - const shown = rows.slice(0, MAX_ICONS) - const overflowRows = rows.slice(MAX_ICONS) - const overflow = overflowRows.length - - if (rows.length === 0) { - return ( -
-

{t('Active relays')}

-

-
- ) - } - - return ( -
-

- {t('Active relays')} -

-
- {shown.map(({ url, connected }) => ( - - ))} - {overflow > 0 ? ( - - - - - - - {t('More relays', { count: overflow })} - - - {overflowRows.map(({ url, connected }) => ( - navigateToRelay(toRelay(url))} - > - - {simplifyUrl(url)} - - ))} - - - ) : null} -
-
- ) -} diff --git a/src/components/ConnectedRelays/active-relays-display.ts b/src/components/ConnectedRelays/active-relays-display.ts new file mode 100644 index 00000000..d10b17bc --- /dev/null +++ b/src/components/ConnectedRelays/active-relays-display.ts @@ -0,0 +1,13 @@ +import { simplifyUrl } from '@/lib/url' + +export const ACTIVE_RELAYS_MAX_ICONS = 14 + +export function activeRelayRowMuted(connected: boolean) { + return !connected +} + +export function activeRelayRowTitle(url: string, connected: boolean, t: (k: string) => string) { + const base = simplifyUrl(url) + if (!connected) return `${base} — ${t('Not connected')}` + return base +} diff --git a/src/components/HelpAndAccountMenu.tsx b/src/components/HelpAndAccountMenu.tsx index 639a595f..d77503d5 100644 --- a/src/components/HelpAndAccountMenu.tsx +++ b/src/components/HelpAndAccountMenu.tsx @@ -34,13 +34,11 @@ export type HelpAndAccountMenuVariant = 'sidebar' | 'titlebar' function AccountDropdownItems({ onSwitchAccount, onLogoutClick, - onBrowseCache, - showActiveRelays = false + onBrowseCache }: { onSwitchAccount: () => void onLogoutClick: () => void onBrowseCache: () => void - showActiveRelays?: boolean }) { const { t } = useTranslation() const { navigate } = usePrimaryPage() @@ -59,7 +57,7 @@ function AccountDropdownItems({ {t('Browse Cache')} - {showActiveRelays ? : null} + @@ -191,7 +189,6 @@ function TitlebarAccountMenu({ onSwitchAccount={onSwitchAccount} onLogoutClick={onLogoutClick} onBrowseCache={onBrowseCache} - showActiveRelays /> diff --git a/src/components/PostEditor/Mentions.tsx b/src/components/PostEditor/Mentions.tsx index d8b80df3..85e1f21d 100644 --- a/src/components/PostEditor/Mentions.tsx +++ b/src/components/PostEditor/Mentions.tsx @@ -17,12 +17,15 @@ export default function Mentions({ content, mentions, setMentions, - parentEvent + parentEvent, + /** When true, trigger shows only the count (section label supplies the title). */ + compactTrigger = false }: { content: string mentions: string[] setMentions: (mentions: string[]) => void parentEvent?: Event + compactTrigger?: boolean }) { const { t } = useTranslation() const { pubkey } = useNostr() @@ -75,8 +78,18 @@ export default function Mentions({ disabled={potentialMentions.length === 0} onClick={(e) => e.stopPropagation()} > - {t('Mentions')}{' '} - {potentialMentions.length > 0 && `(${mentions.length}/${potentialMentions.length})`} + {compactTrigger ? ( + potentialMentions.length > 0 ? ( + `(${mentions.length}/${potentialMentions.length})` + ) : ( + '—' + ) + ) : ( + <> + {t('Mentions')}{' '} + {potentialMentions.length > 0 && `(${mentions.length}/${potentialMentions.length})`} + + )} diff --git a/src/components/PostEditor/PollEditor.tsx b/src/components/PostEditor/PollEditor.tsx index 67ca555d..9adf9938 100644 --- a/src/components/PostEditor/PollEditor.tsx +++ b/src/components/PostEditor/PollEditor.tsx @@ -7,18 +7,14 @@ import dayjs from 'dayjs' import { Eraser, X } from 'lucide-react' import { Dispatch, SetStateAction, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import PostRelaySelector from './PostRelaySelector' - export default function PollEditor({ pollCreateData, setPollCreateData, - setIsPoll: _setIsPoll, - content = '' + setIsPoll: _setIsPoll }: { pollCreateData: TPollCreateData setPollCreateData: Dispatch> setIsPoll: Dispatch> - content?: string }) { const { t } = useTranslation() const [isMultipleChoice, setIsMultipleChoice] = useState(pollCreateData.isMultipleChoice) @@ -26,15 +22,14 @@ export default function PollEditor({ const [endsAt, setEndsAt] = useState( pollCreateData.endsAt ? dayjs(pollCreateData.endsAt * 1000).format('YYYY-MM-DDTHH:mm') : '' ) - const [additionalRelayUrls, setAdditionalRelayUrls] = useState(pollCreateData.relays) useEffect(() => { - setPollCreateData({ + setPollCreateData((prev) => ({ + ...prev, isMultipleChoice, options, - endsAt: endsAt ? dayjs(endsAt).startOf('minute').unix() : undefined, - relays: additionalRelayUrls - }) - }, [isMultipleChoice, options, endsAt, additionalRelayUrls, setPollCreateData]) + endsAt: endsAt ? dayjs(endsAt).startOf('minute').unix() : undefined + })) + }, [isMultipleChoice, options, endsAt, setPollCreateData]) const handleAddOption = () => { setOptions([...options, '']) @@ -110,12 +105,6 @@ export default function PollEditor({ -
- -
) } diff --git a/src/components/PostEditor/PostContent.tsx b/src/components/PostEditor/PostContent.tsx index 7fbbf86b..9c8cd33a 100644 --- a/src/components/PostEditor/PostContent.tsx +++ b/src/components/PostEditor/PostContent.tsx @@ -42,7 +42,6 @@ import { ExtendedKind, isNip71ShortVideoKind, isNip71StyleVideoKind, - MAX_PUBLISH_RELAYS } from '@/constants' import { cn } from '@/lib/utils' import { useNostr } from '@/providers/NostrProvider' @@ -106,10 +105,9 @@ import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { showPublishingFeedback, showSimplePublishSuccess, showPublishingError } from '@/lib/publishing-feedback' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog' -import Mentions, { extractMentions } from './Mentions' +import { extractMentions } from './Mentions' import PollEditor from './PollEditor' -import PostOptions from './PostOptions' -import PostRelaySelector from './PostRelaySelector' +import PostEditorAdvancedPanel from './PostEditorAdvancedPanel' import PostTextarea, { TPostTextareaHandle } from './PostTextarea' import { newNostrSpecAffectedKindRow, @@ -479,6 +477,13 @@ export default function PostContent({ if (isPoll) setRelayCapBlockInfo(null) }, [isPoll]) + useEffect(() => { + if (!isPoll) return + setPollCreateData((prev) => + prev.relays === additionalRelayUrls ? prev : { ...prev, relays: additionalRelayUrls } + ) + }, [isPoll, additionalRelayUrls]) + // Clear highlight data when initialHighlightData changes or is removed useEffect(() => { if (initialHighlightData) { @@ -3086,7 +3091,9 @@ export default function PostContent({ title={t('Advanced event lab')} > - {t('Advanced event lab')} + + {t('Advanced event lab')} + {!parentEvent ? ( <> @@ -3281,7 +3288,6 @@ export default function PostContent({ pollCreateData={pollCreateData} setPollCreateData={setPollCreateData} setIsPoll={setIsPoll} - content={text} /> )} {isHighlight && ( @@ -3294,23 +3300,18 @@ export default function PostContent({ {isPublicMessage && (
{t('Recipients')}
-
- - {extractedMentions.length > 0 ? ( -
- {t('Recipients detected from your message:')} {extractedMentions.length} -
- ) : ( -
- {t('Add recipients using nostr: mentions (e.g., nostr:npub1...) or the recipient selector above')} -
- )} -
+ {extractedMentions.length > 0 ? ( +

+ {t('Recipients detected from your message:')} {extractedMentions.length} + {!showMoreOptions ? ( + {t('Open Advanced to adjust mention recipients')} + ) : null} +

+ ) : ( +

+ {t('Add recipients using nostr: mentions (e.g., nostr:npub1...) or open Advanced')} +

+ )}
)} {uploadProgresses.length > 0 && @@ -3354,43 +3355,6 @@ export default function PostContent({ ))} - {!isPoll && ( -
- - {relayCapBlockInfo && ( -

- {relayCapBlockInfo.outboxSlotsInPublish > 0 - ? t('Publish relay cap hint with outbox first', { - max: MAX_PUBLISH_RELAYS, - reservedSlots: relayCapBlockInfo.outboxSlotsInPublish, - selected: relayCapBlockInfo.selectedTotal, - selectedContacted: relayCapBlockInfo.selectedContacted - }) - : t('Publish relay cap hint', { - max: MAX_PUBLISH_RELAYS, - selected: relayCapBlockInfo.selectedTotal, - selectedContacted: relayCapBlockInfo.selectedContacted - })} -

- )} - {isDiscussionThread && threadErrors.relay && ( -

{threadErrors.relay}

- )} -
- )} {/* Hidden uploader for the "Media Note" dropdown item */} {!parentEvent && ( )} -
-
+
+
textareaRef.current?.insertText(txt)} insertEmoji={(em) => textareaRef.current?.insertEmoji(em)} @@ -3422,13 +3386,7 @@ export default function PostContent({ onToggleMoreOptions={() => setShowMoreOptions((pre) => !pre)} />
-
- +
-
@@ -78,11 +80,11 @@ export function PostEditorFormatToolbar({ onUploadCompressProgress={upload.onUploadCompressProgress} accept="image/*" > - - + {!isTouchDevice() && ( { @@ -90,29 +92,33 @@ export function PostEditorFormatToolbar({ insertEmoji(emoji) }} > - )} insertText(gifUrl)}> - insertText(memeUrl)}> - - - + +