From df4d5e52aef2191cfaad73d4b87f25b395b10fc3 Mon Sep 17 00:00:00 2001 From: codytseng Date: Sun, 19 Jan 2025 22:17:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=92=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants.ts | 1 + src/pages/secondary/SettingsPage/index.tsx | 3 +- src/providers/MuteListProvider.tsx | 55 ++++++++++++---------- src/services/storage.service.ts | 31 ++++++++++++ 4 files changed, 64 insertions(+), 26 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index b42fbda9..f5a085f7 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -8,6 +8,7 @@ export const StorageKey = { ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', ACCOUNT_FOLLOW_LIST_EVENT_MAP: 'accountFollowListEventMap', ACCOUNT_MUTE_LIST_EVENT_MAP: 'accountMuteListEventMap', + ACCOUNT_MUTE_DECRYPTED_TAGS_MAP: 'accountMuteDecryptedTagsMap', ACCOUNT_PROFILE_EVENT_MAP: 'accountProfileEventMap', ADD_CLIENT_TAG: 'addClientTag' } diff --git a/src/pages/secondary/SettingsPage/index.tsx b/src/pages/secondary/SettingsPage/index.tsx index 8b9ae3f5..7d3276d9 100644 --- a/src/pages/secondary/SettingsPage/index.tsx +++ b/src/pages/secondary/SettingsPage/index.tsx @@ -115,7 +115,7 @@ export default function SettingsPage({ index }: { index?: number }) { } const SettingItem = forwardRef>( - ({ children, className, ...props }) => { + ({ children, className, ...props }, ref) => { return (
>( className )} {...props} + ref={ref} > {children}
diff --git a/src/providers/MuteListProvider.tsx b/src/providers/MuteListProvider.tsx index fb3e3015..948f12dd 100644 --- a/src/providers/MuteListProvider.tsx +++ b/src/providers/MuteListProvider.tsx @@ -30,28 +30,6 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) { const [tags, setTags] = useState([]) const mutePubkeys = useMemo(() => extractPubkeysFromEventTags(tags), [tags]) - useEffect(() => { - if (!muteListEvent) { - setTags([]) - return - } - - const updateTags = async () => { - const tags = muteListEvent.tags - if (muteListEvent.content && accountPubkey) { - try { - const plainText = await nip04Decrypt(accountPubkey, muteListEvent.content) - const contentTags = z.array(z.array(z.string())).parse(JSON.parse(plainText)) - tags.push(...contentTags.filter((tag) => tags.every((t) => !isSameTag(t, tag)))) - } catch { - // ignore - } - } - setTags(tags) - } - updateTags() - }, [muteListEvent]) - useEffect(() => { if (!accountPubkey) return @@ -60,6 +38,8 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) { const storedMuteListEvent = storage.getAccountMuteListEvent(accountPubkey) if (storedMuteListEvent) { setMuteListEvent(storedMuteListEvent) + const tags = await extractMuteTags(storedMuteListEvent) + setTags(tags) } const events = await client.fetchEvents(relayList?.write ?? client.getDefaultRelayUrls(), { kinds: [kinds.Mutelist], @@ -68,16 +48,41 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) { const muteEvent = getLatestEvent(events) as Event | undefined if (muteEvent) { setMuteListEvent(muteEvent) + const tags = await extractMuteTags(muteEvent) + setTags(tags) } } init() }, [accountPubkey]) - const updateMuteListEvent = (event: Event) => { + const extractMuteTags = async (muteListEvent: Event) => { + const tags = [...muteListEvent.tags] + if (muteListEvent.content) { + const storedDecryptedTags = storage.getAccountMuteDecryptedTags(muteListEvent) + + if (storedDecryptedTags) { + tags.push(...storedDecryptedTags) + } else { + try { + const plainText = await nip04Decrypt(muteListEvent.pubkey, muteListEvent.content) + const contentTags = z.array(z.array(z.string())).parse(JSON.parse(plainText)) + storage.setAccountMuteDecryptedTags(muteListEvent, contentTags) + tags.push(...contentTags.filter((tag) => tags.every((t) => !isSameTag(t, tag)))) + } catch (error) { + console.error('Failed to decrypt mute list content', error) + } + } + } + return tags + } + + const update = (event: Event, tags: string[][]) => { const isNew = storage.setAccountMuteListEvent(event) if (!isNew) return + storage.setAccountMuteDecryptedTags(event, tags) setMuteListEvent(event) + setTags(tags) } const mutePubkey = async (pubkey: string) => { @@ -87,7 +92,7 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) { const cipherText = await nip04Encrypt(accountPubkey, JSON.stringify(newTags)) const newMuteListDraftEvent = createMuteListDraftEvent(muteListEvent?.tags ?? [], cipherText) const newMuteListEvent = await publish(newMuteListDraftEvent) - updateMuteListEvent(newMuteListEvent) + update(newMuteListEvent, newTags) } const unmutePubkey = async (pubkey: string) => { @@ -100,7 +105,7 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) { cipherText ) const newMuteListEvent = await publish(newMuteListDraftEvent) - updateMuteListEvent(newMuteListEvent) + update(newMuteListEvent, newTags) } return ( diff --git a/src/services/storage.service.ts b/src/services/storage.service.ts index 032b07ac..ce4ee015 100644 --- a/src/services/storage.service.ts +++ b/src/services/storage.service.ts @@ -29,6 +29,10 @@ class StorageService { private accountRelayListEventMap: Record = {} // pubkey -> relayListEvent private accountFollowListEventMap: Record = {} // pubkey -> followListEvent private accountMuteListEventMap: Record = {} // pubkey -> muteListEvent + private accountMuteDecryptedTagsMap: Record< + string, + { id: string; tags: string[][] } | undefined + > = {} // pubkey -> { id, tags } private accountProfileEventMap: Record = {} // pubkey -> profileEvent constructor() { @@ -67,6 +71,12 @@ class StorageService { this.accountMuteListEventMap = accountMuteListEventMapStr ? JSON.parse(accountMuteListEventMapStr) : {} + const accountMuteDecryptedTagsMapStr = window.localStorage.getItem( + StorageKey.ACCOUNT_MUTE_DECRYPTED_TAGS_MAP + ) + this.accountMuteDecryptedTagsMap = accountMuteDecryptedTagsMapStr + ? JSON.parse(accountMuteDecryptedTagsMapStr) + : {} const accountProfileEventMapStr = window.localStorage.getItem( StorageKey.ACCOUNT_PROFILE_EVENT_MAP ) @@ -184,6 +194,7 @@ class StorageService { delete this.accountFollowListEventMap[account.pubkey] delete this.accountRelayListEventMap[account.pubkey] delete this.accountMuteListEventMap[account.pubkey] + delete this.accountMuteDecryptedTagsMap[account.pubkey] delete this.accountProfileEventMap[account.pubkey] window.localStorage.setItem(StorageKey.ACCOUNTS, JSON.stringify(this.accounts)) window.localStorage.setItem( @@ -194,6 +205,10 @@ class StorageService { StorageKey.ACCOUNT_MUTE_LIST_EVENT_MAP, JSON.stringify(this.accountMuteListEventMap) ) + window.localStorage.setItem( + StorageKey.ACCOUNT_MUTE_DECRYPTED_TAGS_MAP, + JSON.stringify(this.accountMuteDecryptedTagsMap) + ) window.localStorage.setItem( StorageKey.ACCOUNT_RELAY_LIST_EVENT_MAP, JSON.stringify(this.accountRelayListEventMap) @@ -276,6 +291,22 @@ class StorageService { return true } + getAccountMuteDecryptedTags(muteListEvent: Event) { + const stored = this.accountMuteDecryptedTagsMap[muteListEvent.pubkey] + if (stored && stored.id === muteListEvent.id) { + return stored.tags + } + return null + } + + setAccountMuteDecryptedTags(muteListEvent: Event, tags: string[][]) { + this.accountMuteDecryptedTagsMap[muteListEvent.pubkey] = { id: muteListEvent.id, tags } + window.localStorage.setItem( + StorageKey.ACCOUNT_MUTE_DECRYPTED_TAGS_MAP, + JSON.stringify(this.accountMuteDecryptedTagsMap) + ) + } + getAccountProfileEvent(pubkey: string) { return this.accountProfileEventMap[pubkey] }