Redesigned Primary note

This commit is contained in:
Bojan Mojsilovic 2024-04-16 14:37:31 +02:00
parent 20b4bf76da
commit 33e7efdc02
14 changed files with 102 additions and 56 deletions

View File

@ -7,6 +7,11 @@
background-color: var(--text-tertiary); background-color: var(--text-tertiary);
-webkit-mask: url(../../assets/icons/bookmark_empty.svg) no-repeat 0 / 16px; -webkit-mask: url(../../assets/icons/bookmark_empty.svg) no-repeat 0 / 16px;
mask: url(../../assets/icons/bookmark_empty.svg) no-repeat 0 / 16px; mask: url(../../assets/icons/bookmark_empty.svg) no-repeat 0 / 16px;
&.large {
width: 20px;
height: 20px;
}
} }
.fullBookmark { .fullBookmark {
@ -17,6 +22,11 @@
background-color: var(--active-bookmarked); background-color: var(--active-bookmarked);
-webkit-mask: url(../../assets/icons/bookmark_filled.svg) no-repeat 0 / 16px; -webkit-mask: url(../../assets/icons/bookmark_filled.svg) no-repeat 0 / 16px;
mask: url(../../assets/icons/bookmark_filled.svg) no-repeat 0 / 16px; mask: url(../../assets/icons/bookmark_filled.svg) no-repeat 0 / 16px;
&.large {
width: 20px;
height: 20px;
}
} }
&:hover { &:hover {

View File

@ -16,7 +16,7 @@ import styles from './BookmarkNote.module.scss';
import { saveBookmarks } from '../../lib/localStore'; import { saveBookmarks } from '../../lib/localStore';
import { importEvents, triggerImportEvents } from '../../lib/notes'; import { importEvents, triggerImportEvents } from '../../lib/notes';
const BookmarkNote: Component<{ note: PrimalNote }> = (props) => { const BookmarkNote: Component<{ note: PrimalNote, large?: boolean }> = (props) => {
const account = useAccountContext(); const account = useAccountContext();
const app = useAppContext(); const app = useAppContext();
const intl = useIntl(); const intl = useIntl();
@ -147,10 +147,10 @@ const BookmarkNote: Component<{ note: PrimalNote }> = (props) => {
<Show <Show
when={isBookmarked()} when={isBookmarked()}
fallback={ fallback={
<div class={styles.emptyBookmark}></div> <div class={`${styles.emptyBookmark} ${props.large ? styles.large : ''}`}></div>
} }
> >
<div class={styles.fullBookmark}></div> <div class={`${styles.fullBookmark} ${props.large ? styles.large : ''}`}></div>
</Show> </Show>
</ButtonGhost> </ButtonGhost>
</div> </div>

View File

@ -245,8 +245,9 @@
background-color: var(--background-card); background-color: var(--background-card);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 12px; padding-inline: 12px;
padding-top: 0; padding-top: 0;
padding-bottom: 12px;
border-radius: 0; border-radius: 0;
border: none; border: none;
@ -286,6 +287,29 @@
} }
} }
.time {
padding-block: 20px;
border-bottom: 1px solid var(--devider);
margin-bottom: 16px;
color: var(--text-tertiary);
font-size: 16px;
font-weight: 400;
line-height: 16px;
.reactSummary {
&::before {
content: ' · ';
}
.number {
color: var(--text-primary);
font-size: 16px;
font-weight: 600;
line-height: 16px;
}
}
}
} }
} }

View File

