feat: a link profile image

This commit is contained in:
Kieran 2023-05-10 13:29:07 +01:00
parent 2ed38d1b97
commit ec4e6498d3
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
10 changed files with 97 additions and 127 deletions

View File

@ -19,4 +19,5 @@
border: 1px solid var(--font-tertiary-color); border: 1px solid var(--font-tertiary-color);
margin-top: auto; margin-top: auto;
margin-bottom: auto; margin-bottom: auto;
overflow: hidden;
} }

View File

@ -327,10 +327,9 @@ export default function Note(props: NoteProps) {
{options.showHeader && ( {options.showHeader && (
<div className="header flex"> <div className="header flex">
<ProfileImage <ProfileImage
autoWidth={false}
pubkey={ev.pubkey} pubkey={ev.pubkey}
subHeader={replyTag() ?? undefined} subHeader={replyTag() ?? undefined}
linkToProfile={opt?.canClick === undefined} link={opt?.canClick === undefined ? undefined : ""}
/> />
{(options.showTime || options.showBookmarked) && ( {(options.showTime || options.showBookmarked) && (
<div className="info"> <div className="info">

View File

@ -293,7 +293,7 @@ export function NoteCreator() {
ev.stopPropagation = true; ev.stopPropagation = true;
LoginStore.switchAccount(a); LoginStore.switchAccount(a);
}}> }}>
<ProfileImage pubkey={a} linkToProfile={false} /> <ProfileImage pubkey={a} link={""} />
</MenuItem> </MenuItem>
)); ));
} }

View File

@ -1,6 +1,10 @@
.pfp { .pfp {
display: flex; display: grid;
grid-template-columns: min-content auto;
align-items: center; align-items: center;
text-decoration: none;
user-select: none;
min-width: 0;
} }
.pfp .avatar-wrapper { .pfp .avatar-wrapper {
@ -14,7 +18,7 @@
cursor: pointer; cursor: pointer;
} }
.pfp a { a.pfp {
text-decoration: none; text-decoration: none;
} }
@ -25,6 +29,10 @@
font-weight: 600; font-weight: 600;
} }
.pfp .subheader .about { .pfp .profile-name {
max-width: calc(100vw - 140px); max-width: stretch;
}
.pfp a {
text-decoration: none;
} }

View File

@ -1,7 +1,6 @@
import "./ProfileImage.css"; import "./ProfileImage.css";
import { useMemo } from "react"; import { useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { HexKey, NostrPrefix } from "@snort/nostr"; import { HexKey, NostrPrefix } from "@snort/nostr";
import { useUserProfile } from "Hooks/useUserProfile"; import { useUserProfile } from "Hooks/useUserProfile";
@ -9,7 +8,7 @@ import { hexToBech32, profileLink } from "Util";
import Avatar from "Element/Avatar"; import Avatar from "Element/Avatar";
import Nip05 from "Element/Nip05"; import Nip05 from "Element/Nip05";
import { MetadataCache } from "Cache"; import { MetadataCache } from "Cache";
import usePageWidth from "Hooks/usePageWidth"; import { Link } from "react-router-dom";
export interface ProfileImageProps { export interface ProfileImageProps {
pubkey: HexKey; pubkey: HexKey;
@ -17,10 +16,8 @@ export interface ProfileImageProps {
showUsername?: boolean; showUsername?: boolean;
className?: string; className?: string;
link?: string; link?: string;
autoWidth?: boolean;
defaultNip?: string; defaultNip?: string;
verifyNip?: boolean; verifyNip?: boolean;
linkToProfile?: boolean;
overrideUsername?: string; overrideUsername?: string;
} }
@ -30,50 +27,32 @@ export default function ProfileImage({
showUsername = true, showUsername = true,
className, className,
link, link,
autoWidth = true,
defaultNip, defaultNip,
verifyNip, verifyNip,
linkToProfile = true,
overrideUsername, overrideUsername,
}: ProfileImageProps) { }: ProfileImageProps) {
const navigate = useNavigate();
const user = useUserProfile(pubkey); const user = useUserProfile(pubkey);
const nip05 = defaultNip ? defaultNip : user?.nip05; const nip05 = defaultNip ? defaultNip : user?.nip05;
const width = usePageWidth();
const name = useMemo(() => { const name = useMemo(() => {
return overrideUsername ?? getDisplayName(user, pubkey); return overrideUsername ?? getDisplayName(user, pubkey);
}, [user, pubkey, overrideUsername]); }, [user, pubkey, overrideUsername]);
if (!pubkey && !link) {
link = "#";
}
const onAvatarClick = () => {
if (linkToProfile) {
navigate(link ?? profileLink(pubkey));
}
};
return ( return (
<div className={`pfp f-ellipsis${className ? ` ${className}` : ""}`} onClick={onAvatarClick}> <Link className={`pfp${className ? ` ${className}` : ""}`} to={link === undefined ? profileLink(pubkey) : ""}>
<div className="avatar-wrapper"> <div className="avatar-wrapper">
<Avatar user={user} /> <Avatar user={user} />
</div> </div>
{showUsername && ( {showUsername && (
<div className="profile-name"> <div className="f-ellipsis">
<div className="username"> <div className="username">
<div className="display-name"> <div>{name.trim()}</div>
<div>{name.trim()}</div> {nip05 && <Nip05 nip05={nip05} pubkey={pubkey} verifyNip={verifyNip} />}
{nip05 && <Nip05 nip05={nip05} pubkey={pubkey} verifyNip={verifyNip} />}
</div>
</div>
<div className="subheader" style={{ width: autoWidth ? width - 190 : "" }}>
{subHeader}
</div> </div>
<div className="subheader">{subHeader}</div>
</div> </div>
)} )}
</div> </Link>
); );
} }

