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