@ -16,6 +16,7 @@ import NoteHeader from './NoteHeader/NoteHeader';
import { createStore } from 'solid-js/store'; import { createStore } from 'solid-js/store';
import { CustomZapInfo, useAppContext } from '../../contexts/AppContext'; import { CustomZapInfo, useAppContext } from '../../contexts/AppContext';
import NoteContextTrigger from './NoteContextTrigger'; import NoteContextTrigger from './NoteContextTrigger';
import { date, longDate, veryLongDate } from '../../lib/dates';
export type NoteFooterState = { export type NoteFooterState = {
likes: number, likes: number,
@ -146,6 +147,12 @@ const Note: Component<{
); );
} }
const reactionSum = () => {
const { likes, zaps, reposts } = props.note.post;
return (likes || 0) + (zaps || 0) + (reposts || 0);
};
return ( return (
<Switch> <Switch>
<Match when={noteType() === 'notification'}> <Match when={noteType() === 'notification'}>
@ -200,12 +207,25 @@ const Note: Component<{
<ParsedNote note={props.note} width={Math.min(574, window.innerWidth)} /> <ParsedNote note={props.note} width={Math.min(574, window.innerWidth)} />
</div> </div>
<div
class={styles.time}
title={date(props.note.post?.created_at).date.toLocaleString()}
>
<span>
{veryLongDate(props.note.post?.created_at).replace('at', '·')}
</span>
<span class={styles.reactSummary}>
<span class={styles.number}>{reactionSum()}</span> Reactions
</span>
</div>
<NoteFooter <NoteFooter
note={props.note} note={props.note}
state={footerState} state={footerState}
updateState={updateFooterState} updateState={updateFooterState}
customZapInfo={customZapInfo} customZapInfo={customZapInfo}
wide={true} wide={true}
large={true}
/> />
</div> </div>
</div> </div>

View File

@ -1,18 +1,11 @@
import { A } from '@solidjs/router'; import { Component, Show } from 'solid-js';
import { Component, createSignal, Show } from 'solid-js'; import { PrimalUser } from '../../types/primal';
import { PrimalNote, PrimalUser } from '../../types/primal';
import ParsedNote from '../ParsedNote/ParsedNote';
import NoteFooter from './NoteFooter/NoteFooter';
import NoteHeader from './NoteHeader/NoteHeader';
import styles from './Note.module.scss'; import styles from './Note.module.scss';
import { useThreadContext } from '../../contexts/ThreadContext'; import { useThreadContext } from '../../contexts/ThreadContext';
import { useIntl } from '@cookbook/solid-intl'; import { useIntl } from '@cookbook/solid-intl';
import { authorName, nip05Verification, truncateNpub } from '../../stores/profile'; import { authorName, nip05Verification } from '../../stores/profile';
import { note as t } from '../../translations';
import { hookForDev } from '../../lib/devTools'; import { hookForDev } from '../../lib/devTools';
import NoteReplyHeader from './NoteHeader/NoteReplyHeader';
import Avatar from '../Avatar/Avatar';
import { date } from '../../lib/dates'; import { date } from '../../lib/dates';
import VerificationCheck from '../VerificationCheck/VerificationCheck'; import VerificationCheck from '../VerificationCheck/VerificationCheck';

View File

@ -1,27 +1,17 @@
import { A } from '@solidjs/router'; import { Component, createEffect, createSignal } from 'solid-js';
import { Component, createEffect, createSignal, Show } from 'solid-js'; import { MenuItem, NostrRelaySignedEvent } from '../../types/primal';
import { MenuItem, NostrRelaySignedEvent, PrimalNote, PrimalRepost, PrimalUser } from '../../types/primal';
import ParsedNote from '../ParsedNote/ParsedNote';
import NoteFooter from './NoteFooter/NoteFooter';
import NoteHeader from './NoteHeader/NoteHeader';
import styles from './Note.module.scss'; import styles from './Note.module.scss';
import { useThreadContext } from '../../contexts/ThreadContext';
import { useIntl } from '@cookbook/solid-intl'; import { useIntl } from '@cookbook/solid-intl';
import { authorName, nip05Verification, truncateNpub, userName } from '../../stores/profile'; import { authorName, userName } from '../../stores/profile';
import { note as t, actions as tActions, toast as tToast } from '../../translations'; import { note as t, actions as tActions, toast as tToast } from '../../translations';
import { hookForDev } from '../../lib/devTools'; import { hookForDev } from '../../lib/devTools';
import NoteReplyHeader from './NoteHeader/NoteReplyHeader';
import Avatar from '../Avatar/Avatar';
import { date } from '../../lib/dates';
import VerificationCheck from '../VerificationCheck/VerificationCheck';
import PrimalMenu from '../PrimalMenu/PrimalMenu'; import PrimalMenu from '../PrimalMenu/PrimalMenu';
import { useAccountContext } from '../../contexts/AccountContext'; import { useAccountContext } from '../../contexts/AccountContext';
import { APP_ID } from '../../App'; import { APP_ID } from '../../App';
import { reportUser } from '../../lib/profile'; import { reportUser } from '../../lib/profile';
import { useToastContext } from '../Toaster/Toaster'; import { useToastContext } from '../Toaster/Toaster';
import { broadcastEvent } from '../../lib/notes'; import { broadcastEvent } from '../../lib/notes';
import { getScreenCordinates } from '../../utils';
import { NoteContextMenuInfo } from '../../contexts/AppContext'; import { NoteContextMenuInfo } from '../../contexts/AppContext';
import ConfirmModal from '../ConfirmModal/ConfirmModal'; import ConfirmModal from '../ConfirmModal/ConfirmModal';

View File

@ -2,11 +2,23 @@
width: 16px; width: 16px;
height: 16px; height: 16px;
background-color: var(--text-tertiary-2); background-color: var(--text-tertiary-2);
&.large {
width: 20px;
height: 20px;
}
} }
@mixin typeDiv { @mixin typeDiv {
display: flex; display: flex;
align-items: center; align-items: center;
font-weight: 400;
font-size: 14px;
line-height: 16px;
&.large {
font-size: 16px;
}
} }
.contextButton { .contextButton {
@ -59,9 +71,6 @@
} }
.stat { .stat {
font-weight: 400;
font-size: 14px;
line-height: 16px;
align-items: center; align-items: center;
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;

View File

@ -30,6 +30,7 @@ const NoteFooter: Component<{
state: NoteFooterState, state: NoteFooterState,
updateState: SetStoreFunction<NoteFooterState>, updateState: SetStoreFunction<NoteFooterState>,
customZapInfo: CustomZapInfo, customZapInfo: CustomZapInfo,
large?: boolean,
}> = (props) => { }> = (props) => {
const account = useAccountContext(); const account = useAccountContext();
@ -321,6 +322,7 @@ const NoteFooter: Component<{
highlighted={props.state.replied} highlighted={props.state.replied}
label={props.state.replies === 0 ? '' : truncateNumber(props.state.replies, 2)} label={props.state.replies === 0 ? '' : truncateNumber(props.state.replies, 2)}
title={props.state.replies.toLocaleString()} title={props.state.replies.toLocaleString()}
large={props.large}
/> />
<NoteFooterActionButton <NoteFooterActionButton
@ -335,6 +337,7 @@ const NoteFooter: Component<{
label={props.state.satsZapped === 0 ? '' : truncateNumber(props.state.satsZapped, 2)} label={props.state.satsZapped === 0 ? '' : truncateNumber(props.state.satsZapped, 2)}
hidden={props.state.hideZapIcon} hidden={props.state.hideZapIcon}
title={props.state.satsZapped.toLocaleString()} title={props.state.satsZapped.toLocaleString()}
large={props.large}
/> />
<NoteFooterActionButton <NoteFooterActionButton
@ -344,6 +347,7 @@ const NoteFooter: Component<{
highlighted={props.state.liked} highlighted={props.state.liked}
label={props.state.likes === 0 ? '' : truncateNumber(props.state.likes, 2)} label={props.state.likes === 0 ? '' : truncateNumber(props.state.likes, 2)}
title={props.state.likes.toLocaleString()} title={props.state.likes.toLocaleString()}
large={props.large}
/> />
<button <button
@ -357,7 +361,7 @@ const NoteFooter: Component<{
ref={repostMenu} ref={repostMenu}
> >
<div <div
class={styles.icon} class={`${styles.icon} ${props.large ? styles.large : ''}`}
style={'visibility: visible'} style={'visibility: visible'}
></div> ></div>
<div class={styles.statNumber}> <div class={styles.statNumber}>
@ -373,7 +377,10 @@ const NoteFooter: Component<{
</div> </div>
</button> </button>
<BookmarkNote note={props.note} /> <BookmarkNote
note={props.note}
large={props.large}
/>
</div> </div>
) )

View File

@ -23,6 +23,7 @@ const NoteFooterActionButton: Component<{
label: string | number, label: string | number,
hidden?: boolean, hidden?: boolean,
title?: string, title?: string,
large?: boolean,
}> = (props) => { }> = (props) => {
return ( return (
@ -36,9 +37,9 @@ const NoteFooterActionButton: Component<{
onTouchEnd={props.onTouchEnd ?? (() => {})} onTouchEnd={props.onTouchEnd ?? (() => {})}
disabled={props.disabled} disabled={props.disabled}
> >
<div class={`${buttonTypeClasses[props.type]}`}> <div class={`${buttonTypeClasses[props.type]} ${props.large ? styles.large : ''}`}>
<div <div
class={styles.icon} class={`${styles.icon} ${props.large ? styles.large : ''}`}
style={props.hidden ? 'visibility: hidden': 'visibility: visible'} style={props.hidden ? 'visibility: hidden': 'visibility: visible'}
></div> ></div>
<div class={styles.statNumber}>{props.label || ''}</div> <div class={styles.statNumber}>{props.label || ''}</div>

View File

@ -222,17 +222,7 @@ const NoteHeader: Component<{
<VerificationCheck <VerificationCheck
user={props.note.user} user={props.note.user}
fallback={<div class={styles.ellipsisIcon}></div>}
/> />
<span
class={styles.time}
title={date(props.note.post?.created_at).date.toLocaleString()}
>
{props.primary ?
longDate(props.note.post?.created_at) :
date(props.note.post?.created_at).label}
</span>
</div> </div>
<Show <Show
when={props.note.user?.nip05} when={props.note.user?.nip05}

View File

@ -1,14 +1,9 @@
import { A } from '@solidjs/router'; import { Component, createMemo, Show } from 'solid-js';
import { Component, createMemo, createSignal, Show } from 'solid-js'; import { PrimalNote } from '../../types/primal';
import { PrimalNote, PrimalRepost, PrimalUser } from '../../types/primal';
import ParsedNote from '../ParsedNote/ParsedNote';
import NoteFooter from './NoteFooter/NoteFooter';
import NoteHeader from './NoteHeader/NoteHeader';
import styles from './Note.module.scss'; import styles from './Note.module.scss';
import { useThreadContext } from '../../contexts/ThreadContext';
import { useIntl } from '@cookbook/solid-intl'; import { useIntl } from '@cookbook/solid-intl';
import { authorName, nip05Verification, truncateNpub, userName } from '../../stores/profile';
import { note as t } from '../../translations'; import { note as t } from '../../translations';
import { hookForDev } from '../../lib/devTools'; import { hookForDev } from '../../lib/devTools';
import MentionedUserLink from './MentionedUserLink/MentionedUserLink'; import MentionedUserLink from './MentionedUserLink/MentionedUserLink';

View File

@ -86,7 +86,7 @@
padding: 12px; padding: 12px;
background-color: var(--background-card); background-color: var(--background-card);
border: none; border: none;
border-bottom: 1px solid var(--devider); border-block: 1px solid var(--devider);
border-radius: 0; border-radius: 0;
outline: none; outline: none;
display: flex; display: flex;

View File

@ -18,6 +18,16 @@ export const longDate = (timestamp: number | undefined) => {
return dtf.format(date); return dtf.format(date);
}; };
export const veryLongDate = (timestamp: number | undefined) => {
if (!timestamp || timestamp < 0) {
return '';
}
const date = new Date(timestamp * 1000);
const dtf = new Intl.DateTimeFormat('en-US', { dateStyle: 'full', timeStyle: 'short'});
return dtf.format(date);
};
export const date = (postTimestamp: number, style: Intl.RelativeTimeFormatStyle = 'short', since?: number) => { export const date = (postTimestamp: number, style: Intl.RelativeTimeFormatStyle = 'short', since?: number) => {
const today = since ?? Math.floor((new Date()).getTime() / 1000); const today = since ?? Math.floor((new Date()).getTime() / 1000);
const date = new Date(postTimestamp * 1000); const date = new Date(postTimestamp * 1000);

View File

@ -3,9 +3,7 @@ import Note from '../components/Note/Note';
import styles from './Thread.module.scss'; import styles from './Thread.module.scss';
import { useNavigate, useParams } from '@solidjs/router'; import { useNavigate, useParams } from '@solidjs/router';
import { PrimalNote, SendNoteResult } from '../types/primal'; import { PrimalNote, SendNoteResult } from '../types/primal';
import NotePrimary from '../components/Note/NotePrimary/NotePrimary';
import PeopleList from '../components/PeopleList/PeopleList'; import PeopleList from '../components/PeopleList/PeopleList';
import PageNav from '../components/PageNav/PageNav';
import ReplyToNote from '../components/ReplyToNote/ReplyToNote'; import ReplyToNote from '../components/ReplyToNote/ReplyToNote';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
@ -13,7 +11,6 @@ import { useThreadContext } from '../contexts/ThreadContext';
import Wormhole from '../components/Wormhole/Wormhole'; import Wormhole from '../components/Wormhole/Wormhole';
import { useAccountContext } from '../contexts/AccountContext'; import { useAccountContext } from '../contexts/AccountContext';
import { sortByRecency } from '../stores/note'; import { sortByRecency } from '../stores/note';
import { scrollWindowTo } from '../lib/scroll';
import { useIntl } from '@cookbook/solid-intl'; import { useIntl } from '@cookbook/solid-intl';
import Search from '../components/Search/Search'; import Search from '../components/Search/Search';
import { placeholders as tPlaceholders, thread as t } from '../translations'; import { placeholders as tPlaceholders, thread as t } from '../translations';