View File

@ -1,28 +1,23 @@
.reactions-modal .modal-body { .reactions-modal .modal-body {
padding: 0;
max-width: 586px;
}
.reactions-view {
padding: 24px 32px; padding: 24px 32px;
background-color: #1b1b1b; background-color: #1b1b1b;
border-radius: 16px; border-radius: 16px;
position: relative; position: relative;
min-height: 33vh;
} }
.light .reactions-view { .light .reactions-modal .modal-body {
background-color: var(--note-bg); background-color: var(--note-bg);
} }
@media (max-width: 720px) { @media (max-width: 720px) {
.reactions-view { .reactions-modal .modal-body {
padding: 12px 16px; padding: 12px 16px;
margin-top: -160px;
max-width: calc(100vw - 32px); max-width: calc(100vw - 32px);
} }
} }
.reactions-view .close { .reactions-modal .modal-body .close {
position: absolute; position: absolute;
top: 12px; top: 12px;
right: 16px; right: 16px;
@ -30,18 +25,18 @@
cursor: pointer; cursor: pointer;
} }
.reactions-view .close:hover { .reactions-modal .modal-body .close:hover {
color: var(--font-tertiary-color); color: var(--font-tertiary-color);
} }
.reactions-view .reactions-header { .reactions-modal .modal-body .reactions-header {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
margin-bottom: 32px; margin-bottom: 32px;
} }
.reactions-view .reactions-header h2 { .reactions-modal .modal-body .reactions-header h2 {
margin: 0; margin: 0;
flex-grow: 1; flex-grow: 1;
font-weight: 600; font-weight: 600;
@ -49,26 +44,25 @@
line-height: 19px; line-height: 19px;
} }
.reactions-view .body { .reactions-modal .modal-body .body {
overflow: scroll; overflow: scroll;
height: 320px; height: 40vh;
-ms-overflow-style: none; /* for Internet Explorer, Edge */ -ms-overflow-style: none; /* for Internet Explorer, Edge */
scrollbar-width: none; /* Firefox */ scrollbar-width: none; /* Firefox */
} }
.reactions-view .body::-webkit-scrollbar { .reactions-modal .modal-body .body::-webkit-scrollbar {
display: none; display: none;
} }
.reactions-item { .reactions-item {
display: flex; display: grid;
flex-direction: row; grid-template-columns: 52px auto;
align-items: center; align-items: center;
margin-bottom: 24px; margin-bottom: 24px;
} }
.reactions-item .reaction-icon { .reactions-item .reaction-icon {
width: 52px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -93,12 +87,8 @@
line-height: 17px; line-height: 17px;
} }
.reactions-item .zap-comment {
width: 332px;
}
@media (max-width: 520px) { @media (max-width: 520px) {
.reactions-view .tab.disabled { .reactions-modal .modal-body .tab.disabled {
display: none; display: none;
} }
} }

