Fix footer and context menu in article preview

This commit is contained in:
Bojan Mojsilovic 2024-05-30 14:34:03 +02:00
parent e28299e9f0
commit b606e90532
12 changed files with 90 additions and 29 deletions

View File

@ -1,4 +1,5 @@
.article { .article {
position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
text-decoration: none; text-decoration: none;
@ -115,3 +116,9 @@
} }
} }
.upRightFloater {
position: absolute;
top: -6px;
right: 8px;
}

View File

@ -12,6 +12,7 @@ import { PrimalArticle, ZapOption } from '../../types/primal';
import { uuidv4 } from '../../utils'; import { uuidv4 } from '../../utils';
import Avatar from '../Avatar/Avatar'; import Avatar from '../Avatar/Avatar';
import { NoteReactionsState } from '../Note/Note'; import { NoteReactionsState } from '../Note/Note';
import NoteContextTrigger from '../Note/NoteContextTrigger';
import ArticleFooter from '../Note/NoteFooter/ArticleFooter'; import ArticleFooter from '../Note/NoteFooter/ArticleFooter';
import NoteFooter from '../Note/NoteFooter/NoteFooter'; import NoteFooter from '../Note/NoteFooter/NoteFooter';
import NoteTopZaps from '../Note/NoteTopZaps'; import NoteTopZaps from '../Note/NoteTopZaps';
@ -53,6 +54,7 @@ const ArticlePreview: Component<{
let latestTopZap: string = ''; let latestTopZap: string = '';
let latestTopZapFeed: string = ''; let latestTopZapFeed: string = '';
let articleContextMenu: HTMLDivElement | undefined;
const onConfirmZap = (zapOption: ZapOption) => { const onConfirmZap = (zapOption: ZapOption) => {
app?.actions.closeCustomZapModal(); app?.actions.closeCustomZapModal();
@ -181,19 +183,48 @@ const ArticlePreview: Component<{
onCancel: onCancelZap, onCancel: onCancelZap,
}); });
const openReactionModal = (openOn = 'likes') => {
app?.actions.openReactionModal(props.article.id, {
likes: reactionsState.likes,
zaps: reactionsState.zapCount,
reposts: reactionsState.reposts,
quotes: reactionsState.quoteCount,
openOn,
});
};
const onContextMenuTrigger = () => {
app?.actions.openContextMenu(
props.article,
articleContextMenu?.getBoundingClientRect(),
() => {
app?.actions.openCustomZapModal(customZapInfo());
},
openReactionModal,
);
}
return ( return (
<A class={styles.article} href={`/e/${props.article.naddr}`}> <A class={styles.article} href={`/e/${props.article.naddr}`}>
<div class={styles.upRightFloater}>
<NoteContextTrigger
ref={articleContextMenu}
onClick={onContextMenuTrigger}
/>
</div>
<div class={styles.header}> <div class={styles.header}>
<div class={styles.userInfo}> <div class={styles.userInfo}>
<Avatar user={props.article.author} size="micro"/> <Avatar user={props.article.user} size="micro"/>
<div class={styles.userName}>{userName(props.article.author)}</div> <div class={styles.userName}>{userName(props.article.user)}</div>
<VerificationCheck user={props.article.author} /> <VerificationCheck user={props.article.user} />
<div class={styles.nip05}>{props.article.author.nip05 || ''}</div> <div class={styles.nip05}>{props.article.user.nip05 || ''}</div>
</div> </div>
<div class={styles.time}> <div class={styles.time}>
{shortDate(props.article.published)} {shortDate(props.article.published)}
</div> </div>
</div> </div>
<div class={styles.body}> <div class={styles.body}>
<div class={styles.text}> <div class={styles.text}>
<div class={styles.content}> <div class={styles.content}>

View File

@ -60,12 +60,12 @@ const NoteContextMenu: Component<{
const doMuteUser = () => { const doMuteUser = () => {
account?.actions.addToMuteList(note()?.post.pubkey); account?.actions.addToMuteList(note()?.pubkey);
props.onClose(); props.onClose();
}; };
const doUnmuteUser = () => { const doUnmuteUser = () => {
account?.actions.removeFromMuteList(note()?.post.pubkey); account?.actions.removeFromMuteList(note()?.pubkey);
props.onClose(); props.onClose();
}; };
@ -77,21 +77,21 @@ const NoteContextMenu: Component<{
const copyNoteLink = () => { const copyNoteLink = () => {
if (!props.data) return; if (!props.data) return;
navigator.clipboard.writeText(`${window.location.origin}/e/${note().post.noteId}`); navigator.clipboard.writeText(`${window.location.origin}/e/${note().noteId}`);
props.onClose() props.onClose()
toaster?.sendSuccess(intl.formatMessage(tToast.notePrimalLinkCoppied)); toaster?.sendSuccess(intl.formatMessage(tToast.notePrimalLinkCoppied));
}; };
const copyNoteText = () => { const copyNoteText = () => {
if (!props.data) return; if (!props.data) return;
navigator.clipboard.writeText(`${note().post.content}`); navigator.clipboard.writeText(`${note().content}`);
props.onClose() props.onClose()
toaster?.sendSuccess(intl.formatMessage(tToast.notePrimalTextCoppied)); toaster?.sendSuccess(intl.formatMessage(tToast.notePrimalTextCoppied));
}; };
const copyNoteId = () => { const copyNoteId = () => {
if (!props.data) return; if (!props.data) return;
navigator.clipboard.writeText(`${note().post.noteId}`); navigator.clipboard.writeText(`${note().noteId}`);
props.onClose() props.onClose()
toaster?.sendSuccess(intl.formatMessage(tToast.noteIdCoppied)); toaster?.sendSuccess(intl.formatMessage(tToast.noteIdCoppied));
}; };
@ -128,7 +128,7 @@ const NoteContextMenu: Component<{
const onClickOutside = (e: MouseEvent) => { const onClickOutside = (e: MouseEvent) => {
if ( if (
!props.data || !props.data ||
!document?.getElementById(`note_context_${note().post.id}`)?.contains(e.target as Node) !document?.getElementById(`note_context_${note().id}`)?.contains(e.target as Node)
) { ) {
props.onClose() props.onClose()
} }
@ -222,7 +222,7 @@ const NoteContextMenu: Component<{
]; ];
}; };
const noteContext = () => account?.publicKey !== note()?.post.pubkey ? const noteContext = () => account?.publicKey !== note()?.pubkey ?
[ ...noteContextForEveryone, ...noteContextForOtherPeople()] : [ ...noteContextForEveryone, ...noteContextForOtherPeople()] :
noteContextForEveryone; noteContextForEveryone;
@ -251,7 +251,7 @@ const NoteContextMenu: Component<{
/> />
<PrimalMenu <PrimalMenu
id={`note_context_${note()?.post.id}`} id={`note_context_${note()?.id}`}
items={noteContext()} items={noteContext()}
hidden={!props.open} hidden={!props.open}
position="note_footer" position="note_footer"

View File

@ -181,7 +181,7 @@ const ArticleFooter: Component<{
return; return;
} }
if (!canUserReceiveZaps(props.note.author)) { if (!canUserReceiveZaps(props.note.user)) {
toast?.sendWarning( toast?.sendWarning(
intl.formatMessage(t.zapUnavailable), intl.formatMessage(t.zapUnavailable),
); );
@ -206,7 +206,7 @@ const ArticleFooter: Component<{
return; return;
} }
if (account.relays.length === 0 || !canUserReceiveZaps(props.note.author)) { if (account.relays.length === 0 || !canUserReceiveZaps(props.note.user)) {
return; return;
} }

View File

@ -29,7 +29,7 @@ export type CustomZapInfo = {
}; };
export type NoteContextMenuInfo = { export type NoteContextMenuInfo = {
note: PrimalNote, note: PrimalNote | PrimalArticle,
position: DOMRect | undefined, position: DOMRect | undefined,
openCustomZap?: () => void, openCustomZap?: () => void,
openReactions?: () => void, openReactions?: () => void,
@ -72,7 +72,7 @@ export type AppContextStore = {
openCustomZapModal: (custonZapInfo: CustomZapInfo) => void, openCustomZapModal: (custonZapInfo: CustomZapInfo) => void,
closeCustomZapModal: () => void, closeCustomZapModal: () => void,
resetCustomZap: () => void, resetCustomZap: () => void,
openContextMenu: (note: PrimalNote, position: DOMRect | undefined, openCustomZapModal: () => void, openReactionModal: () => void) => void, openContextMenu: (note: PrimalNote | PrimalArticle, position: DOMRect | undefined, openCustomZapModal: () => void, openReactionModal: () => void) => void,
closeContextMenu: () => void, closeContextMenu: () => void,
openLnbcModal: (lnbc: string, onPay: () => void) => void, openLnbcModal: (lnbc: string, onPay: () => void) => void,
closeLnbcModal: () => void, closeLnbcModal: () => void,
@ -155,7 +155,7 @@ export const AppProvider = (props: { children: JSXElement }) => {
}; };
const openContextMenu = ( const openContextMenu = (
note: PrimalNote, note: PrimalNote | PrimalArticle,
position: DOMRect | undefined, position: DOMRect | undefined,
openCustomZap: () => void, openCustomZap: () => void,
openReactions: () => void, openReactions: () => void,

View File

@ -2,7 +2,7 @@ import { nip19 } from "nostr-tools";
import { createContext, createEffect, onCleanup, useContext } from "solid-js"; import { createContext, createEffect, onCleanup, useContext } from "solid-js";
import { createStore, reconcile, unwrap } from "solid-js/store"; import { createStore, reconcile, unwrap } from "solid-js/store";
import { APP_ID } from "../App"; import { APP_ID } from "../App";
import { Kind } from "../constants"; import { Kind, minKnownProfiles } from "../constants";
import { getArticlesFeed, getEvents, getExploreFeed, getFeed, getFutureArticlesFeed, getFutureExploreFeed, getFutureFeed } from "../lib/feed"; import { getArticlesFeed, getEvents, getExploreFeed, getFeed, getFutureArticlesFeed, getFutureExploreFeed, getFutureFeed } from "../lib/feed";
import { fetchStoredFeed, saveStoredFeed } from "../lib/localStore"; import { fetchStoredFeed, saveStoredFeed } from "../lib/localStore";
import { setLinkPreviews } from "../lib/notes"; import { setLinkPreviews } from "../lib/notes";
@ -307,7 +307,8 @@ export const ReadsProvider = (props: { children: ContextChildren }) => {
}; };
const fetchNotes = (topic: string, subId: string, until = 0, includeReplies?: boolean) => { const fetchNotes = (topic: string, subId: string, until = 0, includeReplies?: boolean) => {
const [scope, timeframe] = topic.split(';'); const t = account?.publicKey || '532d830dffe09c13e75e8b145c825718fc12b0003f61d61e9077721c7fff93cb';
const [scope, timeframe] = t.split(';');
updateStore('isFetching', true); updateStore('isFetching', true);
updateStore('page', () => ({ messages: [], users: {}, postStats: {} })); updateStore('page', () => ({ messages: [], users: {}, postStats: {} }));
@ -329,7 +330,7 @@ export const ReadsProvider = (props: { children: ContextChildren }) => {
return; return;
} }
getArticlesFeed(account?.publicKey, topic, `reads_feed_${subId}`, until, 20); getArticlesFeed(account?.publicKey, t, `reads_feed_${subId}`, until, 20);
}; };
const clearNotes = () => { const clearNotes = () => {

View File

@ -32,6 +32,7 @@ export const getFeed = (user_pubkey: string | undefined, pubkey: string | undef
let payload = { limit, until: time, pubkey }; let payload = { limit, until: time, pubkey };
if (user_pubkey) { if (user_pubkey) {
// @ts-ignore dynamic property
payload.user_pubkey = user_pubkey; payload.user_pubkey = user_pubkey;
} }
if (include_replies) { if (include_replies) {

View File

@ -19,8 +19,8 @@ export const zapNote = async (note: PrimalNote, sender: string | undefined, amou
const sats = Math.round(amount * 1000); const sats = Math.round(amount * 1000);
let payload = { let payload = {
profile: note.post.pubkey, profile: note.pubkey,
event: note.msg.id, event: note.id,
amount: sats, amount: sats,
relays: relays.map(r => r.url) relays: relays.map(r => r.url)
}; };
@ -55,7 +55,7 @@ export const zapArticle = async (note: PrimalArticle, sender: string | undefined
return false; return false;
} }
const callback = await getZapEndpoint(note.author); const callback = await getZapEndpoint(note.user);
if (!callback) { if (!callback) {
return false; return false;

View File

@ -83,7 +83,14 @@ const Notifications: Component = () => {
const [relatedNotes, setRelatedNotes] = createStore<NotificationStore>({ const [relatedNotes, setRelatedNotes] = createStore<NotificationStore>({
notes: [], notes: [],
users: [], users: [],
page: { messages: [], users: {}, postStats: {}, mentions: {}, noteActions: {} }, page: {
messages: [],
users: {},
postStats: {},
mentions: {},
noteActions: {},
topZaps: {},
},
reposts: {}, reposts: {},
}) })
@ -91,7 +98,15 @@ const Notifications: Component = () => {
notes: [], notes: [],
users: {}, users: {},
userStats: {}, userStats: {},
page: { messages: [], users: {}, postStats: {}, notifications: [], mentions: {}, noteActions: {} }, page: {
messages: [],
users: {},
postStats: {},
notifications: [],
mentions: {},
noteActions: {},
topZaps: {},
},
reposts: {}, reposts: {},
notifications: [], notifications: [],
}) })

View File

@ -94,7 +94,7 @@ const Home: Component = () => {
} }
if (newPostAuthors.length < 3) { if (newPostAuthors.length < 3) {
const users = context?.future.notes.map(note => note.author) || []; const users = context?.future.notes.map(note => note.user) || [];
const uniqueUsers = users.reduce<PrimalUser[]>((acc, user) => { const uniqueUsers = users.reduce<PrimalUser[]>((acc, user) => {
const isDuplicate = acc.find(u => u && u.pubkey === user.pubkey); const isDuplicate = acc.find(u => u && u.pubkey === user.pubkey);

View File

@ -366,8 +366,10 @@ export const convertToNotes: ConvertToNotes = (page, topZaps) => {
replyTo: replyTo && replyTo[1], replyTo: replyTo && replyTo[1],
tags: msg.tags, tags: msg.tags,
id: msg.id, id: msg.id,
noteId: nip19.noteEncode(msg.id),
pubkey: msg.pubkey, pubkey: msg.pubkey,
topZaps: [ ...tz ], topZaps: [ ...tz ],
content: sanitize(msg.content),
}; };
}); });
} }
@ -466,10 +468,11 @@ export const convertToArticles: ConvertToArticles = (page, topZaps) => {
image: '', image: '',
tags: [], tags: [],
published: msg.created_at || 0, published: msg.created_at || 0,
content: msg.content, content: sanitize(msg.content),
author: convertToUser(user), user: convertToUser(user),
topZaps: [...tz], topZaps: [...tz],
naddr: nip19.naddrEncode({ identifier, pubkey, kind }), naddr: nip19.naddrEncode({ identifier, pubkey, kind }),
noteId: nip19.naddrEncode({ identifier, pubkey, kind }),
msg, msg,
mentionedNotes, mentionedNotes,
mentionedUsers, mentionedUsers,

View File

@ -498,8 +498,10 @@ export type PrimalNote = {
replyTo?: string, replyTo?: string,
id: string, id: string,
pubkey: string, pubkey: string,
noteId: string,
tags: string[][], tags: string[][],
topZaps: TopZap[], topZaps: TopZap[],
content: string,
}; };
@ -510,7 +512,7 @@ export type PrimalArticle = {
tags: string[], tags: string[],
published: number, published: number,
content: string, content: string,
author: PrimalUser, user: PrimalUser,
topZaps: TopZap[], topZaps: TopZap[],
repost?: PrimalRepost, repost?: PrimalRepost,
mentionedNotes?: Record<string, PrimalNote>, mentionedNotes?: Record<string, PrimalNote>,
@ -519,6 +521,7 @@ export type PrimalArticle = {
id: string, id: string,
pubkey: string, pubkey: string,
naddr: string, naddr: string,
noteId: string,
msg: NostrNoteContent, msg: NostrNoteContent,
wordCount: number, wordCount: number,
noteActions: NoteActions, noteActions: NoteActions,