Browse Source

make the notifications page more advanced

imwald
Silberengel 1 month ago
parent
commit
579b1e533b
  1. 5
      src/i18n/locales/de.ts
  2. 5
      src/i18n/locales/en.ts
  3. 108
      src/pages/primary/SpellsPage/index.tsx
  4. 6
      src/providers/NostrProvider/index.tsx
  5. 3
      src/providers/nostr-context.tsx

5
src/i18n/locales/de.ts

@ -596,6 +596,11 @@ export default { @@ -596,6 +596,11 @@ export default {
Pay: 'Bezahlen',
interactions: 'Interaktionen',
notifications: 'Benachrichtigungen',
notificationsViewAsAccount: 'Anzeigen als',
notificationsViewAsAccountAria:
'Gespeichertes Konto wechseln. Benachrichtigungen, Antworten, Zaps, Reaktionen und Boosts nutzen dieses Konto.',
notificationsSwitchAccountFailed:
'Kontowechsel fehlgeschlagen. Signierer, Passwort oder Erweiterung prüfen und erneut versuchen.',
'Show untrusted {type}': 'Untrusted {{type}} anzeigen',
'Hide untrusted {type}': 'Untrusted {{type}} ausblenden',
'Currently hiding {type} from untrusted users.':

5
src/i18n/locales/en.ts

@ -617,6 +617,11 @@ export default { @@ -617,6 +617,11 @@ export default {
Pay: 'Pay',
interactions: 'interactions',
notifications: 'notifications',
notificationsViewAsAccount: 'View as',
notificationsViewAsAccountAria:
'Switch stored account. Notifications, replies, zaps, reactions, and boosts use this account.',
notificationsSwitchAccountFailed:
'Could not switch account. Check your signer, password, or extension and try again.',
'Show untrusted {type}': 'Show untrusted {{type}}',
'Hide untrusted {type}': 'Hide untrusted {{type}}',
'Currently hiding {type} from untrusted users.':

108
src/pages/primary/SpellsPage/index.tsx

@ -15,6 +15,13 @@ import { @@ -15,6 +15,13 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@/components/ui/select'
import { Separator } from '@/components/ui/separator'
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from '@/components/ui/drawer'
import UserAvatar from '@/components/UserAvatar'
@ -50,7 +57,7 @@ import { @@ -50,7 +57,7 @@ import {
FIRST_RELAY_RESULT_GRACE_MS,
} from '@/constants'
import { filterEventsExcludingTombstones, isUserInEventMentions } from '@/lib/event'
import { formatPubkey } from '@/lib/pubkey'
import { formatPubkey, hexPubkeysEqual, normalizeHexPubkey } from '@/lib/pubkey'
import {
augmentSubRequestsWithFavoritesFastReadAndInbox,
getRelayUrlsWithFavoritesFastReadAndInbox
@ -98,6 +105,7 @@ import type { Event } from 'nostr-tools' @@ -98,6 +105,7 @@ import type { Event } from 'nostr-tools'
import { kinds as nostrKinds, verifyEvent } from 'nostr-tools'
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import CreateSpellDialog from './CreateSpellDialog'
import {
appendCuratedReadOnlyRelays,
@ -310,8 +318,17 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -310,8 +318,17 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
) {
const { t } = useTranslation()
const { navigate: navigatePrimary } = usePrimaryPage()
const { pubkey, account, relayList, attemptDelete, bookmarkListEvent, interestListEvent } =
useNostr()
const {
pubkey,
account,
accounts,
relayList,
attemptDelete,
bookmarkListEvent,
interestListEvent,
switchAccount,
isAccountSessionHydrating
} = useNostr()
const { addBookmark, removeBookmark } = useBookmarks()
const { hideUntrustedNotifications } = useUserTrust()
const { isSmallScreen } = useScreenSize()
@ -352,6 +369,43 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -352,6 +369,43 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
const spellFeedInstrLabelRef = useRef('')
const [spellFeedInstrumentToken, setSpellFeedInstrumentToken] = useState(0)
const [followSetManualRefreshKey, setFollowSetManualRefreshKey] = useState(0)
/** Notifications `#p` + mention filter track the active session; changing the dropdown calls {@link switchAccount}. */
const notificationsFeedPubkey = useMemo(() => {
const cur = pubkey?.trim()
return cur ? normalizeHexPubkey(cur) : null
}, [pubkey])
const storedAccountPubkeys = useMemo(() => {
const seen = new Set<string>()
const out: string[] = []
for (const a of accounts) {
const raw = a.pubkey?.trim()
if (!raw) continue
const p = normalizeHexPubkey(raw)
if (!seen.has(p)) {
seen.add(p)
out.push(p)
}
}
return out
}, [accounts])
const handleNotificationsAccountPick = useCallback(
async (v: string) => {
const target = normalizeHexPubkey(v)
if (pubkey && hexPubkeysEqual(target, pubkey)) return
const nextAccount = accounts.find((a) => hexPubkeysEqual(a.pubkey, target))
if (!nextAccount) {
toast.error(t('notificationsSwitchAccountFailed'))
return
}
const switched = await switchAccount(nextAccount)
if (!switched || !hexPubkeysEqual(normalizeHexPubkey(switched), target)) {
toast.error(t('notificationsSwitchAccountFailed'))
}
},
[pubkey, accounts, switchAccount, t]
)
const logSpellFeedPickerSelection = useCallback((label: string, extra?: Record<string, unknown>) => {
spellFeedInstrT0Ref.current = performance.now()
@ -835,8 +889,8 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -835,8 +889,8 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
)
if (selectedFauxSpell === 'notifications') {
if (!pubkey || !feedUrls.length) return []
return buildNotificationsSpellSubRequests(feedUrls, pubkey)
if (!notificationsFeedPubkey || !feedUrls.length) return []
return buildNotificationsSpellSubRequests(feedUrls, notificationsFeedPubkey)
}
if (selectedFauxSpell === 'discussions') {
// Read-only prepended in appendCuratedReadOnlyRelays so FAUX_SPELL_MAX_RELAYS still includes aggr.
@ -876,7 +930,7 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -876,7 +930,7 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
]
}
return []
}, [selectedFauxSpell, pubkey, fauxFeedRelaysDepsKey, relayMailboxStableKey])
}, [selectedFauxSpell, pubkey, notificationsFeedPubkey, fauxFeedRelaysDepsKey, relayMailboxStableKey])
const fauxSubRequests = useMemo<TFeedSubRequest[]>(() => {
const base = isFollowFeedFauxSpellId(selectedFauxSpell ?? '')
@ -1205,8 +1259,9 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -1205,8 +1259,9 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
}, [selectedFauxSpell])
const notificationsMentionExtraHide = useCallback(
(evt: Event) => (pubkey ? !isUserInEventMentions(evt, pubkey) : false),
[pubkey]
(evt: Event) =>
notificationsFeedPubkey ? !isUserInEventMentions(evt, notificationsFeedPubkey) : false,
[notificationsFeedPubkey]
)
const fauxFeedEmptyMessage = useMemo(() => {
@ -1670,7 +1725,40 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -1670,7 +1725,40 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
) : selectedFauxSpell && fauxSubRequests.length > 0 ? (
<>
{selectedFauxSpell === 'notifications' ? (
<div className="flex shrink-0 justify-end px-1 pb-2">
<div className="flex shrink-0 flex-wrap items-center justify-end gap-2 px-1 pb-2 sm:justify-between">
{storedAccountPubkeys.length > 1 && notificationsFeedPubkey ? (
<div className="flex min-w-0 flex-1 items-center gap-2 sm:max-w-[min(100%,20rem)]">
<span className="hidden shrink-0 text-xs text-muted-foreground sm:inline">
{t('notificationsViewAsAccount')}
</span>
<Select
value={notificationsFeedPubkey}
disabled={isAccountSessionHydrating}
onValueChange={(v) => void handleNotificationsAccountPick(v)}
>
<SelectTrigger
className="h-9 min-w-0 flex-1"
aria-label={t('notificationsViewAsAccountAria')}
>
<SelectValue />
</SelectTrigger>
<SelectContent position="popper">
{storedAccountPubkeys.map((pk) => (
<SelectItem key={pk} value={pk}>
<span className="flex min-w-0 items-center gap-2">
<UserAvatar userId={pk} size="small" className="shrink-0" />
<Username
userId={pk}
className="min-w-0 truncate text-left font-normal"
skeletonClassName="h-4 w-24"
/>
</span>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
) : null}
<HideUntrustedContentButton type="notifications" size="titlebar-icon" />
</div>
) : null}
@ -1712,7 +1800,7 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -1712,7 +1800,7 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
: false
}
extraShouldHideEvent={
selectedFauxSpell === 'notifications' && pubkey
selectedFauxSpell === 'notifications' && notificationsFeedPubkey
? notificationsMentionExtraHide
: undefined
}

6
src/providers/NostrProvider/index.tsx

@ -840,14 +840,14 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { @@ -840,14 +840,14 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
}
}
const switchAccount = async (act: TAccountPointer | null) => {
const switchAccount = async (act: TAccountPointer | null): Promise<string | null> => {
if (!act) {
storage.switchAccount(null)
setAccount(null)
setSigner(null)
return
return null
}
await loginWithAccountPointer(act)
return await loginWithAccountPointer(act)
}
const finishNcryptsecPasswordPrompt = useCallback((password: string | null) => {

3
src/providers/nostr-context.tsx

@ -35,7 +35,8 @@ export type TNostrContext = { @@ -35,7 +35,8 @@ export type TNostrContext = {
accounts: TAccountPointer[]
nsec: string | null
ncryptsec: string | null
switchAccount: (account: TAccountPointer | null) => Promise<void>
/** Returns the new session pubkey on success, or `null` if logout / switch failed. */
switchAccount: (account: TAccountPointer | null) => Promise<string | null>
nsecLogin: (nsec: string, password?: string, needSetup?: boolean) => Promise<string>
ncryptsecLogin: (ncryptsec: string) => Promise<string>
nip07Login: () => Promise<string>

Loading…
Cancel
Save