mirror of
https://github.com/PrimalHQ/primal-web-app.git
synced 2024-09-30 00:41:09 +00:00
Enable reactions
This commit is contained in:
parent
cca4d11df0
commit
3914a3c0c9
@ -1,11 +1,15 @@
|
||||
import { A } from '@solidjs/router';
|
||||
import { Component, createEffect, For, JSXElement, Show } from 'solid-js';
|
||||
import { batch, Component, createEffect, For, JSXElement, Show } from 'solid-js';
|
||||
import { createStore } from 'solid-js/store';
|
||||
import { Portal } from 'solid-js/web';
|
||||
import { useAccountContext } from '../../contexts/AccountContext';
|
||||
import { CustomZapInfo, useAppContext } from '../../contexts/AppContext';
|
||||
import { useThreadContext } from '../../contexts/ThreadContext';
|
||||
import { shortDate } from '../../lib/dates';
|
||||
import { hookForDev } from '../../lib/devTools';
|
||||
import { userName } from '../../stores/profile';
|
||||
import { PrimalArticle } from '../../types/primal';
|
||||
import { PrimalArticle, ZapOption } from '../../types/primal';
|
||||
import { uuidv4 } from '../../utils';
|
||||
import Avatar from '../Avatar/Avatar';
|
||||
import { NoteReactionsState } from '../Note/Note';
|
||||
import ArticleFooter from '../Note/NoteFooter/ArticleFooter';
|
||||
@ -21,17 +25,20 @@ const ArticlePreview: Component<{
|
||||
article: PrimalArticle,
|
||||
}> = (props) => {
|
||||
|
||||
const app = useAppContext();
|
||||
const account = useAccountContext();
|
||||
const thread = useThreadContext();
|
||||
|
||||
const [reactionsState, updateReactionsState] = createStore<NoteReactionsState>({
|
||||
likes: 0,
|
||||
liked: false,
|
||||
reposts: 0,
|
||||
reposted: false,
|
||||
replies: 0,
|
||||
replied: false,
|
||||
zapCount: 0,
|
||||
satsZapped: 0,
|
||||
zapped: false,
|
||||
likes: props.article.likes,
|
||||
liked: props.article.noteActions.liked,
|
||||
reposts: props.article.reposts,
|
||||
reposted: props.article.noteActions.reposted,
|
||||
replies: props.article.replies,
|
||||
replied: props.article.noteActions.replied,
|
||||
zapCount: props.article.zaps,
|
||||
satsZapped: props.article.satszapped,
|
||||
zapped: props.article.noteActions.zapped,
|
||||
zappedAmount: 0,
|
||||
zappedNow: false,
|
||||
isZapping: false,
|
||||
@ -44,6 +51,136 @@ const ArticlePreview: Component<{
|
||||
quoteCount: 0,
|
||||
});
|
||||
|
||||
let latestTopZap: string = '';
|
||||
let latestTopZapFeed: string = '';
|
||||
|
||||
const onConfirmZap = (zapOption: ZapOption) => {
|
||||
app?.actions.closeCustomZapModal();
|
||||
batch(() => {
|
||||
updateReactionsState('zappedAmount', () => zapOption.amount || 0);
|
||||
updateReactionsState('satsZapped', (z) => z + (zapOption.amount || 0));
|
||||
updateReactionsState('zapped', () => true);
|
||||
updateReactionsState('showZapAnim', () => true)
|
||||
});
|
||||
|
||||
addTopZap(zapOption);
|
||||
addTopZapFeed(zapOption)
|
||||
};
|
||||
|
||||
const onSuccessZap = (zapOption: ZapOption) => {
|
||||
app?.actions.closeCustomZapModal();
|
||||
app?.actions.resetCustomZap();
|
||||
|
||||
const pubkey = account?.publicKey;
|
||||
|
||||
if (!pubkey) return;
|
||||
|
||||
batch(() => {
|
||||
updateReactionsState('zapCount', (z) => z + 1);
|
||||
updateReactionsState('isZapping', () => false);
|
||||
updateReactionsState('showZapAnim', () => false);
|
||||
updateReactionsState('hideZapIcon', () => false);
|
||||
updateReactionsState('zapped', () => true);
|
||||
});
|
||||
};
|
||||
|
||||
const onFailZap = (zapOption: ZapOption) => {
|
||||
app?.actions.closeCustomZapModal();
|
||||
app?.actions.resetCustomZap();
|
||||
batch(() => {
|
||||
updateReactionsState('zappedAmount', () => -(zapOption.amount || 0));
|
||||
updateReactionsState('satsZapped', (z) => z - (zapOption.amount || 0));
|
||||
updateReactionsState('isZapping', () => false);
|
||||
updateReactionsState('showZapAnim', () => false);
|
||||
updateReactionsState('hideZapIcon', () => false);
|
||||
updateReactionsState('zapped', () => props.article.noteActions.zapped);
|
||||
});
|
||||
|
||||
removeTopZap(zapOption);
|
||||
removeTopZapFeed(zapOption);
|
||||
};
|
||||
|
||||
const onCancelZap = (zapOption: ZapOption) => {
|
||||
app?.actions.closeCustomZapModal();
|
||||
app?.actions.resetCustomZap();
|
||||
batch(() => {
|
||||
updateReactionsState('zappedAmount', () => -(zapOption.amount || 0));
|
||||
updateReactionsState('satsZapped', (z) => z - (zapOption.amount || 0));
|
||||
updateReactionsState('isZapping', () => false);
|
||||
updateReactionsState('showZapAnim', () => false);
|
||||
updateReactionsState('hideZapIcon', () => false);
|
||||
updateReactionsState('zapped', () => props.article.noteActions.zapped);
|
||||
});
|
||||
|
||||
removeTopZap(zapOption);
|
||||
removeTopZapFeed(zapOption);
|
||||
};
|
||||
|
||||
const addTopZap = (zapOption: ZapOption) => {
|
||||
const pubkey = account?.publicKey;
|
||||
|
||||
if (!pubkey) return;
|
||||
|
||||
const oldZaps = [ ...reactionsState.topZaps ];
|
||||
|
||||
latestTopZap = uuidv4() as string;
|
||||
|
||||
const newZap = {
|
||||
amount: zapOption.amount || 0,
|
||||
message: zapOption.message || '',
|
||||
pubkey,
|
||||
eventId: props.article.id,
|
||||
id: latestTopZap,
|
||||
};
|
||||
|
||||
if (!thread?.users.find((u) => u.pubkey === pubkey)) {
|
||||
thread?.actions.fetchUsers([pubkey])
|
||||
}
|
||||
|
||||
const zaps = [ ...oldZaps, { ...newZap }].sort((a, b) => b.amount - a.amount);
|
||||
updateReactionsState('topZaps', () => [...zaps]);
|
||||
};
|
||||
|
||||
const removeTopZap = (zapOption: ZapOption) => {
|
||||
const zaps = reactionsState.topZaps.filter(z => z.id !== latestTopZap);
|
||||
updateReactionsState('topZaps', () => [...zaps]);
|
||||
};
|
||||
|
||||
|
||||
const addTopZapFeed = (zapOption: ZapOption) => {
|
||||
const pubkey = account?.publicKey;
|
||||
|
||||
if (!pubkey) return;
|
||||
|
||||
const oldZaps = [ ...reactionsState.topZapsFeed ];
|
||||
|
||||
latestTopZapFeed = uuidv4() as string;
|
||||
|
||||
const newZap = {
|
||||
amount: zapOption.amount || 0,
|
||||
message: zapOption.message || '',
|
||||
pubkey,
|
||||
eventId: props.article.id,
|
||||
id: latestTopZapFeed,
|
||||
};
|
||||
|
||||
const zaps = [ ...oldZaps, { ...newZap }].sort((a, b) => b.amount - a.amount).slice(0, 4);
|
||||
updateReactionsState('topZapsFeed', () => [...zaps]);
|
||||
}
|
||||
|
||||
const removeTopZapFeed = (zapOption: ZapOption) => {
|
||||
const zaps = reactionsState.topZapsFeed.filter(z => z.id !== latestTopZapFeed);
|
||||
updateReactionsState('topZapsFeed', () => [...zaps]);
|
||||
};
|
||||
|
||||
const customZapInfo: () => CustomZapInfo = () => ({
|
||||
note: props.article,
|
||||
onConfirm: onConfirmZap,
|
||||
onSuccess: onSuccessZap,
|
||||
onFail: onFailZap,
|
||||
onCancel: onCancelZap,
|
||||
});
|
||||
|
||||
return (
|
||||
<A class={styles.article} href={`/e/${props.article.naddr}`}>
|
||||
<div class={styles.header}>
|
||||
@ -101,13 +238,8 @@ const ArticlePreview: Component<{
|
||||
note={props.article}
|
||||
state={reactionsState}
|
||||
updateState={updateReactionsState}
|
||||
customZapInfo={{
|
||||
note: props.article,
|
||||
onConfirm: () => {},
|
||||
onSuccess: () => {},
|
||||
onFail: () => {},
|
||||
onCancel: () => {},
|
||||
}}
|
||||
customZapInfo={customZapInfo()}
|
||||
onZapAnim={addTopZapFeed}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -8,7 +8,7 @@ import { useToastContext } from '../../Toaster/Toaster';
|
||||
import { useIntl } from '@cookbook/solid-intl';
|
||||
|
||||
import { truncateNumber } from '../../../lib/notifications';
|
||||
import { canUserReceiveZaps, zapNote } from '../../../lib/zap';
|
||||
import { canUserReceiveZaps, zapArticle, zapNote } from '../../../lib/zap';
|
||||
import { useSettingsContext } from '../../../contexts/SettingsContext';
|
||||
|
||||
import zapMD from '../../../assets/lottie/zap_md_2.json';
|
||||
@ -135,167 +135,167 @@ const ArticleFooter: Component<{
|
||||
const doReply = () => {};
|
||||
|
||||
const doLike = async (e: MouseEvent) => {
|
||||
// e.preventDefault();
|
||||
// e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// if (!account) {
|
||||
// return;
|
||||
// }
|
||||
if (!account) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if (!account.hasPublicKey()) {
|
||||
// account.actions.showGetStarted();
|
||||
// return;
|
||||
// }
|
||||
if (!account.hasPublicKey()) {
|
||||
account.actions.showGetStarted();
|
||||
return;
|
||||
}
|
||||
|
||||
// if (account.relays.length === 0) {
|
||||
// toast?.sendWarning(
|
||||
// intl.formatMessage(t.noRelaysConnected),
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
if (account.relays.length === 0) {
|
||||
toast?.sendWarning(
|
||||
intl.formatMessage(t.noRelaysConnected),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// const success = await account.actions.addLike(props.note);
|
||||
const success = await account.actions.addLike(props.note);
|
||||
|
||||
// if (success) {
|
||||
// batch(() => {
|
||||
// props.updateState('likes', (l) => l + 1);
|
||||
// props.updateState('liked', () => true);
|
||||
// });
|
||||
// }
|
||||
if (success) {
|
||||
batch(() => {
|
||||
props.updateState('likes', (l) => l + 1);
|
||||
props.updateState('liked', () => true);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const startZap = (e: MouseEvent | TouchEvent) => {
|
||||
// e.preventDefault();
|
||||
// e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// if (!account?.hasPublicKey()) {
|
||||
// account?.actions.showGetStarted();
|
||||
// props.updateState('isZapping', () => false);
|
||||
// return;
|
||||
// }
|
||||
if (!account?.hasPublicKey()) {
|
||||
account?.actions.showGetStarted();
|
||||
props.updateState('isZapping', () => false);
|
||||
return;
|
||||
}
|
||||
|
||||
// if (account.relays.length === 0) {
|
||||
// toast?.sendWarning(
|
||||
// intl.formatMessage(t.noRelaysConnected),
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
if (account.relays.length === 0) {
|
||||
toast?.sendWarning(
|
||||
intl.formatMessage(t.noRelaysConnected),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// if (!canUserReceiveZaps(props.note.user)) {
|
||||
// toast?.sendWarning(
|
||||
// intl.formatMessage(t.zapUnavailable),
|
||||
// );
|
||||
// props.updateState('isZapping', () => false);
|
||||
// return;
|
||||
// }
|
||||
if (!canUserReceiveZaps(props.note.author)) {
|
||||
toast?.sendWarning(
|
||||
intl.formatMessage(t.zapUnavailable),
|
||||
);
|
||||
props.updateState('isZapping', () => false);
|
||||
return;
|
||||
}
|
||||
|
||||
// quickZapDelay = setTimeout(() => {
|
||||
// app?.actions.openCustomZapModal(props.customZapInfo);
|
||||
// props.updateState('isZapping', () => true);
|
||||
// }, 500);
|
||||
quickZapDelay = setTimeout(() => {
|
||||
app?.actions.openCustomZapModal(props.customZapInfo);
|
||||
props.updateState('isZapping', () => true);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const commitZap = (e: MouseEvent | TouchEvent) => {
|
||||
// e.preventDefault();
|
||||
// e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// clearTimeout(quickZapDelay);
|
||||
clearTimeout(quickZapDelay);
|
||||
|
||||
// if (!account?.hasPublicKey()) {
|
||||
// account?.actions.showGetStarted();
|
||||
// return;
|
||||
// }
|
||||
if (!account?.hasPublicKey()) {
|
||||
account?.actions.showGetStarted();
|
||||
return;
|
||||
}
|
||||
|
||||
// if (account.relays.length === 0 || !canUserReceiveZaps(props.note.user)) {
|
||||
// return;
|
||||
// }
|
||||
if (account.relays.length === 0 || !canUserReceiveZaps(props.note.author)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if (app?.customZap === undefined) {
|
||||
// doQuickZap();
|
||||
// }
|
||||
if (app?.customZap === undefined) {
|
||||
doQuickZap();
|
||||
}
|
||||
};
|
||||
|
||||
const animateZap = () => {
|
||||
// setTimeout(() => {
|
||||
// props.updateState('hideZapIcon', () => true);
|
||||
setTimeout(() => {
|
||||
props.updateState('hideZapIcon', () => true);
|
||||
|
||||
// if (!medZapAnimation) {
|
||||
// return;
|
||||
// }
|
||||
if (!medZapAnimation) {
|
||||
return;
|
||||
}
|
||||
|
||||
// let newLeft = props.wide ? 15 : 13;
|
||||
// let newTop = props.wide ? -6 : -6;
|
||||
let newLeft = props.wide ? 15 : 13;
|
||||
let newTop = props.wide ? -6 : -6;
|
||||
|
||||
// if (props.large) {
|
||||
// newLeft = 2;
|
||||
// newTop = -9;
|
||||
// }
|
||||
if (props.large) {
|
||||
newLeft = 2;
|
||||
newTop = -9;
|
||||
}
|
||||
|
||||
// medZapAnimation.style.left = `${newLeft}px`;
|
||||
// medZapAnimation.style.top = `${newTop}px`;
|
||||
medZapAnimation.style.left = `${newLeft}px`;
|
||||
medZapAnimation.style.top = `${newTop}px`;
|
||||
|
||||
// const onAnimDone = () => {
|
||||
// batch(() => {
|
||||
// props.updateState('showZapAnim', () => false);
|
||||
// props.updateState('hideZapIcon', () => false);
|
||||
// props.updateState('zapped', () => true);
|
||||
// });
|
||||
// medZapAnimation?.removeEventListener('complete', onAnimDone);
|
||||
// }
|
||||
const onAnimDone = () => {
|
||||
batch(() => {
|
||||
props.updateState('showZapAnim', () => false);
|
||||
props.updateState('hideZapIcon', () => false);
|
||||
props.updateState('zapped', () => true);
|
||||
});
|
||||
medZapAnimation?.removeEventListener('complete', onAnimDone);
|
||||
}
|
||||
|
||||
// medZapAnimation.addEventListener('complete', onAnimDone);
|
||||
medZapAnimation.addEventListener('complete', onAnimDone);
|
||||
|
||||
// try {
|
||||
// // @ts-ignore
|
||||
// medZapAnimation.seek(0);
|
||||
// // @ts-ignore
|
||||
// medZapAnimation.play();
|
||||
// } catch (e) {
|
||||
// console.warn('Failed to animte zap:', e);
|
||||
// onAnimDone();
|
||||
// }
|
||||
// }, 10);
|
||||
try {
|
||||
// @ts-ignore
|
||||
medZapAnimation.seek(0);
|
||||
// @ts-ignore
|
||||
medZapAnimation.play();
|
||||
} catch (e) {
|
||||
console.warn('Failed to animte zap:', e);
|
||||
onAnimDone();
|
||||
}
|
||||
}, 10);
|
||||
};
|
||||
|
||||
const doQuickZap = async () => {
|
||||
// if (!account?.hasPublicKey()) {
|
||||
// account?.actions.showGetStarted();
|
||||
// return;
|
||||
// }
|
||||
if (!account?.hasPublicKey()) {
|
||||
account?.actions.showGetStarted();
|
||||
return;
|
||||
}
|
||||
|
||||
// const amount = settings?.defaultZap.amount || 10;
|
||||
// const message = settings?.defaultZap.message || '';
|
||||
// const emoji = settings?.defaultZap.emoji;
|
||||
const amount = settings?.defaultZap.amount || 10;
|
||||
const message = settings?.defaultZap.message || '';
|
||||
const emoji = settings?.defaultZap.emoji;
|
||||
|
||||
// batch(() => {
|
||||
// props.updateState('isZapping', () => true);
|
||||
// props.updateState('satsZapped', (z) => z + amount);
|
||||
// props.updateState('showZapAnim', () => true);
|
||||
// });
|
||||
batch(() => {
|
||||
props.updateState('isZapping', () => true);
|
||||
props.updateState('satsZapped', (z) => z + amount);
|
||||
props.updateState('showZapAnim', () => true);
|
||||
});
|
||||
|
||||
// props.onZapAnim && props.onZapAnim({ amount, message, emoji })
|
||||
props.onZapAnim && props.onZapAnim({ amount, message, emoji })
|
||||
|
||||
// setTimeout(async () => {
|
||||
// const success = await zapNote(props.note, account.publicKey, amount, message, account.relays);
|
||||
setTimeout(async () => {
|
||||
const success = await zapArticle(props.note, account.publicKey, amount, message, account.relays);
|
||||
|
||||
// props.updateState('isZapping', () => false);
|
||||
props.updateState('isZapping', () => false);
|
||||
|
||||
// if (success) {
|
||||
// props.customZapInfo.onSuccess({
|
||||
// emoji,
|
||||
// amount,
|
||||
// message,
|
||||
// });
|
||||
if (success) {
|
||||
props.customZapInfo.onSuccess({
|
||||
emoji,
|
||||
amount,
|
||||
message,
|
||||
});
|
||||
|
||||
// return;
|
||||
// }
|
||||
return;
|
||||
}
|
||||
|
||||
// props.customZapInfo.onFail({
|
||||
// emoji,
|
||||
// amount,
|
||||
// message,
|
||||
// });
|
||||
// }, lottieDuration());
|
||||
props.customZapInfo.onFail({
|
||||
emoji,
|
||||
amount,
|
||||
message,
|
||||
});
|
||||
}, lottieDuration());
|
||||
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
PrimalNote,
|
||||
PrimalUser,
|
||||
NostrEventContent,
|
||||
PrimalArticle,
|
||||
} from '../types/primal';
|
||||
import { Kind, pinEncodePrefix, relayConnectingTimeout } from "../constants";
|
||||
import { isConnected, refreshSocketListeners, removeSocketListeners, socket, subscribeTo, reset, subTo } from "../sockets";
|
||||
@ -79,7 +80,7 @@ export type AccountContextStore = {
|
||||
showNewNoteForm: () => void,
|
||||
hideNewNoteForm: () => void,
|
||||
setActiveUser: (user: PrimalUser) => void,
|
||||
addLike: (note: PrimalNote) => Promise<boolean>,
|
||||
addLike: (note: PrimalNote | PrimalArticle) => Promise<boolean>,
|
||||
setPublicKey: (pubkey: string | undefined) => void,
|
||||
addFollow: (pubkey: string, cb?: (remove: boolean, pubkey: string) => void) => void,
|
||||
removeFollow: (pubkey: string, cb?: (remove: boolean, pubkey: string) => void) => void,
|
||||
@ -517,15 +518,15 @@ export function AccountProvider(props: { children: JSXElement }) {
|
||||
updateStore('showNewNoteForm', () => false);
|
||||
};
|
||||
|
||||
const addLike = async (note: PrimalNote) => {
|
||||
if (store.likes.includes(note.post.id)) {
|
||||
const addLike = async (note: PrimalNote | PrimalArticle) => {
|
||||
if (store.likes.includes(note.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { success } = await sendLike(note, store.relays, store.relaySettings);
|
||||
|
||||
if (success) {
|
||||
updateStore('likes', (likes) => [ ...likes, note.post.id]);
|
||||
updateStore('likes', (likes) => [ ...likes, note.id]);
|
||||
saveLikes(store.publicKey, store.likes);
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
onMount,
|
||||
useContext
|
||||
} from "solid-js";
|
||||
import { PrimalNote, PrimalUser, ZapOption } from "../types/primal";
|
||||
import { PrimalArticle, PrimalNote, PrimalUser, ZapOption } from "../types/primal";
|
||||
import { CashuMint } from "@cashu/cashu-ts";
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ export type ReactionStats = {
|
||||
|
||||
export type CustomZapInfo = {
|
||||
profile?: PrimalUser,
|
||||
note?: PrimalNote,
|
||||
note?: PrimalNote | PrimalArticle,
|
||||
onConfirm: (zapOption: ZapOption) => void,
|
||||
onSuccess: (zapOption: ZapOption) => void,
|
||||
onFail: (zapOption: ZapOption) => void,
|
||||
|
@ -489,6 +489,7 @@ export const ReadsProvider = (props: { children: ContextChildren }) => {
|
||||
if (content.kind === Kind.NoteStats) {
|
||||
const statistic = content as NostrStatsContent;
|
||||
const stat = JSON.parse(statistic.content);
|
||||
console.log('READS STATS: ', stat)
|
||||
|
||||
if (scope) {
|
||||
updateStore(scope, 'page', 'postStats',
|
||||
@ -523,6 +524,7 @@ export const ReadsProvider = (props: { children: ContextChildren }) => {
|
||||
const noteActionContent = content as NostrNoteActionsContent;
|
||||
const noteActions = JSON.parse(noteActionContent.content) as NoteActions;
|
||||
|
||||
console.log('READS ACTIONS: ', content)
|
||||
if (scope) {
|
||||
updateStore(scope, 'page', 'noteActions',
|
||||
(actions) => ({ ...actions, [noteActions.event_id]: { ...noteActions } })
|
||||
|
@ -308,13 +308,13 @@ export const importEvents = (events: NostrRelaySignedEvent[], subid: string) =>
|
||||
|
||||
type NostrEvent = { content: string, kind: number, tags: string[][], created_at: number };
|
||||
|
||||
export const sendLike = async (note: PrimalNote, relays: Relay[], relaySettings?: NostrRelays) => {
|
||||
export const sendLike = async (note: PrimalNote | PrimalArticle, relays: Relay[], relaySettings?: NostrRelays) => {
|
||||
const event = {
|
||||
content: '+',
|
||||
kind: Kind.Reaction,
|
||||
tags: [
|
||||
['e', note.post.id],
|
||||
['p', note.post.pubkey],
|
||||
['e', note.id],
|
||||
['p', note.pubkey],
|
||||
],
|
||||
created_at: Math.floor((new Date()).getTime() / 1000),
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { bech32 } from "@scure/base";
|
||||
// @ts-ignore Bad types in nostr-tools
|
||||
import { nip57, Relay, utils } from "nostr-tools";
|
||||
import { PrimalNote, PrimalUser } from "../types/primal";
|
||||
import { PrimalArticle, PrimalNote, PrimalUser } from "../types/primal";
|
||||
import { logError } from "./logger";
|
||||
import { enableWebLn, sendPayment, signEvent } from "./nostrAPI";
|
||||
|
||||
@ -50,6 +50,51 @@ export const zapNote = async (note: PrimalNote, sender: string | undefined, amou
|
||||
}
|
||||
}
|
||||
|
||||
export const zapArticle = async (note: PrimalArticle, sender: string | undefined, amount: number, comment = '', relays: Relay[]) => {
|
||||
if (!sender) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const callback = await getZapEndpoint(note.author);
|
||||
|
||||
if (!callback) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const sats = Math.round(amount * 1000);
|
||||
|
||||
let payload = {
|
||||
profile: note.pubkey,
|
||||
event: note.msg.id,
|
||||
amount: sats,
|
||||
relays: relays.map(r => r.url)
|
||||
};
|
||||
|
||||
if (comment.length > 0) {
|
||||
// @ts-ignore
|
||||
payload.comment = comment;
|
||||
}
|
||||
|
||||
const zapReq = nip57.makeZapRequest(payload);
|
||||
|
||||
try {
|
||||
const signedEvent = await signEvent(zapReq);
|
||||
|
||||
const event = encodeURIComponent(JSON.stringify(signedEvent));
|
||||
|
||||
const r2 = await (await fetch(`${callback}?amount=${sats}&nostr=${event}`)).json();
|
||||
const pr = r2.pr;
|
||||
|
||||
await enableWebLn();
|
||||
await sendPayment(pr);
|
||||
|
||||
return true;
|
||||
} catch (reason) {
|
||||
console.error('Failed to zap: ', reason);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const zapProfile = async (profile: PrimalUser, sender: string | undefined, amount: number, comment = '', relays: Relay[]) => {
|
||||
if (!sender || !profile) {
|
||||
return false;
|
||||
|
@ -27,13 +27,14 @@ import { PrimalUser } from '../types/primal';
|
||||
import Avatar from '../components/Avatar/Avatar';
|
||||
import { userName } from '../stores/profile';
|
||||
import { useAccountContext } from '../contexts/AccountContext';
|
||||
import { feedNewPosts, placeholders, branding } from '../translations';
|
||||
import { reads, branding } from '../translations';
|
||||
import Search from '../components/Search/Search';
|
||||
import { setIsHome } from '../components/Layout/Layout';
|
||||
import PageTitle from '../components/PageTitle/PageTitle';
|
||||
import { useAppContext } from '../contexts/AppContext';
|
||||
import { useReadsContext } from '../contexts/ReadsContext';
|
||||
import ArticlePreview from '../components/ArticlePreview/ArticlePreview';
|
||||
import PageCaption from '../components/PageCaption/PageCaption';
|
||||
|
||||
|
||||
const Home: Component = () => {
|
||||
@ -133,18 +134,7 @@ const Home: Component = () => {
|
||||
<Search />
|
||||
</Wormhole>
|
||||
|
||||
<div class={styles.normalCentralHeader}>
|
||||
<HomeHeader
|
||||
hasNewPosts={hasNewPosts}
|
||||
loadNewContent={loadNewContent}
|
||||
newPostCount={newPostCount}
|
||||
newPostAuthors={newPostAuthors}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class={styles.phoneCentralHeader}>
|
||||
<HomeHeaderPhone />
|
||||
</div>
|
||||
<PageCaption title={intl.formatMessage(reads.pageTitle)} />
|
||||
|
||||
<StickySidebar>
|
||||
<HomeSidebar />
|
||||
|
@ -366,6 +366,7 @@ export const convertToNotes: ConvertToNotes = (page, topZaps) => {
|
||||
replyTo: replyTo && replyTo[1],
|
||||
tags: msg.tags,
|
||||
id: msg.id,
|
||||
pubkey: msg.pubkey,
|
||||
topZaps: [ ...tz ],
|
||||
};
|
||||
});
|
||||
@ -391,6 +392,7 @@ export const convertToArticles: ConvertToArticles = (page, topZaps) => {
|
||||
const kind = msg.kind;
|
||||
|
||||
const user = page?.users[msg.pubkey];
|
||||
const stat = page?.postStats[msg.id];
|
||||
|
||||
const mentionIds = Object.keys(mentions)
|
||||
let userMentionIds = msg.tags?.reduce((acc, t) => t[0] === 'p' ? [...acc, t[1]] : acc, []);
|
||||
@ -447,8 +449,18 @@ export const convertToArticles: ConvertToArticles = (page, topZaps) => {
|
||||
|
||||
const wordCount = page.wordCount ? page.wordCount[message.id] || 0 : 0;
|
||||
|
||||
|
||||
const noActions = {
|
||||
event_id: msg.id,
|
||||
liked: false,
|
||||
replied: false,
|
||||
reposted: false,
|
||||
zapped: false,
|
||||
};
|
||||
|
||||
let article: PrimalArticle = {
|
||||
id: msg.id,
|
||||
pubkey: msg.pubkey,
|
||||
title: '',
|
||||
summary: '',
|
||||
image: '',
|
||||
@ -462,6 +474,15 @@ export const convertToArticles: ConvertToArticles = (page, topZaps) => {
|
||||
mentionedNotes,
|
||||
mentionedUsers,
|
||||
wordCount,
|
||||
noteActions: (page.noteActions && page.noteActions[msg.id]) ?? noActions,
|
||||
likes: stat?.likes || 0,
|
||||
mentions: stat?.mentions || 0,
|
||||
reposts: stat?.reposts || 0,
|
||||
replies: stat?.replies || 0,
|
||||
zaps: stat?.zaps || 0,
|
||||
score: stat?.score || 0,
|
||||
score24h: stat?.score24h || 0,
|
||||
satszapped: stat?.satszapped || 0,
|
||||
};
|
||||
|
||||
msg.tags.forEach(tag => {
|
||||
|
11
src/types/primal.d.ts
vendored
11
src/types/primal.d.ts
vendored
@ -497,6 +497,7 @@ export type PrimalNote = {
|
||||
mentionedUsers?: Record<string, PrimalUser>,
|
||||
replyTo?: string,
|
||||
id: string,
|
||||
pubkey: string,
|
||||
tags: string[][],
|
||||
topZaps: TopZap[],
|
||||
};
|
||||
@ -516,9 +517,19 @@ export type PrimalArticle = {
|
||||
mentionedUsers?: Record<string, PrimalUser>,
|
||||
replyTo?: string,
|
||||
id: string,
|
||||
pubkey: string,
|
||||
naddr: string,
|
||||
msg: NostrNoteContent,
|
||||
wordCount: number,
|
||||
noteActions: NoteActions,
|
||||
likes: number,
|
||||
mentions: number,
|
||||
reposts: number,
|
||||
replies: number,
|
||||
zaps: number,
|
||||
score: number,
|
||||
score24h: number,
|
||||
satszapped: number,
|
||||
};
|
||||
|
||||
export type PrimalFeed = {
|
||||
|
Loading…
Reference in New Issue
Block a user