Refactor zapps for note

This commit is contained in:
Bojan Mojsilovic 2024-04-17 16:19:08 +02:00
parent d06c74f466
commit ac21e06df7
5 changed files with 169 additions and 31 deletions

View File

@ -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);

View File

@ -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<{
<div class={styles.zapHighlights}>
<Show when={firstZap()}>
<A class={styles.firstZap} href={`/p/${hexToNpub(firstZap().sender)}`}>
<button
class={styles.firstZap}
onClick={() => openReactionModal('zaps')}
>
<Avatar user={zapSender(firstZap())} size="micro" />
<div class={styles.amount}>
{firstZap().amount_sats.toLocaleString()}
{firstZap().amount.toLocaleString()}
</div>
<div class={styles.description}>
{firstZap().message}
</div>
</A>
</button>
</Show>
<div class={styles.topZaps}>
<div class={styles.zapList}>
<For each={topZaps()}>
{zap => (
<A class={styles.topZap} href={`/p/${hexToNpub(zap.sender)}`}>
<button
class={styles.topZap}
onClick={() => openReactionModal('zaps')}
>
<Avatar user={zapSender(zap)} size="micro" />
<div class={styles.amount}>
{zap.amount_sats.toLocaleString()}
{zap.amount.toLocaleString()}
</div>
</A>
</button>
)}
</For>
</div>

View File

@ -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<any[]>([]);
const [zapList, setZapList] = createStore<any[]>([]);
const [repostList, setRepostList] = createStore<any[]>([]);
// const [quotesList, setQuotesList] = createStore<any[]>([]);
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 (

View File

@ -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) => {

View File

@ -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 }]},
]));
};