feat: a link profile image
This commit is contained in:
parent
2ed38d1b97
commit
ec4e6498d3
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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">
|
||||||
|
@ -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>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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}}"
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
|
|
||||||
html {
|
html {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.light {
|
html.light {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user