View File

@ -2,7 +2,6 @@ import "./Reactions.css";
import { useState, useMemo, useEffect } from "react"; import { useState, useMemo, useEffect } from "react";
import { useIntl, FormattedMessage } from "react-intl"; import { useIntl, FormattedMessage } from "react-intl";
import { TaggedRawEvent } from "@snort/nostr"; import { TaggedRawEvent } from "@snort/nostr";
import { formatShort } from "Number"; import { formatShort } from "Number";
@ -75,72 +74,66 @@ const Reactions = ({ show, setShow, positive, negative, reposts, zaps }: Reactio
return show ? ( return show ? (
<Modal className="reactions-modal" onClose={onClose}> <Modal className="reactions-modal" onClose={onClose}>
<div className="reactions-view"> <div className="close" onClick={onClose}>
<div className="close" onClick={onClose}> <Icon name="close" />
<Icon name="close" /> </div>
</div> <div className="reactions-header">
<div className="reactions-header"> <h2>
<h2> <FormattedMessage {...messages.ReactionsCount} values={{ n: total }} />
<FormattedMessage {...messages.ReactionsCount} values={{ n: total }} /> </h2>
</h2> </div>
</div> <Tabs tabs={tabs} tab={tab} setTab={setTab} />
<Tabs tabs={tabs} tab={tab} setTab={setTab} /> <div className="body" key={tab.value}>
<div className="body" key={tab.value}> {tab.value === 0 &&
{tab.value === 0 && likes.map(ev => {
likes.map(ev => { return (
return ( <div key={ev.id} className="reactions-item">
<div key={ev.id} className="reactions-item"> <div className="reaction-icon">{ev.content === "+" ? <Icon name="heart" /> : ev.content}</div>
<div className="reaction-icon">{ev.content === "+" ? <Icon name="heart" /> : ev.content}</div> <ProfileImage pubkey={ev.pubkey} />
<ProfileImage autoWidth={false} pubkey={ev.pubkey} /> </div>
</div> );
); })}
})} {tab.value === 1 &&
{tab.value === 1 && zaps.map(z => {
zaps.map(z => { return (
return ( z.sender && (
z.sender && ( <div key={z.id} className="reactions-item">
<div key={z.id} className="reactions-item"> <div className="zap-reaction-icon">
<div className="zap-reaction-icon"> <Icon name="zap" size={20} />
<Icon name="zap" size={20} /> <span className="zap-amount">{formatShort(z.amount)}</span>
<span className="zap-amount">{formatShort(z.amount)}</span>
</div>
<ProfileImage
autoWidth={false}
pubkey={z.anonZap ? "" : z.sender}
subHeader={
<div className="f-ellipsis zap-comment" title={z.content}>
{z.content}
</div>
}
overrideUsername={z.anonZap ? formatMessage({ defaultMessage: "Anonymous" }) : undefined}
/>
</div> </div>
) <ProfileImage
); pubkey={z.anonZap ? "" : z.sender}
})} subHeader={<div title={z.content}>{z.content}</div>}
{tab.value === 2 && link={z.anonZap ? "" : undefined}
reposts.map(ev => { overrideUsername={z.anonZap ? formatMessage({ defaultMessage: "Anonymous" }) : undefined}
return ( />
<div key={ev.id} className="reactions-item">
<div className="reaction-icon">
<Icon name="repost" size={16} />
</div>
<ProfileImage autoWidth={false} pubkey={ev.pubkey} />
</div> </div>
); )
})} );
{tab.value === 3 && })}
dislikes.map(ev => { {tab.value === 2 &&
return ( reposts.map(ev => {
<div key={ev.id} className="reactions-item"> return (
<div className="reaction-icon"> <div key={ev.id} className="reactions-item">
<Icon name="dislike" /> <div className="reaction-icon">
</div> <Icon name="repost" size={16} />
<ProfileImage autoWidth={false} pubkey={ev.pubkey} />
</div> </div>
); <ProfileImage pubkey={ev.pubkey} />
})} </div>
</div> );
})}
{tab.value === 3 &&
dislikes.map(ev => {
return (
<div key={ev.id} className="reactions-item f-ellipsis">
<div className="reaction-icon">
<Icon name="dislike" />
</div>
<ProfileImage pubkey={ev.pubkey} />
</div>
);
})}
</div> </div>
</Modal> </Modal>
) : null; ) : null;

View File

@ -119,7 +119,7 @@ const Timeline = (props: TimelineProps) => {
<> <>
<div className="card latest-notes pointer" onClick={() => onShowLatest()} ref={ref}> <div className="card latest-notes pointer" onClick={() => onShowLatest()} ref={ref}>
{latestAuthors.slice(0, 3).map(p => { {latestAuthors.slice(0, 3).map(p => {
return <ProfileImage pubkey={p} showUsername={false} linkToProfile={false} />; return <ProfileImage pubkey={p} showUsername={false} link={""} />;
})} })}
<FormattedMessage <FormattedMessage
defaultMessage="{n} new {n, plural, =1 {note} other {notes}}" defaultMessage="{n} new {n, plural, =1 {note} other {notes}}"
@ -130,7 +130,7 @@ const Timeline = (props: TimelineProps) => {
{!inView && ( {!inView && (
<div className="card latest-notes latest-notes-fixed pointer fade-in" onClick={() => onShowLatest(true)}> <div className="card latest-notes latest-notes-fixed pointer fade-in" onClick={() => onShowLatest(true)}>
{latestAuthors.slice(0, 3).map(p => { {latestAuthors.slice(0, 3).map(p => {
return <ProfileImage pubkey={p} showUsername={false} linkToProfile={false} />; return <ProfileImage pubkey={p} showUsername={false} link={""} />;
})} })}
<FormattedMessage <FormattedMessage
defaultMessage="{n} new {n, plural, =1 {note} other {notes}}" defaultMessage="{n} new {n, plural, =1 {note} other {notes}}"

View File

@ -107,8 +107,8 @@ const Zap = ({ zap, showZapped = true }: { zap: ParsedZap; showZapped?: boolean
return valid && sender ? ( return valid && sender ? (
<div className="zap note card"> <div className="zap note card">
<div className="header"> <div className="header">
<ProfileImage autoWidth={false} pubkey={sender} /> <ProfileImage pubkey={sender} />
{receiver !== pubKey && showZapped && <ProfileImage autoWidth={false} pubkey={unwrap(receiver)} />} {receiver !== pubKey && showZapped && <ProfileImage pubkey={unwrap(receiver)} />}
<div className="amount"> <div className="amount">
<span className="amount-number"> <span className="amount-number">
<FormattedMessage {...messages.Sats} values={{ n: formatShort(amount ?? 0) }} /> <FormattedMessage {...messages.Sats} values={{ n: formatShort(amount ?? 0) }} />
@ -151,7 +151,6 @@ export const ZapsSummary = ({ zaps }: ZapsSummaryProps) => {
<div className="summary"> <div className="summary">
{sender && ( {sender && (
<ProfileImage <ProfileImage
autoWidth={false}
pubkey={anonZap ? "" : sender} pubkey={anonZap ? "" : sender}
overrideUsername={anonZap ? formatMessage({ defaultMessage: "Anonymous" }) : undefined} overrideUsername={anonZap ? formatMessage({ defaultMessage: "Anonymous" }) : undefined}
/> />

View File

@ -42,6 +42,7 @@
html { html {
scroll-behavior: smooth; scroll-behavior: smooth;
-webkit-tap-highlight-color: transparent;
} }
html.light { html.light {