Refactor notes

This commit is contained in:
Bojan Mojsilovic 2024-04-02 15:45:39 +02:00
parent 21457cc2c5
commit ded15540a2
13 changed files with 631 additions and 276 deletions

View File

@ -234,13 +234,158 @@
font-weight: bold;
}
.verificationFailed {
display: inline-block;
width: 4px;
height: 4px;
}
.notePrimary {
position: relative;
background-color: var(--background-card);
display: flex;
flex-direction: column;
padding: 12px;
padding-top: 0;
border-radius: 0;
border: none;
.content {
grid-area: content;
display: flex;
flex-direction: column;
margin-left: 2px;
margin-top: 2px;
cursor: text;
.message {
position: relative;
grid-area: message;
color: var(--text-primary);
word-break: break-word;
font-size: 18px;
font-weight: 400;
line-height: 24px;
width: 100%;
margin-bottom: 12px;
a:hover {
text-decoration: underline;
}
.messageFade {
position: absolute;
z-index: 1;
top: 610px;
left: 0;
pointer-events: none;
background-image: var(--fade-note-vertical);
width: 100%;
height: 40px;
}
}
}
}
.noteNotificationLink {
text-decoration: none;
color: unset;
margin: 0px;
padding: 0px;
// background: var(--brand-gradient-vertical);
background-color: var(--background-card);
border-radius: 6px;
display: block;
transition: 0.2s padding;
margin-top: 6px;
.noteNotifications {
background-color: var(--background-site);
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
grid-template-areas: "content";
padding: 0px;
border-radius: 4px;
transition: 0.2s border-radius ease-out;
.content {
grid-area: content;
display: flex;
flex-direction: column;
.message {
grid-area: message;
color: var(--text-primary);
word-break: break-word;
font-size: 16px;
line-height: 24px;
width: 100%;
a:hover {
text-decoration: underline;
}
}
.footer {
margin-top: 12px;
}
}
}
// &:hover {
// padding-left: 4px;
// transition: 0.2s padding;
// border-radius: 4px;
// >div {
// border-radius: 0px 4px 4px 0px;
// transition: 0.2s border-radius ease-out;
// }
// }
}
.context {
background: none;
display: flex;
justify-content: flex-end;
align-items: center;
.contextButton {
width: 42px;
height: 32px;
padding: 0;
margin: 0;
background: none;
border: none;
outline: none;
display: flex;
justify-content: center;
align-items: center;
&:focus {
outline: none;
box-shadow: none;
}
.contextIcon {
width: 16px;
height: 14px;
background-color: var(--text-secondary-2);
-webkit-mask: url(../../assets/icons/context.svg) no-repeat 0 / 100%;
mask: url(../../assets/icons/context.svg) no-repeat 0 / 100%;
}
&:hover {
.contextIcon {
background-color: var(--text-primary);
}
}
}
}
@media only screen and (max-width: 720px) {
.note {
width: 100dvw;
@ -258,6 +403,24 @@
width: 100%;
}
.notePrimary {
width: 100vw;
margin-left: 0px;
margin-right: 0px;
padding-right: 12px;
.content {
margin-left: 0px;
}
}
.noteNotificationLink {
.noteNotifications {
grid-template-columns: 1fr;
margin-left: 0px;
margin-right: 0px;
padding-right: 0px;
}
}
}
.upRightFloater {

View File

@ -1,6 +1,6 @@
import { A } from '@solidjs/router';
import { Component, Show } from 'solid-js';
import { PrimalNote } from '../../types/primal';
import { batch, Component, Match, Show, Switch } from 'solid-js';
import { PrimalNote, ZapOption } from '../../types/primal';
import ParsedNote from '../ParsedNote/ParsedNote';
import NoteFooter from './NoteFooter/NoteFooter';
@ -12,20 +12,207 @@ import Avatar from '../Avatar/Avatar';
import NoteAuthorInfo from './NoteAuthorInfo';
import NoteRepostHeader from './NoteRepostHeader';
import NoteReplyToHeader from './NoteReplyToHeader';
import BookmarkNote from '../BookmarkNote/BookmarkNote';
import NoteHeader from './NoteHeader/NoteHeader';
import { createStore } from 'solid-js/store';
import { CustomZapInfo, useAppContext } from '../../contexts/AppContext';
import NoteContextTrigger from './NoteContextTrigger';
const Note: Component<{ note: PrimalNote, id?: string, parent?: boolean, shorten?: boolean }> = (props) => {
export type NoteFooterState = {
likes: number,
liked: boolean,
reposts: number,
reposted: boolean,
replies: number,
replied: boolean,
zapCount: number,
satsZapped: number,
zappedAmount: number,
zapped: boolean,
zappedNow: boolean,
isZapping: boolean,
showZapAnim: boolean,
hideZapIcon: boolean,
isRepostMenuVisible: boolean,
};
const Note: Component<{
note: PrimalNote,
id?: string,
parent?: boolean,
shorten?: boolean,
noteType?: 'feed' | 'primary' | 'notification'
}> = (props) => {
const threadContext = useThreadContext();
const app = useAppContext();
const intl = useIntl();
const noteType = () => props.noteType || 'feed';
const repost = () => props.note.repost;
const navToThread = (note: PrimalNote) => {
threadContext?.actions.setPrimaryNote(note);
};
const [footerState, updateFooterState] = createStore({
likes: props.note.post.likes,
liked: props.note.post.noteActions.liked,
reposts: props.note.post.reposts,
reposted: props.note.post.noteActions.reposted,
replies: props.note.post.replies,
replied: props.note.post.noteActions.replied,
zapCount: props.note.post.zaps,
satsZapped: props.note.post.satszapped,
zapped: props.note.post.noteActions.zapped,
zappedAmount: 0,
zappedNow: false,
isZapping: false,
showZapAnim: false,
hideZapIcon: false,
isRepostMenuVisible: false,
});
let noteContextMenu: HTMLDivElement | undefined;
const onConfirmZap = (zapOption: ZapOption) => {
app?.actions.closeCustomZapModal();
batch(() => {
updateFooterState('zappedAmount', () => zapOption.amount || 0);
updateFooterState('zappedNow', () => true);
updateFooterState('zapped', () => true);
updateFooterState('showZapAnim', () => true)
});
};
const onSuccessZap = (zapOption: ZapOption) => {
app?.actions.closeCustomZapModal();
batch(() => {
updateFooterState('isZapping', () => false);
updateFooterState('zappedNow', () => false);
updateFooterState('showZapAnim', () => false);
updateFooterState('hideZapIcon', () => false);
updateFooterState('zapped', () => true);
});
};
const onFailZap = (zapOption: ZapOption) => {
app?.actions.closeCustomZapModal();
batch(() => {
updateFooterState('zappedAmount', () => -(zapOption.amount || 0));
updateFooterState('isZapping', () => false);
updateFooterState('zappedNow', () => true);
updateFooterState('showZapAnim', () => false);
updateFooterState('hideZapIcon', () => false);
updateFooterState('zapped', () => props.note.post.noteActions.zapped);
});
};
const onCancelZap = (zapOption: ZapOption) => {
app?.actions.closeCustomZapModal();
batch(() => {
updateFooterState('zappedAmount', () => -(zapOption.amount || 0));
updateFooterState('isZapping', () => false);
updateFooterState('zappedNow', () => true);
updateFooterState('showZapAnim', () => false);
updateFooterState('hideZapIcon', () => false);
updateFooterState('zapped', () => props.note.post.noteActions.zapped);
});
};
const customZapInfo: CustomZapInfo = {
note: props.note,
onConfirm: onConfirmZap,
onSuccess: onSuccessZap,
onFail: onFailZap,
onCancel: onCancelZap,
};
const onContextMenuTrigger = () => {
app?.actions.openContextMenu(
props.note,
noteContextMenu?.getBoundingClientRect(),
() => {
app?.actions.openCustomZapModal(customZapInfo);
},
() => {
app?.actions.openReactionModal(props.note.post.id, {
likes: footerState.likes,
zaps: footerState.zapCount,
reposts: footerState.reposts,
quotes: 0,
});
}
);
}
return (
<Switch>
<Match when={noteType() === 'notification'}>
<A
id={props.id}
class={styles.noteNotificationLink}
href={`/e/${props.note?.post.noteId}`}
onClick={() => navToThread(props.note)}
data-event={props.note.post.id}
data-event-bech32={props.note.post.noteId}
>
<div class={styles.noteNotifications}>
<div class={styles.content}>
<div class={styles.message}>
<ParsedNote note={props.note} shorten={true} />
</div>
<div class={styles.footer}>
<NoteFooter
note={props.note}
state={footerState}
updateState={updateFooterState}
customZapInfo={customZapInfo}
/>
</div>
</div>
</div>
</A>
</Match>
<Match when={noteType() === 'primary'}>
<div
id={props.id}
class={styles.notePrimary}
data-event={props.note.post.id}
data-event-bech32={props.note.post.noteId}
>
<div class={styles.border}></div>
<NoteHeader note={props.note} primary={true} />
<div class={styles.upRightFloater}>
<NoteContextTrigger
ref={noteContextMenu}
onClick={onContextMenuTrigger}
/>
</div>
<div class={styles.content}>
<div class={styles.message}>
<ParsedNote note={props.note} width={Math.min(574, window.innerWidth)} />
</div>
<NoteFooter
note={props.note}
state={footerState}
updateState={updateFooterState}
customZapInfo={customZapInfo}
wide={true}
/>
</div>
</div>
</Match>
<Match when={noteType() === 'feed'}>
<A
id={props.id}
class={`${styles.note} ${props.parent ? styles.parent : ''}`}
@ -59,7 +246,10 @@ const Note: Component<{ note: PrimalNote, id?: string, parent?: boolean, shorten
/>
<div class={styles.upRightFloater}>
<BookmarkNote note={props.note} />
<NoteContextTrigger
ref={noteContextMenu}
onClick={onContextMenuTrigger}
/>
</div>
<NoteReplyToHeader note={props.note} />
@ -72,11 +262,18 @@ const Note: Component<{ note: PrimalNote, id?: string, parent?: boolean, shorten
/>
</div>
<NoteFooter note={props.note} />
<NoteFooter
note={props.note}
state={footerState}
updateState={updateFooterState}
customZapInfo={customZapInfo}
/>
</div>
</div>
</A>
)
</Match>
</Switch>
);
}
export default hookForDev(Note);

View File

@ -51,6 +51,7 @@ const NoteContextMenu: Component<{
if (!props.open) {
context.setAttribute('style',`top: -1024px; left: -1034px;`);
return;
}
const docRect = document.documentElement.getBoundingClientRect();

View File

@ -0,0 +1,28 @@
import { Component } from 'solid-js';
import { hookForDev } from '../../lib/devTools';
import styles from './Note.module.scss';
const NoteContextTrigger: Component<{
ref: HTMLDivElement | undefined,
id?: string,
onClick?: () => void,
}> = (props) => {
return (
<div ref={props.ref} class={styles.context}>
<button
class={styles.contextButton}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
props.onClick && props.onClick();
}}
>
<div class={styles.contextIcon} ></div>
</button>
</div>
)
}
export default hookForDev(NoteContextTrigger);

View File

@ -43,12 +43,12 @@
.footer {
display: grid;
grid-template-columns: 128px 128px 128px 128px 16px;
grid-template-columns: 125px 125px 125px 125px 28px;
position: relative;
width: 100%;
&.wide {
grid-template-columns: 140px 140px 140px 138px 16px;
grid-template-columns: 137px 137px 137px 135px 28px;
}
.context {

View File

@ -1,5 +1,5 @@
import { Component, createEffect, createSignal, Show } from 'solid-js';
import { MenuItem, PrimalNote, ZapOption } from '../../../types/primal';
import { batch, Component, createEffect, Show } from 'solid-js';
import { MenuItem, PrimalNote } from '../../../types/primal';
import { sendRepost } from '../../../lib/notes';
import styles from './NoteFooter.module.scss';
@ -9,7 +9,6 @@ import { useIntl } from '@cookbook/solid-intl';
import { truncateNumber } from '../../../lib/notifications';
import { canUserReceiveZaps, zapNote } from '../../../lib/zap';
import CustomZap from '../../CustomZap/CustomZap';
import { useSettingsContext } from '../../../contexts/SettingsContext';
import zapMD from '../../../assets/lottie/zap_md.json';
@ -19,8 +18,19 @@ import { hookForDev } from '../../../lib/devTools';
import { getScreenCordinates } from '../../../utils';
import ZapAnimation from '../../ZapAnimation/ZapAnimation';
import { CustomZapInfo, useAppContext } from '../../../contexts/AppContext';
import NoteFooterActionButton from './NoteFooterActionButton';
import { NoteFooterState } from '../Note';
import { SetStoreFunction } from 'solid-js/store';
import BookmarkNote from '../../BookmarkNote/BookmarkNote';
const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> = (props) => {
const NoteFooter: Component<{
note: PrimalNote,
wide?: boolean,
id?: string,
state: NoteFooterState,
updateState: SetStoreFunction<NoteFooterState>,
customZapInfo: CustomZapInfo,
}> = (props) => {
const account = useAccountContext();
const toast = useToastContext();
@ -30,28 +40,8 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
let medZapAnimation: HTMLElement | undefined;
const [liked, setLiked] = createSignal(props.note.post.noteActions.liked);
const [zapped, setZapped] = createSignal(props.note.post.noteActions.zapped);
const [replied, setReplied] = createSignal(props.note.post.noteActions.replied);
const [reposted, setReposted] = createSignal(props.note.post.noteActions.reposted);
const [likes, setLikes] = createSignal(props.note.post.likes);
const [reposts, setReposts] = createSignal(props.note.post.reposts);
const [replies, setReplies] = createSignal(props.note.post.replies);
const [zapCount, setZapCount] = createSignal(props.note.post.zaps);
const [zaps, setZaps] = createSignal(props.note.post.satszapped);
const [isRepostMenuVisible, setIsRepostMenuVisible] = createSignal(false);
const [showZapAnim, setShowZapAnim] = createSignal(false);
const [hideZapIcon, setHideZapIcon] = createSignal(false);
const [zappedNow, setZappedNow] = createSignal(false);
const [zappedAmount, setZappedAmount] = createSignal(0);
const [isZapping, setIsZapping] = createSignal(false);
let quickZapDelay = 0;
let footerDiv: HTMLDivElement | undefined;
let noteContextMenu: HTMLDivElement | undefined;
let repostMenu: HTMLDivElement | undefined;
const repostMenuItems: MenuItem[] = [
@ -67,17 +57,16 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
},
];
const onClickOutside = (e: MouseEvent) => {
if (
!document?.getElementById(`repost_menu_${props.note.post.id}`)?.contains(e.target as Node)
) {
setIsRepostMenuVisible(false);
props.updateState('isRepostMenuVisible', () => false);
}
}
createEffect(() => {
if (isRepostMenuVisible()) {
if (props.state.isRepostMenuVisible) {
document.addEventListener('click', onClickOutside);
}
else {
@ -87,7 +76,7 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
const showRepostMenu = (e: MouseEvent) => {
e.preventDefault();
setIsRepostMenuVisible(true);
props.updateState('isRepostMenuVisible', () => true);
};
const doQuote = () => {
@ -95,7 +84,7 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
account?.actions.showGetStarted();
return;
}
setIsRepostMenuVisible(false);
props.updateState('isRepostMenuVisible', () => false);
account?.actions?.quoteNote(`nostr:${props.note.post.noteId}`);
account?.actions?.showNewNoteForm();
};
@ -117,13 +106,16 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
return;
}
setIsRepostMenuVisible(false);
props.updateState('isRepostMenuVisible', () => false);
const { success } = await sendRepost(props.note, account.relays, account.relaySettings);
if (success) {
setReposts(reposts() + 1);
setReposted(true);
batch(() => {
props.updateState('reposts', (r) => r + 1);
props.updateState('reposted', () => true);
});
toast?.sendSuccess(
intl.formatMessage(t.repostSuccess),
);
@ -160,63 +152,20 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
const success = await account.actions.addLike(props.note);
if (success) {
setLikes(likes() + 1);
setLiked(true);
batch(() => {
props.updateState('likes', (l) => l + 1);
props.updateState('liked', () => true);
});
}
};
const onConfirmZap = (zapOption: ZapOption) => {
app?.actions.closeCustomZapModal();
setZappedAmount(() => zapOption.amount || 0);
setZappedNow(true);
setZapped(true);
animateZap();
};
const onSuccessZap = (zapOption: ZapOption) => {
app?.actions.closeCustomZapModal();
setIsZapping(false);
setZappedNow(false);
setShowZapAnim(false);
setHideZapIcon(false);
setZapped(true);
};
const onFailZap = (zapOption: ZapOption) => {
setZappedAmount(() => -(zapOption.amount || 0));
setZappedNow(true);
app?.actions.closeCustomZapModal();
setIsZapping(false);
setShowZapAnim(false);
setHideZapIcon(false);
setZapped(props.note.post.noteActions.zapped);
};
const onCancelZap = (zapOption: ZapOption) => {
setZappedAmount(() => -(zapOption.amount || 0));
setZappedNow(true);
app?.actions.closeCustomZapModal();
setIsZapping(false);
setShowZapAnim(false);
setHideZapIcon(false);
setZapped(props.note.post.noteActions.zapped);
};
const customZapInfo: CustomZapInfo = {
note: props.note,
onConfirm: onConfirmZap,
onSuccess: onSuccessZap,
onFail: onFailZap,
onCancel: onCancelZap,
};
const startZap = (e: MouseEvent | TouchEvent) => {
e.preventDefault();
e.stopPropagation();
if (!account?.hasPublicKey()) {
account?.actions.showGetStarted()
setIsZapping(false);
account?.actions.showGetStarted();
props.updateState('isZapping', () => false);
return;
}
@ -231,13 +180,13 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
toast?.sendWarning(
intl.formatMessage(t.zapUnavailable),
);
setIsZapping(false);
props.updateState('isZapping', () => false);
return;
}
quickZapDelay = setTimeout(() => {
app?.actions.openCustomZapModal(customZapInfo);
setIsZapping(true);
app?.actions.openCustomZapModal(props.customZapInfo);
props.updateState('isZapping', () => true);
}, 500);
};
@ -262,9 +211,8 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
};
const animateZap = () => {
setShowZapAnim(true);
setTimeout(() => {
setHideZapIcon(true);
props.updateState('hideZapIcon', () => true);
if (!medZapAnimation) {
return;
@ -277,8 +225,10 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
medZapAnimation.style.top = `${newTop}px`;
const onAnimDone = () => {
setShowZapAnim(false);
setHideZapIcon(false);
batch(() => {
props.updateState('showZapAnim', () => false);
props.updateState('hideZapIcon', () => false);
});
medZapAnimation?.removeEventListener('complete', onAnimDone);
}
@ -302,19 +252,24 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
return;
}
setZappedAmount(() => settings?.defaultZap.amount || 0);
setZappedNow(true);
animateZap();
batch(() => {
props.updateState('zappedAmount', () => settings?.defaultZap.amount || 0);
props.updateState('zappedNow', () => true);
props.updateState('showZapAnim', () => true);
});
const success = await zapNote(props.note, account.publicKey, settings?.defaultZap.amount || 10, settings?.defaultZap.message || '', account.relays);
setIsZapping(false);
props.updateState('isZapping', () => false);
if (success) {
return;
}
setZappedAmount(() => -(settings?.defaultZap.amount || 0));
setZappedNow(true);
setZapped(props.note.post.noteActions.zapped);
batch(() => {
props.updateState('zappedAmount', () => -(settings?.defaultZap.amount || 0));
props.updateState('zappedNow', () => true);
props.updateState('zapped', () => props.note.post.noteActions.zapped);
});
}
const buttonTypeClasses: Record<string, string> = {
@ -324,51 +279,21 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
repost: styles.repostType,
};
const actionButton = (opts: {
type: 'zap' | 'like' | 'reply' | 'repost',
disabled?: boolean,
highlighted?: boolean,
onClick?: (e: MouseEvent) => void,
onMouseDown?: (e: MouseEvent) => void,
onMouseUp?: (e: MouseEvent) => void,
onTouchStart?: (e: TouchEvent) => void,
onTouchEnd?: (e: TouchEvent) => void,
label: string | number,
hidden?: boolean,
title?: string,
}) => {
return (
<button
id={`btn_${opts.type}_${props.note.post.id}`}
class={`${styles.stat} ${opts.highlighted ? styles.highlighted : ''}`}
onClick={opts.onClick ?? (() => {})}
onMouseDown={opts.onMouseDown ?? (() => {})}
onMouseUp={opts.onMouseUp ?? (() => {})}
onTouchStart={opts.onTouchStart ?? (() => {})}
onTouchEnd={opts.onTouchEnd ?? (() => {})}
disabled={opts.disabled}
>
<div class={`${buttonTypeClasses[opts.type]}`}>
<div
class={styles.icon}
style={opts.hidden ? 'visibility: hidden': 'visibility: visible'}
></div>
<div class={styles.statNumber}>{opts.label || ''}</div>
</div>
</button>
);
};
createEffect(() => {
if (props.state.zappedNow) {
batch(() => {
props.updateState('zapCount', (z) => z + 1);
props.updateState('satsZapped', (z) => z + props.state.zappedAmount);
props.updateState('zapped', () => true);
});
props.updateState('zappedNow', () => false);
}
});
createEffect(() => {
if (zappedNow()) {
setZapCount(c => c + 1);
setZaps((z) => z + zappedAmount());
setZapped(true);
setZappedNow(false);
if (props.state.showZapAnim) {
animateZap();
}
});
const determineOrient = () => {
@ -380,7 +305,7 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
return (
<div id={props.id} class={`${styles.footer} ${props.wide ? styles.wide : ''}`} ref={footerDiv} onClick={(e) => {e.preventDefault();}}>
<Show when={showZapAnim()}>
<Show when={props.state.showZapAnim}>
<ZapAnimation
id={`note-med-zap-${props.note.post.id}`}
src={zapMD}
@ -389,41 +314,43 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
/>
</Show>
{actionButton({
onClick: doReply,
type: 'reply',
highlighted: replied(),
label: replies() === 0 ? '' : truncateNumber(replies(), 2),
title: replies().toLocaleString(),
})}
<NoteFooterActionButton
note={props.note}
onClick={doReply}
type="reply"
highlighted={props.state.replied}
label={props.state.replies === 0 ? '' : truncateNumber(props.state.replies, 2)}
title={props.state.replies.toLocaleString()}
/>
{actionButton({
onClick: (e: MouseEvent) => e.preventDefault(),
onMouseDown: startZap,
onMouseUp: commitZap,
onTouchStart: startZap,
onTouchEnd: commitZap,
type: 'zap',
highlighted: zapped() || isZapping(),
label: zaps() === 0 ? '' : truncateNumber(zaps(), 2),
hidden: hideZapIcon(),
title: zaps().toLocaleString(),
})}
{actionButton({
onClick: doLike,
type: 'like',
highlighted: liked(),
label: likes() === 0 ? '' : truncateNumber(likes(), 2),
title: likes().toLocaleString(),
})}
<NoteFooterActionButton
note={props.note}
onClick={(e: MouseEvent) => e.preventDefault()}
onMouseDown={startZap}
onMouseUp={commitZap}
onTouchStart={startZap}
onTouchEnd={commitZap}
type="zap"
highlighted={props.state.zapped || props.state.isZapping}
label={props.state.satsZapped === 0 ? '' : truncateNumber(props.state.satsZapped, 2)}
hidden={props.state.hideZapIcon}
title={props.state.satsZapped.toLocaleString()}
/>
<NoteFooterActionButton
note={props.note}
onClick={doLike}
type="like"
highlighted={props.state.liked}
label={props.state.likes === 0 ? '' : truncateNumber(props.state.likes, 2)}
title={props.state.likes.toLocaleString()}
/>
<button
id={`btn_repost_${props.note.post.id}`}
class={`${styles.stat} ${reposted() ? styles.highlighted : ''}`}
class={`${styles.stat} ${props.state.reposted ? styles.highlighted : ''}`}
onClick={showRepostMenu}
title={reposts().toLocaleString()}
title={props.state.reposts.toLocaleString()}
>
<div
class={`${buttonTypeClasses.repost}`}
@ -434,45 +361,20 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
style={'visibility: visible'}
></div>
<div class={styles.statNumber}>
{reposts() === 0 ? '' : truncateNumber(reposts(), 2)}
{props.state.reposts === 0 ? '' : truncateNumber(props.state.reposts, 2)}
</div>
<PrimalMenu
id={`repost_menu_${props.note.post.id}`}
items={repostMenuItems}
position="note_footer"
orientation={determineOrient()}
hidden={!isRepostMenuVisible()}
hidden={!props.state.isRepostMenuVisible}
/>
</div>
</button>
<div ref={noteContextMenu} class={styles.context}>
<button
class={styles.contextButton}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
<BookmarkNote note={props.note} />
app?.actions.openContextMenu(
props.note,
noteContextMenu?.getBoundingClientRect(),
() => {
app?.actions.openCustomZapModal(customZapInfo);
},
() => {
app?.actions.openReactionModal(props.note.post.id, {
likes: likes(),
zaps: zapCount(),
reposts: reposts(),
quotes: 0,
});
}
);
}}
>
<div class={styles.contextIcon} ></div>
</button>
</div>
</div>
)
}

View File

@ -0,0 +1,50 @@
import { Component, createEffect, onCleanup } from 'solid-js';
import { PrimalNote } from '../../../types/primal';
import styles from './NoteFooter.module.scss';
const buttonTypeClasses: Record<string, string> = {
zap: styles.zapType,
like: styles.likeType,
reply: styles.replyType,
repost: styles.repostType,
};
const NoteFooterActionButton: Component<{
type: 'zap' | 'like' | 'reply' | 'repost',
note: PrimalNote,
disabled?: boolean,
highlighted?: boolean,
onClick?: (e: MouseEvent) => void,
onMouseDown?: (e: MouseEvent) => void,
onMouseUp?: (e: MouseEvent) => void,
onTouchStart?: (e: TouchEvent) => void,
onTouchEnd?: (e: TouchEvent) => void,
label: string | number,
hidden?: boolean,
title?: string,
}> = (props) => {
return (
<button
id={`btn_${props.type}_${props.note.post.id}`}
class={`${styles.stat} ${props.highlighted ? styles.highlighted : ''}`}
onClick={props.onClick ?? (() => {})}
onMouseDown={props.onMouseDown ?? (() => {})}
onMouseUp={props.onMouseUp ?? (() => {})}
onTouchStart={props.onTouchStart ?? (() => {})}
onTouchEnd={props.onTouchEnd ?? (() => {})}
disabled={props.disabled}
>
<div class={`${buttonTypeClasses[props.type]}`}>
<div
class={styles.icon}
style={props.hidden ? 'visibility: hidden': 'visibility: visible'}
></div>
<div class={styles.statNumber}>{props.label || ''}</div>
</div>
</button>
)
}
export default NoteFooterActionButton;

View File

@ -5,7 +5,7 @@
padding-top: 12px;
padding-bottom: 17px;
border-bottom: 1px solid var(--subtile-devider);
border-bottom: 1px solid var(--devider);
.newBubble {
position: absolute;

View File

@ -33,6 +33,7 @@ import NotificationNote from '../Note/NotificationNote/NotificationNote';
import NotificationAvatar from '../NotificationAvatar/NotificationAvatar';
import { notificationsNew as t } from '../../translations';
import { hookForDev } from '../../lib/devTools';
import Note from '../Note/Note';
const typeIcons: Record<string, string> = {
[NotificationType.NEW_USER_FOLLOWED_YOU]: userFollow,
@ -183,9 +184,10 @@ const NotificationItem: Component<NotificationItemProps> = (props) => {
>
<div class={styles.reference}>
<Show when={props.note}>
<NotificationNote
<Note
// @ts-ignore
note={props.note}
noteType="notification"
/>
</Show>
</div>

View File

@ -33,6 +33,7 @@ import NotificationNote from '../Note/NotificationNote/NotificationNote';
import { truncateNumber } from '../../lib/notifications';
import { notificationsOld as t } from '../../translations';
import { hookForDev } from '../../lib/devTools';
import Note from '../Note/Note';
const typeIcons: Record<string, string> = {
[NotificationType.NEW_USER_FOLLOWED_YOU]: userFollow,
@ -148,9 +149,10 @@ const NotificationItemOld: Component<NotificationItemProps> = (props) => {
>
<div class={styles.reference}>
<Show when={note()}>
<NotificationNote
<Note
// @ts-ignore
note={note()}
noteType="notification"
/>
</Show>
</div>

View File

@ -72,7 +72,14 @@ export const getUserFeed = (user_pubkey: string | undefined, pubkey: string | un
return;
}
let payload = { pubkey, limit, notes } ;
let payload: {
pubkey: string,
limit: number,
notes: 'authored' | 'replies' | 'bookmarks',
user_pubkey?: string,
until?: number,
offset?: number,
} = { pubkey, limit, notes } ;
if (user_pubkey) {
payload.user_pubkey = user_pubkey;

View File

@ -6,7 +6,7 @@
.oldNotifications {
position: relative;
padding-right: 2px;
// padding-right: 2px;
}
.loader {
@ -62,28 +62,6 @@
}
@media only screen and (max-width: 1300px) {
.newContentNotification {
left: calc(calc(100vw - 1032px) / 2 + 48px + 32px);
}
}
@media only screen and (max-width: 1087px) {
.newContentNotification {
left: calc(calc(100vw - 720px) / 2 + 48px + 32px);
}
}
@media only screen and (max-width: 720px) {
.newContentNotification {
left: 0;
width: 100%;
justify-content: center;
}
}
.notificationTabs {
position: relative;
display: flex;
@ -122,7 +100,7 @@
}
.notificationTabContent {
width: 100%;
width: 602px;
}
.notificationTabIndicator {
@ -148,3 +126,27 @@
border-radius: 0 0 8px 8px;
padding-top: 22px;
}
@media only screen and (max-width: 1300px) {
.newContentNotification {
left: calc(calc(100vw - 1032px) / 2 + 48px + 32px);
}
}
@media only screen and (max-width: 1087px) {
.newContentNotification {
left: calc(calc(100vw - 720px) / 2 + 48px + 32px);
}
}
@media only screen and (max-width: 720px) {
.newContentNotification {
left: 0;
width: 100%;
justify-content: center;
}
.notificationTabContent {
width: 100%;
}
}

View File

@ -195,8 +195,9 @@ const Thread: Component = () => {
</div>
}>
<div id="primary_note">
<NotePrimary
<Note
note={primaryNote() as PrimalNote}
noteType="primary"
/>
<Show when={account?.hasPublicKey()}>
<ReplyToNote