mirror of
https://github.com/PrimalHQ/primal-web-app.git
synced 2024-10-01 17:31:13 +00:00
Note quotes reactions
This commit is contained in:
parent
a12bfc573d
commit
00f976ae78
@ -17,6 +17,10 @@
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
&.altBack {
|
||||
background-color: var(--background-sheet);
|
||||
}
|
||||
|
||||
.mentionedNoteHeader {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
|
@ -15,7 +15,13 @@ import VerificationCheck from '../VerificationCheck/VerificationCheck';
|
||||
|
||||
import styles from './EmbeddedNote.module.scss';
|
||||
|
||||
const EmbeddedNote: Component<{ note: PrimalNote, mentionedUsers?: Record<string, PrimalUser>, includeEmbeds?: boolean, isLast?: boolean}> = (props) => {
|
||||
const EmbeddedNote: Component<{
|
||||
note: PrimalNote,
|
||||
mentionedUsers?: Record<string, PrimalUser>,
|
||||
includeEmbeds?: boolean,
|
||||
isLast?: boolean,
|
||||
alternativeBackground?: boolean,
|
||||
}> = (props) => {
|
||||
|
||||
const threadContext = useThreadContext();
|
||||
const intl = useIntl();
|
||||
@ -32,11 +38,20 @@ const EmbeddedNote: Component<{ note: PrimalNote, mentionedUsers?: Record<string
|
||||
return trimVerification(props.note.user?.nip05);
|
||||
});
|
||||
|
||||
const klass = () => {
|
||||
let k = styles.mentionedNote;
|
||||
k += ' embeddedNote';
|
||||
if (props.isLast) k+= ' noBottomMargin';
|
||||
if (props.alternativeBackground) k+= ` ${styles.altBack}`;
|
||||
|
||||
return k;
|
||||
}
|
||||
|
||||
const wrapper = (children: JSXElement) => {
|
||||
if (props.includeEmbeds) {
|
||||
return (
|
||||
<div
|
||||
class={`${styles.mentionedNote} embeddedNote ${props.isLast ? 'noBottomMargin' : ''}`}
|
||||
class={klass()}
|
||||
data-event={props.note.post.id}
|
||||
data-event-bech32={noteId()}
|
||||
>
|
||||
@ -48,7 +63,7 @@ const EmbeddedNote: Component<{ note: PrimalNote, mentionedUsers?: Record<string
|
||||
return (
|
||||
<A
|
||||
href={`/e/${noteId()}`}
|
||||
class={`${styles.mentionedNote} embeddedNote ${props.isLast ? 'noBottomMargin' : ''}`}
|
||||
class={klass()}
|
||||
onClick={() => navToThread()}
|
||||
data-event={props.note.post.id}
|
||||
data-event-bech32={noteId()}
|
||||
|
@ -23,6 +23,11 @@
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.reactionNote {
|
||||
background: none;
|
||||
border-bottom: 1px solid var(--subtile-devider);
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
@ -22,7 +22,7 @@ import { thread, zapCustomOption } from '../../translations';
|
||||
import { useAccountContext } from '../../contexts/AccountContext';
|
||||
import { uuidv4 } from '../../utils';
|
||||
|
||||
export type NoteFooterState = {
|
||||
export type NoteReactionsState = {
|
||||
likes: number,
|
||||
liked: boolean,
|
||||
reposts: number,
|
||||
@ -40,6 +40,7 @@ export type NoteFooterState = {
|
||||
moreZapsAvailable: boolean,
|
||||
isRepostMenuVisible: boolean,
|
||||
topZaps: TopZap[],
|
||||
quoteCount: number,
|
||||
};
|
||||
|
||||
const Note: Component<{
|
||||
@ -47,7 +48,9 @@ const Note: Component<{
|
||||
id?: string,
|
||||
parent?: boolean,
|
||||
shorten?: boolean,
|
||||
noteType?: 'feed' | 'primary' | 'notification'
|
||||
noteType?: 'feed' | 'primary' | 'notification' | 'reaction'
|
||||
onClick?: () => void,
|
||||
quoteCount?: number,
|
||||
}> = (props) => {
|
||||
|
||||
const threadContext = useThreadContext();
|
||||
@ -55,15 +58,22 @@ const Note: Component<{
|
||||
const account = useAccountContext();
|
||||
const intl = useIntl();
|
||||
|
||||
createEffect(() => {
|
||||
if (props.quoteCount) {
|
||||
updateReactionsState('quoteCount', () => props.quoteCount || 0);
|
||||
}
|
||||
})
|
||||
|
||||
const noteType = () => props.noteType || 'feed';
|
||||
|
||||
const repost = () => props.note.repost;
|
||||
|
||||
const navToThread = (note: PrimalNote) => {
|
||||
props.onClick && props.onClick();
|
||||
threadContext?.actions.setPrimaryNote(note);
|
||||
};
|
||||
|
||||
const [reactionsState, updateReactionsState] = createStore<NoteFooterState>({
|
||||
const [reactionsState, updateReactionsState] = createStore<NoteReactionsState>({
|
||||
likes: props.note.post.likes,
|
||||
liked: props.note.post.noteActions.liked,
|
||||
reposts: props.note.post.reposts,
|
||||
@ -81,6 +91,7 @@ const Note: Component<{
|
||||
moreZapsAvailable: false,
|
||||
isRepostMenuVisible: false,
|
||||
topZaps: [],
|
||||
quoteCount: props.quoteCount || 0,
|
||||
});
|
||||
|
||||
let noteContextMenu: HTMLDivElement | undefined;
|
||||
@ -173,7 +184,7 @@ const Note: Component<{
|
||||
likes: reactionsState.likes,
|
||||
zaps: reactionsState.zapCount,
|
||||
reposts: reactionsState.reposts,
|
||||
quotes: 0,
|
||||
quotes: reactionsState.quoteCount,
|
||||
openOn,
|
||||
});
|
||||
};
|
||||
@ -190,9 +201,9 @@ const Note: Component<{
|
||||
}
|
||||
|
||||
const reactionSum = () => {
|
||||
const { likes, zapCount, reposts } = reactionsState;
|
||||
const { likes, zapCount, reposts, quoteCount } = reactionsState;
|
||||
|
||||
return (likes || 0) + (zapCount || 0) + (reposts || 0);
|
||||
return (likes || 0) + (zapCount || 0) + (reposts || 0) + (quoteCount || 0);
|
||||
};
|
||||
|
||||
const firstZap = createMemo(() => reactionsState.topZaps[0]);
|
||||
@ -420,6 +431,50 @@ const Note: Component<{
|
||||
</div>
|
||||
</A>
|
||||
</Match>
|
||||
|
||||
<Match when={noteType() === 'reaction'}>
|
||||
<A
|
||||
id={props.id}
|
||||
class={`${styles.note} ${styles.reactionNote}`}
|
||||
href={`/e/${props.note?.post.noteId}`}
|
||||
onClick={() => navToThread(props.note)}
|
||||
data-event={props.note.post.id}
|
||||
data-event-bech32={props.note.post.noteId}
|
||||
draggable={false}
|
||||
>
|
||||
<div class={styles.content}>
|
||||
<div class={styles.leftSide}>
|
||||
<A href={`/p/${props.note.user.npub}`}>
|
||||
<Avatar user={props.note.user} size="vs" />
|
||||
</A>
|
||||
<Show
|
||||
when={props.parent}
|
||||
>
|
||||
<div class={styles.ancestorLine}></div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<div class={styles.rightSide}>
|
||||
<NoteAuthorInfo
|
||||
author={props.note.user}
|
||||
time={props.note.post.created_at}
|
||||
/>
|
||||
|
||||
<NoteReplyToHeader note={props.note} />
|
||||
|
||||
<div class={styles.message}>
|
||||
<ParsedNote
|
||||
note={props.note}
|
||||
shorten={props.shorten}
|
||||
width={Math.min(528, window.innerWidth - 72)}
|
||||
noLightbox={true}
|
||||
altEmbeds={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</A>
|
||||
</Match>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import { getScreenCordinates } from '../../../utils';
|
||||
import ZapAnimation from '../../ZapAnimation/ZapAnimation';
|
||||
import { CustomZapInfo, useAppContext } from '../../../contexts/AppContext';
|
||||
import NoteFooterActionButton from './NoteFooterActionButton';
|
||||
import { NoteFooterState } from '../Note';
|
||||
import { NoteReactionsState } from '../Note';
|
||||
import { SetStoreFunction } from 'solid-js/store';
|
||||
import BookmarkNote from '../../BookmarkNote/BookmarkNote';
|
||||
import { APP_ID } from '../../../App';
|
||||
@ -30,8 +30,8 @@ const NoteFooter: Component<{
|
||||
note: PrimalNote,
|
||||
wide?: boolean,
|
||||
id?: string,
|
||||
state: NoteFooterState,
|
||||
updateState: SetStoreFunction<NoteFooterState>,
|
||||
state: NoteReactionsState,
|
||||
updateState: SetStoreFunction<NoteReactionsState>,
|
||||
customZapInfo: CustomZapInfo,
|
||||
large?: boolean,
|
||||
}> = (props) => {
|
||||
|
@ -132,6 +132,7 @@ const NoteImage: Component<{
|
||||
data-pswp-height={zoomH()}
|
||||
data-image-group={props.imageGroup}
|
||||
data-cropped={true}
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
id={imgId}
|
||||
|
@ -103,3 +103,17 @@
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.reactionNote {
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 48px;
|
||||
font-weight: 700;
|
||||
margin-block: 4px;
|
||||
padding-inline: 12px;
|
||||
|
||||
border: 1px solid var(--subtile-devider);
|
||||
border-radius: var(--border-radius-small);
|
||||
}
|
||||
|
@ -144,6 +144,8 @@ const ParsedNote: Component<{
|
||||
shorten?: boolean,
|
||||
isEmbeded?: boolean,
|
||||
width?: number,
|
||||
noLightbox?: boolean,
|
||||
altEmbeds?: boolean,
|
||||
}> = (props) => {
|
||||
|
||||
const intl = useIntl();
|
||||
@ -172,6 +174,8 @@ const ParsedNote: Component<{
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
if (props.noLightbox) return;
|
||||
|
||||
lightbox.init();
|
||||
});
|
||||
|
||||
@ -927,6 +931,7 @@ const ParsedNote: Component<{
|
||||
note={ment}
|
||||
mentionedUsers={props.note.mentionedUsers || {}}
|
||||
isLast={index === content.length-1}
|
||||
alternativeBackground={props.altEmbeds}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
padding: 16px;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
@ -73,7 +73,7 @@
|
||||
.tab {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding-inline: 14px;
|
||||
padding-inline: 12px;
|
||||
padding-block: 2px;
|
||||
border: none;
|
||||
background: none;
|
||||
@ -107,13 +107,14 @@
|
||||
height: 440px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
padding-right: 8px;
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity:0;
|
||||
opacity:0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity:1;
|
||||
opacity:1;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useIntl } from '@cookbook/solid-intl';
|
||||
import { Tabs } from '@kobalte/core';
|
||||
import { A } from '@solidjs/router';
|
||||
import { Component, createEffect, createSignal, For, onMount, Show } from 'solid-js';
|
||||
import { Component, createEffect, createSignal, For, Match, onMount, Show, Switch } from 'solid-js';
|
||||
import { createStore } from 'solid-js/store';
|
||||
import { APP_ID } from '../../App';
|
||||
import { Kind } from '../../constants';
|
||||
@ -9,20 +9,25 @@ import { useAccountContext } from '../../contexts/AccountContext';
|
||||
import { ReactionStats } from '../../contexts/AppContext';
|
||||
import { hookForDev } from '../../lib/devTools';
|
||||
import { hexToNpub } from '../../lib/keys';
|
||||
import { getEventQuotes, getEventReactions, getEventZaps } from '../../lib/notes';
|
||||
import { getEventQuotes, getEventQuoteStats, getEventReactions, getEventZaps, setLinkPreviews } from '../../lib/notes';
|
||||
import { truncateNumber2 } from '../../lib/notifications';
|
||||
import { updateStore } from '../../services/StoreService';
|
||||
import { subscribeTo } from '../../sockets';
|
||||
import { convertToNotes } from '../../stores/note';
|
||||
import { userName } from '../../stores/profile';
|
||||
import { actions as tActions, placeholders as tPlaceholders, reactionsModal } from '../../translations';
|
||||
import { FeedPage, NostrMentionContent, NostrNoteActionsContent, NostrNoteContent, NostrStatsContent, NostrUserContent, NoteActions, PrimalNote, PrimalUser } from '../../types/primal';
|
||||
import { parseBolt11 } from '../../utils';
|
||||
import Avatar from '../Avatar/Avatar';
|
||||
import Loader from '../Loader/Loader';
|
||||
import Modal from '../Modal/Modal';
|
||||
import Note from '../Note/Note';
|
||||
import Paginator from '../Paginator/Paginator';
|
||||
import VerificationCheck from '../VerificationCheck/VerificationCheck';
|
||||
|
||||
import styles from './ReactionsModal.module.scss';
|
||||
|
||||
|
||||
const ReactionsModal: Component<{
|
||||
id?: string,
|
||||
noteId: string | undefined,
|
||||
@ -38,18 +43,33 @@ const ReactionsModal: Component<{
|
||||
const [likeList, setLikeList] = createStore<any[]>([]);
|
||||
const [zapList, setZapList] = createStore<any[]>([]);
|
||||
const [repostList, setRepostList] = createStore<any[]>([]);
|
||||
// const [quotesList, setQuotesList] = createStore<any[]>([]);
|
||||
const [quotesList, setQuotesList] = createStore<PrimalNote[]>([]);
|
||||
const [quoteCount, setQuoteCount] = createSignal(0);
|
||||
|
||||
const [isFetching, setIsFetching] = createSignal(false);
|
||||
|
||||
let loadedLikes = 0;
|
||||
let loadedZaps = 0;
|
||||
let loadedReposts = 0;
|
||||
// let loadedQuotes = 0;
|
||||
let loadedQuotes = 0;
|
||||
|
||||
createEffect(() => {
|
||||
const count = quoteCount();
|
||||
|
||||
if (count === 0 && props.stats.quotes > 0) {
|
||||
setQuoteCount(props.stats.quotes);
|
||||
}
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (props.noteId && props.stats.openOn) {
|
||||
setSelectedTab(props.stats.openOn)
|
||||
setSelectedTab(props.stats.openOn);
|
||||
}
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
if (props.noteId) {
|
||||
getQuoteCount();
|
||||
}
|
||||
});
|
||||
|
||||
@ -64,9 +84,9 @@ const ReactionsModal: Component<{
|
||||
case 'reposts':
|
||||
loadedReposts === 0 && getReposts();
|
||||
break;
|
||||
// case 'quotes':
|
||||
// loadedQuotes === 0 && getQuotes();
|
||||
// break;
|
||||
case 'quotes':
|
||||
loadedQuotes === 0 && getQuotes();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@ -76,10 +96,13 @@ const ReactionsModal: Component<{
|
||||
setZapList(() => []);
|
||||
setRepostList(() => []);
|
||||
setSelectedTab(() => 'likes');
|
||||
setQuotesList(() => []);
|
||||
setQuoteCount(() => 0);
|
||||
|
||||
loadedLikes = 0;
|
||||
loadedZaps = 0;
|
||||
loadedReposts = 0;
|
||||
loadedQuotes = 0;
|
||||
}
|
||||
});
|
||||
|
||||
@ -203,6 +226,7 @@ const ReactionsModal: Component<{
|
||||
const users: any[] = [];
|
||||
|
||||
const unsub = subscribeTo(subId, (type,_, content) => {
|
||||
|
||||
if (type === 'EOSE') {
|
||||
setRepostList((reposts) => [...reposts, ...users]);
|
||||
loadedReposts = repostList.length;
|
||||
@ -232,44 +256,128 @@ const ReactionsModal: Component<{
|
||||
getEventReactions(props.noteId, Kind.Repost, subId, offset);
|
||||
};
|
||||
|
||||
// const getQuotes = (offset = 0) => {
|
||||
// if (!props.noteId) return;
|
||||
const getQuotes = (offset = 0) => {
|
||||
if (!props.noteId) return;
|
||||
|
||||
// const subId = `nr_q_${props.noteId}_${APP_ID}`;
|
||||
const subId = `nr_q_${props.noteId}_${APP_ID}`;
|
||||
|
||||
// const users: any[] = [];
|
||||
let page: FeedPage = {
|
||||
messages: [],
|
||||
users: {},
|
||||
postStats: {},
|
||||
mentions: {},
|
||||
noteActions: {},
|
||||
};
|
||||
|
||||
// const unsub = subscribeTo(subId, (type,_, content) => {
|
||||
// if (type === 'EOSE') {
|
||||
// setQuotesList((reposts) => [...reposts, ...users]);
|
||||
// loadedQuotes = quotesList.length;
|
||||
// setIsFetching(() => false);
|
||||
// unsub();
|
||||
// }
|
||||
const unsub = subscribeTo(subId, (type,_, content) => {
|
||||
if (type === 'EOSE') {
|
||||
const pageNotes = convertToNotes(page);
|
||||
|
||||
// if (type === 'EVENT') {
|
||||
// if (content?.kind === Kind.Metadata) {
|
||||
// let user = JSON.parse(content.content);
|
||||
setQuotesList((notes) => [...notes, ...pageNotes]);
|
||||
loadedQuotes = quotesList.length;
|
||||
setIsFetching(() => false);
|
||||
unsub();
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (type === 'EVENT') {
|
||||
if (content?.kind === Kind.Metadata) {
|
||||
const user = content as NostrUserContent;
|
||||
|
||||
// users.push(user);
|
||||
page.users[user.pubkey] = { ...user };
|
||||
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
return;
|
||||
}
|
||||
if (content?.kind === Kind.Text) {
|
||||
const message = content as NostrNoteContent;
|
||||
|
||||
// setIsFetching(() => true);
|
||||
// getEventQuotes(props.noteId, subId, offset);
|
||||
// };
|
||||
if (page.messages.find(m => m.id === message.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const totalCount = () => props.stats.likes + props.stats.quotes + props.stats.reposts + props.stats.zaps;
|
||||
page.messages.push(message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (content?.kind === Kind.NoteStats) {
|
||||
const statistic = content as NostrStatsContent;
|
||||
const stat = JSON.parse(statistic.content);
|
||||
|
||||
page.postStats[stat.event_id] = { ...stat };
|
||||
return;
|
||||
}
|
||||
|
||||
if (content?.kind === Kind.Mentions) {
|
||||
const mentionContent = content as NostrMentionContent;
|
||||
const mention = JSON.parse(mentionContent.content);
|
||||
|
||||
if (!page.mentions) {
|
||||
page.mentions = {};
|
||||
}
|
||||
|
||||
page.mentions[mention.id] = { ...mention };
|
||||
return;
|
||||
}
|
||||
|
||||
if (content?.kind === Kind.NoteActions) {
|
||||
const noteActionContent = content as NostrNoteActionsContent;
|
||||
const noteActions = JSON.parse(noteActionContent.content) as NoteActions;
|
||||
|
||||
page.noteActions[noteActions.event_id] = { ...noteActions };
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (content?.kind === Kind.LinkMetadata) {
|
||||
const metadata = JSON.parse(content.content);
|
||||
|
||||
const data = metadata.resources[0];
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const preview = {
|
||||
url: data.url,
|
||||
title: data.md_title,
|
||||
description: data.md_description,
|
||||
mediaType: data.mimetype,
|
||||
contentType: data.mimetype,
|
||||
images: [data.md_image],
|
||||
favicons: [data.icon_url],
|
||||
};
|
||||
|
||||
setLinkPreviews(() => ({ [data.url]: preview }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setIsFetching(() => true);
|
||||
getEventQuotes(props.noteId, subId, offset);
|
||||
};
|
||||
|
||||
const getQuoteCount = () => {
|
||||
if (!props.noteId) return;
|
||||
|
||||
const subId = `nr_qc_${props.noteId}_${APP_ID}`;
|
||||
|
||||
const unsub = subscribeTo(subId, (type,_, content) => {
|
||||
if (type === 'EOSE') {
|
||||
unsub();
|
||||
}
|
||||
|
||||
if (type === 'EVENT') {
|
||||
if (content?.kind === Kind.NoteQuoteStats) {
|
||||
const quoteStats = JSON.parse(content.content);
|
||||
|
||||
setQuoteCount(() => quoteStats.count || 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
getEventQuoteStats(props.noteId, subId);
|
||||
}
|
||||
|
||||
const totalCount = () => props.stats.likes + (quoteCount() || props.stats.quotes || 0) + props.stats.reposts + props.stats.zaps;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@ -287,6 +395,12 @@ const ReactionsModal: Component<{
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Switch>
|
||||
<Match when={!isFetching && totalCount() === 0}>
|
||||
{intl.formatMessage(tPlaceholders.noReactionDetails)}
|
||||
</Match>
|
||||
</Switch>
|
||||
|
||||
<div class={styles.description}>
|
||||
<Tabs.Root value={selectedTab()} onChange={setSelectedTab}>
|
||||
<Tabs.List class={styles.tabs}>
|
||||
@ -305,9 +419,9 @@ const ReactionsModal: Component<{
|
||||
{intl.formatMessage(reactionsModal.tabs.reposts, { count: props.stats.reposts })}
|
||||
</Tabs.Trigger>
|
||||
</Show>
|
||||
<Show when={props.stats.quotes > 0}>
|
||||
<Show when={quoteCount() > 0}>
|
||||
<Tabs.Trigger class={styles.tab} value={'quotes'} >
|
||||
{intl.formatMessage(reactionsModal.tabs.quotes, { count: props.stats.quotes })}
|
||||
{intl.formatMessage(reactionsModal.tabs.quotes, { count: quoteCount() })}
|
||||
</Tabs.Trigger>
|
||||
</Show>
|
||||
|
||||
@ -319,7 +433,12 @@ const ReactionsModal: Component<{
|
||||
each={likeList}
|
||||
fallback={
|
||||
<Show when={!isFetching()}>
|
||||
{intl.formatMessage(tPlaceholders.noLikeDetails)}
|
||||
<Show
|
||||
when={totalCount() > 0}
|
||||
fallback={intl.formatMessage(tPlaceholders.noReactionDetails)}
|
||||
>
|
||||
{intl.formatMessage(tPlaceholders.noLikeDetails)}
|
||||
</Show>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
@ -364,7 +483,12 @@ const ReactionsModal: Component<{
|
||||
each={zapList}
|
||||
fallback={
|
||||
<Show when={!isFetching()}>
|
||||
{intl.formatMessage(tPlaceholders.noZapDetails)}
|
||||
<Show
|
||||
when={totalCount() > 0}
|
||||
fallback={intl.formatMessage(tPlaceholders.noReactionDetails)}
|
||||
>
|
||||
{intl.formatMessage(tPlaceholders.noZapDetails)}
|
||||
</Show>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
@ -416,7 +540,12 @@ const ReactionsModal: Component<{
|
||||
each={repostList}
|
||||
fallback={
|
||||
<Show when={!isFetching()}>
|
||||
{intl.formatMessage(tPlaceholders.noRepostDetails)}
|
||||
<Show
|
||||
when={totalCount() > 0}
|
||||
fallback={intl.formatMessage(tPlaceholders.noReactionDetails)}
|
||||
>
|
||||
{intl.formatMessage(tPlaceholders.noRepostDetails)}
|
||||
</Show>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
@ -456,7 +585,36 @@ const ReactionsModal: Component<{
|
||||
</Show>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content class={styles.tabContent} value={'quotes'}>
|
||||
All the quotes
|
||||
<For
|
||||
each={quotesList}
|
||||
fallback={
|
||||
<Show when={!isFetching()}>
|
||||
<Show
|
||||
when={totalCount() > 0}
|
||||
fallback={intl.formatMessage(tPlaceholders.noReactionDetails)}
|
||||
>
|
||||
{intl.formatMessage(tPlaceholders.noQuoteDetails)}
|
||||
</Show>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
{quote => (
|
||||
<Note
|
||||
note={quote}
|
||||
shorten={true}
|
||||
noteType="reaction"
|
||||
onClick={props.onClose}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
<Paginator
|
||||
loadNextPage={() => {
|
||||
const len = quotesList.length;
|
||||
if (len === 0) return;
|
||||
getQuotes(len);
|
||||
}}
|
||||
isSmall={true}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
</div>
|
||||
|
@ -140,6 +140,7 @@ export enum Kind {
|
||||
UploadChunk = 10_000_135,
|
||||
UserRelays=10_000_139,
|
||||
RelayHint=10_000_141,
|
||||
NoteQuoteStats=10_000_143,
|
||||
|
||||
WALLET_OPERATION = 10_000_300,
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ import {
|
||||
} from "../types/primal";
|
||||
import { APP_ID } from "../App";
|
||||
import { useAccountContext } from "./AccountContext";
|
||||
import { getEventZaps, setLinkPreviews } from "../lib/notes";
|
||||
import { getEventQuoteStats, getEventZaps, setLinkPreviews } from "../lib/notes";
|
||||
import { parseBolt11 } from "../utils";
|
||||
import { getUserProfiles } from "../lib/profile";
|
||||
|
||||
@ -60,6 +60,7 @@ export type ThreadContextStore = {
|
||||
reposts: Record<string, string> | undefined,
|
||||
lastNote: PrimalNote | undefined,
|
||||
topZaps: Record<string, TopZap[]>,
|
||||
quoteCount: number,
|
||||
actions: {
|
||||
saveNotes: (newNotes: PrimalNote[]) => void,
|
||||
clearNotes: () => void,
|
||||
@ -92,6 +93,7 @@ export const initialData = {
|
||||
reposts: {},
|
||||
lastNote: undefined,
|
||||
topZaps: {},
|
||||
quoteCount: 0,
|
||||
};
|
||||
|
||||
|
||||
@ -116,6 +118,7 @@ export const ThreadProvider = (props: { children: ContextChildren }) => {
|
||||
updateStore('noteId', noteId)
|
||||
getThread(account?.publicKey, noteId, `thread_${APP_ID}`);
|
||||
fetchTopZaps(noteId);
|
||||
fetchNoteQuoteStats(noteId);
|
||||
updateStore('isFetching', () => true);
|
||||
}
|
||||
|
||||
@ -276,6 +279,12 @@ export const ThreadProvider = (props: { children: ContextChildren }) => {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (content.kind === Kind.NoteQuoteStats) {
|
||||
const quoteStats = JSON.parse(content.content);
|
||||
|
||||
updateStore('quoteCount', () => quoteStats.count || 0);
|
||||
}
|
||||
};
|
||||
|
||||
const savePage = (page: FeedPage) => {
|
||||
@ -298,6 +307,10 @@ export const ThreadProvider = (props: { children: ContextChildren }) => {
|
||||
getUserProfiles(pubkeys, `thread_pk_${APP_ID}`);
|
||||
};
|
||||
|
||||
const fetchNoteQuoteStats = (noteId: string) => {
|
||||
getEventQuoteStats(noteId, `thread_quote_stats_${APP_ID}`)
|
||||
}
|
||||
|
||||
// SOCKET HANDLERS ------------------------------
|
||||
|
||||
const onMessage = (event: MessageEvent) => {
|
||||
@ -368,6 +381,17 @@ export const ThreadProvider = (props: { children: ContextChildren }) => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (subId === `thread_quote_stats_${APP_ID}`) {
|
||||
if (type === 'EOSE') {
|
||||
savePage(store.page);
|
||||
}
|
||||
|
||||
if (type === 'EVENT') {
|
||||
updatePage(content);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onSocketClose = (closeEvent: CloseEvent) => {
|
||||
|
@ -572,3 +572,14 @@ export const getEventZaps = (eventId: string, user_pubkey: string | undefined, s
|
||||
{cache: ["event_zaps_by_satszapped", { event_id, user_pubkey, limit, offset }]},
|
||||
]));
|
||||
};
|
||||
|
||||
|
||||
export const getEventQuoteStats = (eventId: string, subid: string) => {
|
||||
const event_id = eventId.startsWith('note1') ? npubToHex(eventId) : eventId;
|
||||
|
||||
sendMessage(JSON.stringify([
|
||||
"REQ",
|
||||
subid,
|
||||
{cache: ["note_mentions_count", { event_id }]},
|
||||
]));
|
||||
};
|
||||
|
@ -207,6 +207,7 @@ const Thread: Component = () => {
|
||||
<Note
|
||||
note={primaryNote() as PrimalNote}
|
||||
noteType="primary"
|
||||
quoteCount={threadContext?.quoteCount}
|
||||
/>
|
||||
<Show when={account?.hasPublicKey()}>
|
||||
<ReplyToNote
|
||||
|
@ -308,6 +308,8 @@ export const convertToNotes: ConvertToNotes = (page) => {
|
||||
mentionedNotes,
|
||||
mentionedUsers,
|
||||
replyTo: replyTo && replyTo[1],
|
||||
tags: msg.tags,
|
||||
id: msg.id,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -1014,6 +1014,11 @@ export const placeholders = {
|
||||
description: 'Placeholder when the note is missing',
|
||||
},
|
||||
},
|
||||
noReactionDetails: {
|
||||
id: 'placeholders.noReactionDetails',
|
||||
defaultMessage: 'No details for rections found',
|
||||
description: 'Placeholder when there are no reaction details in reactions modal',
|
||||
},
|
||||
noLikeDetails: {
|
||||
id: 'placeholders.noLikeDetails',
|
||||
defaultMessage: 'No details for likes found',
|
||||
@ -1029,6 +1034,11 @@ export const placeholders = {
|
||||
defaultMessage: 'No details for reposts found',
|
||||
description: 'Placeholder when there are no repost details in reactions modal',
|
||||
},
|
||||
noQuoteDetails: {
|
||||
id: 'placeholders.noQuoteDetails',
|
||||
defaultMessage: 'No details for quotes found',
|
||||
description: 'Placeholder when there are no quote details in reactions modal',
|
||||
},
|
||||
addComment: {
|
||||
id: 'placeholders.addComment',
|
||||
defaultMessage: 'Add a comment...',
|
||||
|
12
src/types/primal.d.ts
vendored
12
src/types/primal.d.ts
vendored
@ -221,6 +221,13 @@ export type NostrZapInfo = {
|
||||
tags?: string[][],
|
||||
};
|
||||
|
||||
export type NostrQuoteStatsInfo = {
|
||||
kind: Kind.NoteQuoteStats,
|
||||
content: string,
|
||||
created_at?: number,
|
||||
tags?: string[][],
|
||||
};
|
||||
|
||||
export type NostrEventContent =
|
||||
NostrNoteContent |
|
||||
NostrUserContent |
|
||||
@ -251,7 +258,8 @@ export type NostrEventContent =
|
||||
PrimalUserRelays |
|
||||
NostrBookmarks |
|
||||
NostrRelayHint |
|
||||
NostrZapInfo;
|
||||
NostrZapInfo |
|
||||
NostrQuoteStatsInfo;
|
||||
|
||||
export type NostrEvent = [
|
||||
type: "EVENT",
|
||||
@ -465,6 +473,8 @@ export type PrimalNote = {
|
||||
mentionedNotes?: Record<string, PrimalNote>,
|
||||
mentionedUsers?: Record<string, PrimalUser>,
|
||||
replyTo?: string,
|
||||
id: string,
|
||||
tags: string[][],
|
||||
};
|
||||
|
||||
export type PrimalFeed = {
|
||||
|
Loading…
Reference in New Issue
Block a user