From ac21e06df79c26ae1b18e314dbe2d464401706e2 Mon Sep 17 00:00:00 2001 From: Bojan Mojsilovic Date: Wed, 17 Apr 2024 16:19:08 +0200 Subject: [PATCH] Refactor zapps for note --- src/components/Note/Note.module.scss | 10 +++ src/components/Note/Note.tsx | 49 +++++++++---- .../ReactionsModal/ReactionsModal.tsx | 50 +++++++++++++- src/contexts/ThreadContext.tsx | 69 +++++++++++++++---- src/lib/notes.tsx | 22 +++++- 5 files changed, 169 insertions(+), 31 deletions(-) diff --git a/src/components/Note/Note.module.scss b/src/components/Note/Note.module.scss index 1ed9251..160f386 100644 --- a/src/components/Note/Note.module.scss +++ b/src/components/Note/Note.module.scss @@ -339,12 +339,17 @@ display: flex; align-items: center; gap: 8px; + padding-left: 2px; padding-right: 10px; + padding-block: 2px; + margin: 0; border-radius: 12px; background: var(--devider); width: fit-content; max-width: 100%; text-decoration: none; + border: none; + outline: none; .amount { color: var(--text-primary); @@ -395,11 +400,16 @@ display: flex; align-items: center; gap: 6px; + padding-left: 2px; padding-right: 10px; + padding-block: 2px; + margin: 0; border-radius: 12px; background: var(--devider); width: fit-content; text-decoration: none; + border: none; + outline: none; .amount { color: var(--text-primary); diff --git a/src/components/Note/Note.tsx b/src/components/Note/Note.tsx index 3f612da..3932902 100644 --- a/src/components/Note/Note.tsx +++ b/src/components/Note/Note.tsx @@ -1,5 +1,5 @@ import { A } from '@solidjs/router'; -import { batch, Component, For, Match, Show, Switch } from 'solid-js'; +import { batch, Component, createMemo, For, Match, Show, Switch } from 'solid-js'; import { PrimalNote, ZapOption } from '../../types/primal'; import ParsedNote from '../ParsedNote/ParsedNote'; import NoteFooter from './NoteFooter/NoteFooter'; @@ -18,6 +18,7 @@ import { CustomZapInfo, useAppContext } from '../../contexts/AppContext'; import NoteContextTrigger from './NoteContextTrigger'; import { date, longDate, veryLongDate } from '../../lib/dates'; import { hexToNpub } from '../../lib/keys'; +import { zapCustomOption } from '../../translations'; export type NoteFooterState = { likes: number, @@ -157,14 +158,31 @@ const Note: Component<{ return (likes || 0) + (zaps || 0) + (reposts || 0); }; - const firstZap = () => (threadContext?.topZaps[props.note.post.id] || [])[0]; + const firstZap = createMemo(() => (threadContext?.topZaps[props.note.post.id] || [])[0]); - const topZaps = () => { - return (threadContext?.topZaps[props.note.post.id] || []).slice(1, 8); - } + const topZaps = createMemo(() => { + // return (threadContext?.topZaps[props.note.post.id] || []).slice(1); + const zaps = (threadContext?.topZaps[props.note.post.id] || []).slice(1); + + let limit = 0; + let digits = 0; + + for (let i=0; i< zaps.length; i++) { + const amount = zaps[i].amount || 0; + const length = Math.log(amount) * Math.LOG10E + 1 | 0; + + digits += length; + + if (digits > 25 || limit > 6) break; + + limit++; + } + + return zaps.slice(0, limit); + }) const zapSender = (zap: TopZap) => { - return threadContext?.users.find(u => u.pubkey === zap.sender); + return threadContext?.users.find(u => u.pubkey === zap.pubkey); } return ( @@ -223,25 +241,32 @@ const Note: Component<{
- +
diff --git a/src/components/ReactionsModal/ReactionsModal.tsx b/src/components/ReactionsModal/ReactionsModal.tsx index 32c5280..9a63998 100644 --- a/src/components/ReactionsModal/ReactionsModal.tsx +++ b/src/components/ReactionsModal/ReactionsModal.tsx @@ -5,11 +5,12 @@ import { Component, createEffect, createSignal, For, onMount, Show } from 'solid import { createStore } from 'solid-js/store'; import { APP_ID } from '../../App'; import { Kind } from '../../constants'; +import { useAccountContext } from '../../contexts/AccountContext'; import { ReactionStats } from '../../contexts/AppContext'; import { hookForDev } from '../../lib/devTools'; import { hexToNpub } from '../../lib/keys'; -import { getEventReactions, getEventZaps } from '../../lib/notes'; -import { truncateNumber, truncateNumber2 } from '../../lib/notifications'; +import { getEventQuotes, getEventReactions, getEventZaps } from '../../lib/notes'; +import { truncateNumber2 } from '../../lib/notifications'; import { subscribeTo } from '../../sockets'; import { userName } from '../../stores/profile'; import { actions as tActions, placeholders as tPlaceholders, reactionsModal } from '../../translations'; @@ -30,18 +31,21 @@ const ReactionsModal: Component<{ }> = (props) => { const intl = useIntl(); + const account = useAccountContext(); const [selectedTab, setSelectedTab] = createSignal('likes'); const [likeList, setLikeList] = createStore([]); const [zapList, setZapList] = createStore([]); const [repostList, setRepostList] = createStore([]); + // const [quotesList, setQuotesList] = createStore([]); const [isFetching, setIsFetching] = createSignal(false); let loadedLikes = 0; let loadedZaps = 0; let loadedReposts = 0; + // let loadedQuotes = 0; createEffect(() => { if (props.noteId && props.stats.openOn) { @@ -60,6 +64,9 @@ const ReactionsModal: Component<{ case 'reposts': loadedReposts === 0 && getReposts(); break; + // case 'quotes': + // loadedQuotes === 0 && getQuotes(); + // break; } }); @@ -184,7 +191,7 @@ const ReactionsModal: Component<{ }); setIsFetching(() => true); - getEventZaps(props.noteId, subId, 20, offset); + getEventZaps(props.noteId, account?.publicKey, subId, 20, offset); // getEventReactions(props.noteId, Kind.Zap, subId, offset); }; @@ -225,6 +232,43 @@ const ReactionsModal: Component<{ getEventReactions(props.noteId, Kind.Repost, subId, offset); }; + // const getQuotes = (offset = 0) => { + // if (!props.noteId) return; + + // const subId = `nr_q_${props.noteId}_${APP_ID}`; + + // const users: any[] = []; + + // const unsub = subscribeTo(subId, (type,_, content) => { + // if (type === 'EOSE') { + // setQuotesList((reposts) => [...reposts, ...users]); + // loadedQuotes = quotesList.length; + // setIsFetching(() => false); + // unsub(); + // } + + // if (type === 'EVENT') { + // if (content?.kind === Kind.Metadata) { + // let user = JSON.parse(content.content); + + // if (!user.displayName || typeof user.displayName === 'string' && user.displayName.trim().length === 0) { + // user.displayName = user.display_name; + // } + // user.pubkey = content.pubkey; + // user.npub = hexToNpub(content.pubkey); + // user.created_at = content.created_at; + + // users.push(user); + + // return; + // } + // } + // }); + + // setIsFetching(() => true); + // getEventQuotes(props.noteId, subId, offset); + // }; + const totalCount = () => props.stats.likes + props.stats.quotes + props.stats.reposts + props.stats.zaps; return ( diff --git a/src/contexts/ThreadContext.tsx b/src/contexts/ThreadContext.tsx index 3971ed9..841ce83 100644 --- a/src/contexts/ThreadContext.tsx +++ b/src/contexts/ThreadContext.tsx @@ -38,15 +38,15 @@ import { } from "../types/primal"; import { APP_ID } from "../App"; import { useAccountContext } from "./AccountContext"; -import { setLinkPreviews } from "../lib/notes"; +import { getEventZaps, setLinkPreviews } from "../lib/notes"; +import { parseBolt11 } from "../utils"; export type TopZap = { - amount_sats: number, - created_at: number, - event_id: string, - receiver: string, - sender: string, - zap_receipt_id: string, + id: string, + amount: number, + pubkey: string, + message: string, + eventId: string, } export type ThreadContextStore = { @@ -112,6 +112,7 @@ export const ThreadProvider = (props: { children: ContextChildren }) => { clearNotes(); updateStore('noteId', noteId) getThread(account?.publicKey, noteId, `thread_${APP_ID}`); + getEventZaps(noteId, account?.publicKey, `thread_zapps_${APP_ID}`, 10, 0); updateStore('isFetching', () => true); } @@ -223,19 +224,50 @@ export const ThreadProvider = (props: { children: ContextChildren }) => { updateStore('page', 'relayHints', (rh) => ({ ...rh, ...hints })); } - if (content.kind === Kind.EventZapInfo) { - const zapInfo = JSON.parse(content.content) as TopZap; - if (store.topZaps[zapInfo.event_id] === undefined) { - updateStore('topZaps', () => ({ [zapInfo.event_id]: [{ ...zapInfo }]})); + if (content?.kind === Kind.Zap) { + const zapTag = content.tags.find(t => t[0] === 'description'); + + if (!zapTag) return; + + const zapInfo = JSON.parse(zapTag[1] || '{}'); + + let amount = '0'; + + let bolt11Tag = content?.tags?.find(t => t[0] === 'bolt11'); + + if (bolt11Tag) { + try { + amount = `${parseBolt11(bolt11Tag[1]) || 0}`; + } catch (e) { + const amountTag = zapInfo.tags.find((t: string[]) => t[0] === 'amount'); + + amount = amountTag ? amountTag[1] : '0'; + } + } + + const eventId = (zapInfo.tags.find((t: string[]) => t[0] === 'e') || [])[1]; + + const zap: TopZap = { + id: zapInfo.id, + amount: parseInt(amount || '0'), + pubkey: zapInfo.pubkey, + message: zapInfo.content, + eventId, + }; + + if (store.topZaps[eventId] === undefined) { + updateStore('topZaps', () => ({ [eventId]: [{ ...zap }]})); return; } - if (store.topZaps[zapInfo.event_id].find(i => i.zap_receipt_id === zapInfo.zap_receipt_id)) { + if (store.topZaps[eventId].find(i => i.id === zap.id)) { return; } - updateStore('topZaps', zapInfo.event_id, (zs) => [ ...zs, { ...zapInfo }]); + updateStore('topZaps', eventId, (zs) => [ ...zs, { ...zap }]); + + return; } }; @@ -299,6 +331,17 @@ export const ThreadProvider = (props: { children: ContextChildren }) => { return; } } + + if (subId === `thread_zapps_${APP_ID}`) { + if (type === 'EOSE') { + savePage(store.page); + } + + if (type === 'EVENT') { + updatePage(content); + return; + } + } }; const onSocketClose = (closeEvent: CloseEvent) => { diff --git a/src/lib/notes.tsx b/src/lib/notes.tsx index aeae4fb..bb2a7d7 100644 --- a/src/lib/notes.tsx +++ b/src/lib/notes.tsx @@ -6,6 +6,7 @@ import LinkPreview from "../components/LinkPreview/LinkPreview"; import { addrRegex, appleMusicRegex, emojiRegex, hashtagRegex, interpunctionRegex, Kind, linebreakRegex, lnRegex, lnUnifiedRegex, mixCloudRegex, nostrNestsRegex, noteRegex, noteRegexLocal, profileRegex, profileRegexG, soundCloudRegex, spotifyRegex, tagMentionRegex, twitchRegex, urlRegex, urlRegexG, wavlakeRegex, youtubeRegex } from "../constants"; import { sendMessage, subscribeTo } from "../sockets"; import { MediaSize, NostrRelays, NostrRelaySignedEvent, PrimalNote, SendNoteResult } from "../types/primal"; +import { npubToHex } from "./keys"; import { logError, logInfo, logWarning } from "./logger"; import { getMediaUrl as getMediaUrlDefault } from "./media"; import { signEvent } from "./nostrAPI"; @@ -541,18 +542,33 @@ export const triggerImportEvents = (events: NostrRelaySignedEvent[], subId: stri export const getEventReactions = (eventId: string, kind: number, subid: string, offset = 0) => { + const event_id = eventId.startsWith('note1') ? npubToHex(eventId) : eventId; + sendMessage(JSON.stringify([ "REQ", subid, - {cache: ["event_actions", { event_id: eventId, kind, limit: 20, offset }]}, + {cache: ["event_actions", { event_id, kind, limit: 20, offset }]}, ])); }; +export const getEventQuotes = (eventId: string, subid: string, offset = 0) => { + const event_id = eventId.startsWith('note1') ? npubToHex(eventId) : eventId; -export const getEventZaps = (eventId: string, subid: string, limit: number, offset = 0) => { sendMessage(JSON.stringify([ "REQ", subid, - {cache: ["event_zaps_by_satszapped", { event_id: eventId, limit, offset }]}, + {cache: ["note_mentions", { event_id, limit: 20, offset }]}, + ])); +}; + +export const getEventZaps = (eventId: string, user_pubkey: string | undefined, subid: string, limit: number, offset = 0) => { + if (!user_pubkey) return; + + const event_id = eventId.startsWith('note1') ? npubToHex(eventId) : eventId; + + sendMessage(JSON.stringify([ + "REQ", + subid, + {cache: ["event_zaps_by_satszapped", { event_id, user_pubkey, limit, offset }]}, ])); };