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 { 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 { createStore } from 'solid-js/store';
|
||||||
import { Portal } from 'solid-js/web';
|
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 { shortDate } from '../../lib/dates';
|
||||||
import { hookForDev } from '../../lib/devTools';
|
import { hookForDev } from '../../lib/devTools';
|
||||||
import { userName } from '../../stores/profile';
|
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 Avatar from '../Avatar/Avatar';
|
||||||
import { NoteReactionsState } from '../Note/Note';
|
import { NoteReactionsState } from '../Note/Note';
|
||||||
import ArticleFooter from '../Note/NoteFooter/ArticleFooter';
|
import ArticleFooter from '../Note/NoteFooter/ArticleFooter';
|
||||||
@ -21,17 +25,20 @@ const ArticlePreview: Component<{
|
|||||||
article: PrimalArticle,
|
article: PrimalArticle,
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
|
|
||||||
|
const app = useAppContext();
|
||||||
|
const account = useAccountContext();
|
||||||
|
const thread = useThreadContext();
|
||||||
|
|
||||||
const [reactionsState, updateReactionsState] = createStore<NoteReactionsState>({
|
const [reactionsState, updateReactionsState] = createStore<NoteReactionsState>({
|
||||||
likes: 0,
|
likes: props.article.likes,
|
||||||
liked: false,
|
liked: props.article.noteActions.liked,
|
||||||
reposts: 0,
|
reposts: props.article.reposts,
|
||||||
reposted: false,
|
reposted: props.article.noteActions.reposted,
|
||||||
replies: 0,
|
replies: props.article.replies,
|
||||||
replied: false,
|
replied: props.article.noteActions.replied,
|
||||||
zapCount: 0,
|
zapCount: props.article.zaps,
|
||||||
satsZapped: 0,
|
satsZapped: props.article.satszapped,
|
||||||
zapped: false,
|
zapped: props.article.noteActions.zapped,
|
||||||
zappedAmount: 0,
|
zappedAmount: 0,
|
||||||
zappedNow: false,
|
zappedNow: false,
|
||||||
isZapping: false,
|
isZapping: false,
|
||||||
@ -44,6 +51,136 @@ const ArticlePreview: Component<{
|
|||||||
quoteCount: 0,
|
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 (
|
return (
|
||||||
<A class={styles.article} href={`/e/${props.article.naddr}`}>
|
<A class={styles.article} href={`/e/${props.article.naddr}`}>
|
||||||
<div class={styles.header}>
|
<div class={styles.header}>
|
||||||
@ -101,13 +238,8 @@ const ArticlePreview: Component<{
|
|||||||
note={props.article}
|
note={props.article}
|
||||||
state={reactionsState}
|
state={reactionsState}
|
||||||
updateState={updateReactionsState}
|
updateState={updateReactionsState}
|
||||||
customZapInfo={{
|
customZapInfo={customZapInfo()}
|
||||||
note: props.article,
|
onZapAnim={addTopZapFeed}
|
||||||
onConfirm: () => {},
|
|
||||||
onSuccess: () => {},
|
|
||||||
onFail: () => {},
|
|
||||||
onCancel: () => {},
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import { useToastContext } from '../../Toaster/Toaster';
|
|||||||
import { useIntl } from '@cookbook/solid-intl';
|
import { useIntl } from '@cookbook/solid-intl';
|
||||||
|
|
||||||
import { truncateNumber } from '../../../lib/notifications';
|
import { truncateNumber } from '../../../lib/notifications';
|
||||||
import { canUserReceiveZaps, zapNote } from '../../../lib/zap';
|
import { canUserReceiveZaps, zapArticle, zapNote } from '../../../lib/zap';
|
||||||
import { useSettingsContext } from '../../../contexts/SettingsContext';
|
import { useSettingsContext } from '../../../contexts/SettingsContext';
|
||||||
|
|
||||||
import zapMD from '../../../assets/lottie/zap_md_2.json';
|
import zapMD from '../../../assets/lottie/zap_md_2.json';
|
||||||
@ -135,167 +135,167 @@ const ArticleFooter: Component<{
|
|||||||
const doReply = () => {};
|
const doReply = () => {};
|
||||||
|
|
||||||
const doLike = async (e: MouseEvent) => {
|
const doLike = async (e: MouseEvent) => {
|
||||||
// e.preventDefault();
|
e.preventDefault();
|
||||||
// e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// if (!account) {
|
if (!account) {
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if (!account.hasPublicKey()) {
|
if (!account.hasPublicKey()) {
|
||||||
// account.actions.showGetStarted();
|
account.actions.showGetStarted();
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if (account.relays.length === 0) {
|
if (account.relays.length === 0) {
|
||||||
// toast?.sendWarning(
|
toast?.sendWarning(
|
||||||
// intl.formatMessage(t.noRelaysConnected),
|
intl.formatMessage(t.noRelaysConnected),
|
||||||
// );
|
);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// const success = await account.actions.addLike(props.note);
|
const success = await account.actions.addLike(props.note);
|
||||||
|
|
||||||
// if (success) {
|
if (success) {
|
||||||
// batch(() => {
|
batch(() => {
|
||||||
// props.updateState('likes', (l) => l + 1);
|
props.updateState('likes', (l) => l + 1);
|
||||||
// props.updateState('liked', () => true);
|
props.updateState('liked', () => true);
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const startZap = (e: MouseEvent | TouchEvent) => {
|
const startZap = (e: MouseEvent | TouchEvent) => {
|
||||||
// e.preventDefault();
|
e.preventDefault();
|
||||||
// e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// if (!account?.hasPublicKey()) {
|
if (!account?.hasPublicKey()) {
|
||||||
// account?.actions.showGetStarted();
|
account?.actions.showGetStarted();
|
||||||
// props.updateState('isZapping', () => false);
|
props.updateState('isZapping', () => false);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if (account.relays.length === 0) {
|
if (account.relays.length === 0) {
|
||||||
// toast?.sendWarning(
|
toast?.sendWarning(
|
||||||
// intl.formatMessage(t.noRelaysConnected),
|
intl.formatMessage(t.noRelaysConnected),
|
||||||
// );
|
);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if (!canUserReceiveZaps(props.note.user)) {
|
if (!canUserReceiveZaps(props.note.author)) {
|
||||||
// toast?.sendWarning(
|
toast?.sendWarning(
|
||||||
// intl.formatMessage(t.zapUnavailable),
|
intl.formatMessage(t.zapUnavailable),
|
||||||
// );
|
);
|
||||||
// props.updateState('isZapping', () => false);
|
props.updateState('isZapping', () => false);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// quickZapDelay = setTimeout(() => {
|
quickZapDelay = setTimeout(() => {
|
||||||
// app?.actions.openCustomZapModal(props.customZapInfo);
|
app?.actions.openCustomZapModal(props.customZapInfo);
|
||||||
// props.updateState('isZapping', () => true);
|
props.updateState('isZapping', () => true);
|
||||||
// }, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
const commitZap = (e: MouseEvent | TouchEvent) => {
|
const commitZap = (e: MouseEvent | TouchEvent) => {
|
||||||
// e.preventDefault();
|
e.preventDefault();
|
||||||
// e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// clearTimeout(quickZapDelay);
|
clearTimeout(quickZapDelay);
|
||||||
|
|
||||||
// if (!account?.hasPublicKey()) {
|
if (!account?.hasPublicKey()) {
|
||||||
// account?.actions.showGetStarted();
|
account?.actions.showGetStarted();
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if (account.relays.length === 0 || !canUserReceiveZaps(props.note.user)) {
|
if (account.relays.length === 0 || !canUserReceiveZaps(props.note.author)) {
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if (app?.customZap === undefined) {
|
if (app?.customZap === undefined) {
|
||||||
// doQuickZap();
|
doQuickZap();
|
||||||
// }
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const animateZap = () => {
|
const animateZap = () => {
|
||||||
// setTimeout(() => {
|
setTimeout(() => {
|
||||||
// props.updateState('hideZapIcon', () => true);
|
props.updateState('hideZapIcon', () => true);
|
||||||
|
|
||||||
// if (!medZapAnimation) {
|
if (!medZapAnimation) {
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// let newLeft = props.wide ? 15 : 13;
|
let newLeft = props.wide ? 15 : 13;
|
||||||
// let newTop = props.wide ? -6 : -6;
|
let newTop = props.wide ? -6 : -6;
|
||||||
|
|
||||||
// if (props.large) {
|
if (props.large) {
|
||||||
// newLeft = 2;
|
newLeft = 2;
|
||||||
// newTop = -9;
|
newTop = -9;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// medZapAnimation.style.left = `${newLeft}px`;
|
medZapAnimation.style.left = `${newLeft}px`;
|
||||||
// medZapAnimation.style.top = `${newTop}px`;
|
medZapAnimation.style.top = `${newTop}px`;
|
||||||
|
|
||||||
// const onAnimDone = () => {
|
const onAnimDone = () => {
|
||||||
// batch(() => {
|
batch(() => {
|
||||||
// props.updateState('showZapAnim', () => false);
|
props.updateState('showZapAnim', () => false);
|
||||||
// props.updateState('hideZapIcon', () => false);
|
props.updateState('hideZapIcon', () => false);
|
||||||
// props.updateState('zapped', () => true);
|
props.updateState('zapped', () => true);
|
||||||
// });
|
});
|
||||||
// medZapAnimation?.removeEventListener('complete', onAnimDone);
|
medZapAnimation?.removeEventListener('complete', onAnimDone);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// medZapAnimation.addEventListener('complete', onAnimDone);
|
medZapAnimation.addEventListener('complete', onAnimDone);
|
||||||
|
|
||||||
// try {
|
try {
|
||||||
// // @ts-ignore
|
// @ts-ignore
|
||||||
// medZapAnimation.seek(0);
|
medZapAnimation.seek(0);
|
||||||
// // @ts-ignore
|
// @ts-ignore
|
||||||
// medZapAnimation.play();
|
medZapAnimation.play();
|
||||||
// } catch (e) {
|
} catch (e) {
|
||||||
// console.warn('Failed to animte zap:', e);
|
console.warn('Failed to animte zap:', e);
|
||||||
// onAnimDone();
|
onAnimDone();
|
||||||
// }
|
}
|
||||||
// }, 10);
|
}, 10);
|
||||||
};
|
};
|
||||||
|
|
||||||
const doQuickZap = async () => {
|
const doQuickZap = async () => {
|
||||||
// if (!account?.hasPublicKey()) {
|
if (!account?.hasPublicKey()) {
|
||||||
// account?.actions.showGetStarted();
|
account?.actions.showGetStarted();
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// const amount = settings?.defaultZap.amount || 10;
|
const amount = settings?.defaultZap.amount || 10;
|
||||||
// const message = settings?.defaultZap.message || '';
|
const message = settings?.defaultZap.message || '';
|
||||||
// const emoji = settings?.defaultZap.emoji;
|
const emoji = settings?.defaultZap.emoji;
|
||||||
|
|
||||||
// batch(() => {
|
batch(() => {
|
||||||
// props.updateState('isZapping', () => true);
|
props.updateState('isZapping', () => true);
|
||||||
// props.updateState('satsZapped', (z) => z + amount);
|
props.updateState('satsZapped', (z) => z + amount);
|
||||||
// props.updateState('showZapAnim', () => true);
|
props.updateState('showZapAnim', () => true);
|
||||||
// });
|
});
|
||||||
|
|
||||||
// props.onZapAnim && props.onZapAnim({ amount, message, emoji })
|
props.onZapAnim && props.onZapAnim({ amount, message, emoji })
|
||||||
|
|
||||||
// setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
// const success = await zapNote(props.note, account.publicKey, amount, message, account.relays);
|
const success = await zapArticle(props.note, account.publicKey, amount, message, account.relays);
|
||||||
|
|
||||||
// props.updateState('isZapping', () => false);
|
props.updateState('isZapping', () => false);
|
||||||
|
|
||||||
// if (success) {
|
if (success) {
|
||||||
// props.customZapInfo.onSuccess({
|
props.customZapInfo.onSuccess({
|
||||||
// emoji,
|
emoji,
|
||||||
// amount,
|
amount,
|
||||||
// message,
|
message,
|
||||||
// });
|
});
|
||||||
|
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// props.customZapInfo.onFail({
|
props.customZapInfo.onFail({
|
||||||
// emoji,
|
emoji,
|
||||||
// amount,
|
amount,
|
||||||
// message,
|
message,
|
||||||
// });
|
});
|
||||||
// }, lottieDuration());
|
}, lottieDuration());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
PrimalNote,
|
PrimalNote,
|
||||||
PrimalUser,
|
PrimalUser,
|
||||||
NostrEventContent,
|
NostrEventContent,
|
||||||
|
PrimalArticle,
|
||||||
} from '../types/primal';
|
} from '../types/primal';
|
||||||
import { Kind, pinEncodePrefix, relayConnectingTimeout } from "../constants";
|
import { Kind, pinEncodePrefix, relayConnectingTimeout } from "../constants";
|
||||||
import { isConnected, refreshSocketListeners, removeSocketListeners, socket, subscribeTo, reset, subTo } from "../sockets";
|
import { isConnected, refreshSocketListeners, removeSocketListeners, socket, subscribeTo, reset, subTo } from "../sockets";
|
||||||
@ -79,7 +80,7 @@ export type AccountContextStore = {
|
|||||||
showNewNoteForm: () => void,
|
showNewNoteForm: () => void,
|
||||||
hideNewNoteForm: () => void,
|
hideNewNoteForm: () => void,
|
||||||
setActiveUser: (user: PrimalUser) => void,
|
setActiveUser: (user: PrimalUser) => void,
|
||||||
addLike: (note: PrimalNote) => Promise<boolean>,
|
addLike: (note: PrimalNote | PrimalArticle) => Promise<boolean>,
|
||||||
setPublicKey: (pubkey: string | undefined) => void,
|
setPublicKey: (pubkey: string | undefined) => void,
|
||||||
addFollow: (pubkey: string, cb?: (remove: boolean, pubkey: string) => void) => void,
|
addFollow: (pubkey: string, cb?: (remove: boolean, pubkey: string) => void) => void,
|
||||||
removeFollow: (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);
|
updateStore('showNewNoteForm', () => false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addLike = async (note: PrimalNote) => {
|
const addLike = async (note: PrimalNote | PrimalArticle) => {
|
||||||
if (store.likes.includes(note.post.id)) {
|
if (store.likes.includes(note.id)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { success } = await sendLike(note, store.relays, store.relaySettings);
|
const { success } = await sendLike(note, store.relays, store.relaySettings);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
updateStore('likes', (likes) => [ ...likes, note.post.id]);
|
updateStore('likes', (likes) => [ ...likes, note.id]);
|
||||||
saveLikes(store.publicKey, store.likes);
|
saveLikes(store.publicKey, store.likes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
onMount,
|
onMount,
|
||||||
useContext
|
useContext
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import { PrimalNote, PrimalUser, ZapOption } from "../types/primal";
|
import { PrimalArticle, PrimalNote, PrimalUser, ZapOption } from "../types/primal";
|
||||||
import { CashuMint } from "@cashu/cashu-ts";
|
import { CashuMint } from "@cashu/cashu-ts";
|
||||||
|
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ export type ReactionStats = {
|
|||||||
|
|
||||||
export type CustomZapInfo = {
|
export type CustomZapInfo = {
|
||||||
profile?: PrimalUser,
|
profile?: PrimalUser,
|
||||||
note?: PrimalNote,
|
note?: PrimalNote | PrimalArticle,
|
||||||
onConfirm: (zapOption: ZapOption) => void,
|
onConfirm: (zapOption: ZapOption) => void,
|
||||||
onSuccess: (zapOption: ZapOption) => void,
|
onSuccess: (zapOption: ZapOption) => void,
|
||||||
onFail: (zapOption: ZapOption) => void,
|
onFail: (zapOption: ZapOption) => void,
|
||||||
|
@ -489,6 +489,7 @@ export const ReadsProvider = (props: { children: ContextChildren }) => {
|
|||||||
if (content.kind === Kind.NoteStats) {
|
if (content.kind === Kind.NoteStats) {
|
||||||
const statistic = content as NostrStatsContent;
|
const statistic = content as NostrStatsContent;
|
||||||
const stat = JSON.parse(statistic.content);
|
const stat = JSON.parse(statistic.content);
|
||||||
|
console.log('READS STATS: ', stat)
|
||||||
|
|
||||||
if (scope) {
|
if (scope) {
|
||||||
updateStore(scope, 'page', 'postStats',
|
updateStore(scope, 'page', 'postStats',
|
||||||
@ -523,6 +524,7 @@ export const ReadsProvider = (props: { children: ContextChildren }) => {
|
|||||||
const noteActionContent = content as NostrNoteActionsContent;
|
const noteActionContent = content as NostrNoteActionsContent;
|
||||||
const noteActions = JSON.parse(noteActionContent.content) as NoteActions;
|
const noteActions = JSON.parse(noteActionContent.content) as NoteActions;
|
||||||
|
|
||||||
|
console.log('READS ACTIONS: ', content)
|
||||||
if (scope) {
|
if (scope) {
|
||||||
updateStore(scope, 'page', 'noteActions',
|
updateStore(scope, 'page', 'noteActions',
|
||||||
(actions) => ({ ...actions, [noteActions.event_id]: { ...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 };
|
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 = {
|
const event = {
|
||||||
content: '+',
|
content: '+',
|
||||||
kind: Kind.Reaction,
|
kind: Kind.Reaction,
|
||||||
tags: [
|
tags: [
|
||||||
['e', note.post.id],
|
['e', note.id],
|
||||||
['p', note.post.pubkey],
|
['p', note.pubkey],
|
||||||
],
|
],
|
||||||
created_at: Math.floor((new Date()).getTime() / 1000),
|
created_at: Math.floor((new Date()).getTime() / 1000),
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { bech32 } from "@scure/base";
|
import { bech32 } from "@scure/base";
|
||||||
// @ts-ignore Bad types in nostr-tools
|
// @ts-ignore Bad types in nostr-tools
|
||||||
import { nip57, Relay, utils } from "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 { logError } from "./logger";
|
||||||
import { enableWebLn, sendPayment, signEvent } from "./nostrAPI";
|
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[]) => {
|
export const zapProfile = async (profile: PrimalUser, sender: string | undefined, amount: number, comment = '', relays: Relay[]) => {
|
||||||
if (!sender || !profile) {
|
if (!sender || !profile) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -27,13 +27,14 @@ import { PrimalUser } from '../types/primal';
|
|||||||
import Avatar from '../components/Avatar/Avatar';
|
import Avatar from '../components/Avatar/Avatar';
|
||||||
import { userName } from '../stores/profile';
|
import { userName } from '../stores/profile';
|
||||||
import { useAccountContext } from '../contexts/AccountContext';
|
import { useAccountContext } from '../contexts/AccountContext';
|
||||||
import { feedNewPosts, placeholders, branding } from '../translations';
|
import { reads, branding } from '../translations';
|
||||||
import Search from '../components/Search/Search';
|
import Search from '../components/Search/Search';
|
||||||
import { setIsHome } from '../components/Layout/Layout';
|
import { setIsHome } from '../components/Layout/Layout';
|
||||||
import PageTitle from '../components/PageTitle/PageTitle';
|
import PageTitle from '../components/PageTitle/PageTitle';
|
||||||
import { useAppContext } from '../contexts/AppContext';
|
import { useAppContext } from '../contexts/AppContext';
|
||||||
import { useReadsContext } from '../contexts/ReadsContext';
|
import { useReadsContext } from '../contexts/ReadsContext';
|
||||||
import ArticlePreview from '../components/ArticlePreview/ArticlePreview';
|
import ArticlePreview from '../components/ArticlePreview/ArticlePreview';
|
||||||
|
import PageCaption from '../components/PageCaption/PageCaption';
|
||||||
|
|
||||||
|
|
||||||
const Home: Component = () => {
|
const Home: Component = () => {
|
||||||
@ -133,18 +134,7 @@ const Home: Component = () => {
|
|||||||
<Search />
|
<Search />
|
||||||
</Wormhole>
|
</Wormhole>
|
||||||
|
|
||||||
<div class={styles.normalCentralHeader}>
|
<PageCaption title={intl.formatMessage(reads.pageTitle)} />
|
||||||
<HomeHeader
|
|
||||||
hasNewPosts={hasNewPosts}
|
|
||||||
loadNewContent={loadNewContent}
|
|
||||||
newPostCount={newPostCount}
|
|
||||||
newPostAuthors={newPostAuthors}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class={styles.phoneCentralHeader}>
|
|
||||||
<HomeHeaderPhone />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<StickySidebar>
|
<StickySidebar>
|
||||||
<HomeSidebar />
|
<HomeSidebar />
|
||||||
|
@ -366,6 +366,7 @@ 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,
|
||||||
|
pubkey: msg.pubkey,
|
||||||
topZaps: [ ...tz ],
|
topZaps: [ ...tz ],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -391,6 +392,7 @@ export const convertToArticles: ConvertToArticles = (page, topZaps) => {
|
|||||||
const kind = msg.kind;
|
const kind = msg.kind;
|
||||||
|
|
||||||
const user = page?.users[msg.pubkey];
|
const user = page?.users[msg.pubkey];
|
||||||
|
const stat = page?.postStats[msg.id];
|
||||||
|
|
||||||
const mentionIds = Object.keys(mentions)
|
const mentionIds = Object.keys(mentions)
|
||||||
let userMentionIds = msg.tags?.reduce((acc, t) => t[0] === 'p' ? [...acc, t[1]] : acc, []);
|
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 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 = {
|
let article: PrimalArticle = {
|
||||||
id: msg.id,
|
id: msg.id,
|
||||||
|
pubkey: msg.pubkey,
|
||||||
title: '',
|
title: '',
|
||||||
summary: '',
|
summary: '',
|
||||||
image: '',
|
image: '',
|
||||||
@ -462,6 +474,15 @@ export const convertToArticles: ConvertToArticles = (page, topZaps) => {
|
|||||||
mentionedNotes,
|
mentionedNotes,
|
||||||
mentionedUsers,
|
mentionedUsers,
|
||||||
wordCount,
|
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 => {
|
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>,
|
mentionedUsers?: Record<string, PrimalUser>,
|
||||||
replyTo?: string,
|
replyTo?: string,
|
||||||
id: string,
|
id: string,
|
||||||
|
pubkey: string,
|
||||||
tags: string[][],
|
tags: string[][],
|
||||||
topZaps: TopZap[],
|
topZaps: TopZap[],
|
||||||
};
|
};
|
||||||
@ -516,9 +517,19 @@ export type PrimalArticle = {
|
|||||||
mentionedUsers?: Record<string, PrimalUser>,
|
mentionedUsers?: Record<string, PrimalUser>,
|
||||||
replyTo?: string,
|
replyTo?: string,
|
||||||
id: string,
|
id: string,
|
||||||
|
pubkey: string,
|
||||||
naddr: string,
|
naddr: string,
|
||||||
msg: NostrNoteContent,
|
msg: NostrNoteContent,
|
||||||
wordCount: number,
|
wordCount: number,
|
||||||
|
noteActions: NoteActions,
|
||||||
|
likes: number,
|
||||||
|
mentions: number,
|
||||||
|
reposts: number,
|
||||||
|
replies: number,
|
||||||
|
zaps: number,
|
||||||
|
score: number,
|
||||||
|
score24h: number,
|
||||||
|
satszapped: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PrimalFeed = {
|
export type PrimalFeed = {
|
||||||
|
Loading…
Reference in New Issue
Block a user