Profile / Thread styles
This commit is contained in:
@ -16,7 +16,7 @@ export default function Copy({ text, maxSize = 32, className }: CopyProps) {
|
||||
<div className={`flex flex-row copy ${className}`} onClick={() => copy(text)}>
|
||||
<span className="body">{trimmed}</span>
|
||||
<span className="icon" style={{ color: copied ? "var(--success)" : "var(--highlight)" }}>
|
||||
{copied ? <Icon name="check" size={14} /> : <Icon name="copy" size={14} />}
|
||||
{copied ? <Icon name="check" size={14} /> : <Icon name="copy-solid" size={14} />}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
@ -13,7 +13,7 @@ export function LiveEvent({ ev }: { ev: NostrEvent }) {
|
||||
<h3>{title}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<Link to={`/live/${encodeTLV(NostrPrefix.Address, d, undefined, ev.kind, ev.pubkey)}`}>
|
||||
<Link to={`https://zap.stream/${encodeTLV(NostrPrefix.Address, d, undefined, ev.kind, ev.pubkey)}`}>
|
||||
<button className="primary" type="button">
|
||||
<FormattedMessage defaultMessage="Watch Live!" />
|
||||
</button>
|
||||
|
@ -29,7 +29,7 @@ const Nip05 = ({ nip05, pubkey, verifyNip = true }: Nip05Params) => {
|
||||
<span className="domain" data-domain={domain?.toLowerCase()}>
|
||||
{domain}
|
||||
</span>
|
||||
<Icon name="badge" className="badge" size={16} />
|
||||
<Icon name="check-verified" className="badge" size={16} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
min-height: 110px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.note:hover {
|
||||
@ -185,7 +185,6 @@
|
||||
|
||||
.note.active {
|
||||
border-left: 1px solid var(--highlight);
|
||||
border-bottom-left-radius: 0;
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
|
@ -191,7 +191,7 @@ export default function NoteFooter(props: NoteFooterProps) {
|
||||
function repostIcon() {
|
||||
return (
|
||||
<div className={`reaction-pill ${hasReposted() ? "reacted" : ""}`} onClick={() => repost()}>
|
||||
<Icon name="repost" size={17} />
|
||||
<Icon name="repeat" size={18} />
|
||||
{reposts.length > 0 && <div className="reaction-pill-number">{formatShort(reposts.length)}</div>}
|
||||
</div>
|
||||
);
|
||||
@ -201,12 +201,11 @@ export default function NoteFooter(props: NoteFooterProps) {
|
||||
if (!prefs.enableReactions) {
|
||||
return null;
|
||||
}
|
||||
const reacted = hasReacted("+");
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`reaction-pill ${hasReacted("+") ? "reacted" : ""} `}
|
||||
onClick={() => react(prefs.reactionEmoji)}>
|
||||
<Icon name="heart" />
|
||||
<div className={`reaction-pill ${reacted ? "reacted" : ""} `} onClick={() => react(prefs.reactionEmoji)}>
|
||||
<Icon name={reacted ? "heart-solid" : "heart"} size={18} />
|
||||
<div className="reaction-pill-number">{formatShort(positive.length)}</div>
|
||||
</div>
|
||||
</>
|
||||
|
@ -1,23 +1,14 @@
|
||||
.reaction {
|
||||
}
|
||||
|
||||
.reaction > .note {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.reaction > .header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.reaction > .header .reply {
|
||||
font-size: var(--font-size-small);
|
||||
.reaction > div:nth-child(1) {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.reaction > .header > .info {
|
||||
font-size: var(--font-size);
|
||||
white-space: nowrap;
|
||||
color: var(--font-secondary-color);
|
||||
margin-right: 24px;
|
||||
.reaction > div:nth-child(1) svg {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
import "./NoteReaction.css";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useMemo } from "react";
|
||||
import { EventKind, NostrEvent, TaggedNostrEvent, NostrPrefix, EventExt } from "@snort/system";
|
||||
import { EventKind, NostrEvent, TaggedNostrEvent, NostrPrefix } from "@snort/system";
|
||||
|
||||
import Note from "Element/Note";
|
||||
import ProfileImage from "Element/ProfileImage";
|
||||
import { getDisplayName } from "Element/ProfileImage";
|
||||
import { eventLink, hexToBech32 } from "SnortUtils";
|
||||
import NoteTime from "Element/NoteTime";
|
||||
import useModeration from "Hooks/useModeration";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import Icon from "Icons/Icon";
|
||||
import { useUserProfile } from "@snort/system-react";
|
||||
import { System } from "index";
|
||||
|
||||
export interface NoteReactionProps {
|
||||
data: TaggedNostrEvent;
|
||||
@ -16,6 +19,7 @@ export interface NoteReactionProps {
|
||||
export default function NoteReaction(props: NoteReactionProps) {
|
||||
const { data: ev } = props;
|
||||
const { isMuted } = useModeration();
|
||||
const profile = useUserProfile(System, ev.pubkey);
|
||||
|
||||
const refEvent = useMemo(() => {
|
||||
if (ev) {
|
||||
@ -60,12 +64,15 @@ export default function NoteReaction(props: NoteReactionProps) {
|
||||
};
|
||||
|
||||
return shouldNotBeRendered ? null : (
|
||||
<div className="reaction">
|
||||
<div className="header flex">
|
||||
<ProfileImage pubkey={EventExt.getRootPubKey(ev)} />
|
||||
<div className="info">
|
||||
<NoteTime from={ev.created_at * 1000} />
|
||||
</div>
|
||||
<div className="card reaction">
|
||||
<div className="flex g4">
|
||||
<Icon name="repeat" size={18} />
|
||||
<FormattedMessage
|
||||
defaultMessage="{name} reposted"
|
||||
values={{
|
||||
name: getDisplayName(profile, ev.pubkey),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{root ? <Note data={root} options={opt} related={[]} /> : null}
|
||||
{!root && refEvent ? (
|
||||
|
@ -50,8 +50,7 @@ export default function ProfileImage({
|
||||
<Link
|
||||
className={`pfp${className ? ` ${className}` : ""}`}
|
||||
to={link === undefined ? profileLink(pubkey) : link}
|
||||
onClick={handleClick}
|
||||
replace={true}>
|
||||
onClick={handleClick}>
|
||||
<div className="avatar-wrapper">
|
||||
<Avatar user={user} />
|
||||
</div>
|
||||
|
@ -5,8 +5,9 @@
|
||||
overflow-x: scroll;
|
||||
-ms-overflow-style: none; /* for Internet Explorer, Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
margin-bottom: 18px;
|
||||
white-space: nowrap;
|
||||
gap: 8px;
|
||||
padding: 16px 12px;
|
||||
}
|
||||
|
||||
.tabs::-webkit-scrollbar {
|
||||
@ -14,23 +15,21 @@
|
||||
}
|
||||
|
||||
.tab {
|
||||
background: var(--gray-ultradark);
|
||||
color: var(--font-tertiary-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 16px;
|
||||
border-radius: 100px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
padding: 6px 12px;
|
||||
text-align: center;
|
||||
font-feature-settings: "tnum";
|
||||
}
|
||||
|
||||
.tab:not(:last-of-type) {
|
||||
margin-right: 8px;
|
||||
font-size: 16px;
|
||||
padding: 10px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
border-color: var(--font-color);
|
||||
color: var(--font-color);
|
||||
color: black;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.tabs > div {
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { ReactNode } from "react";
|
||||
import "./Tabs.css";
|
||||
import useHorizontalScroll from "Hooks/useHorizontalScroll";
|
||||
|
||||
export interface Tab {
|
||||
text: string;
|
||||
text: ReactNode;
|
||||
value: number;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
@ -1,7 +1,3 @@
|
||||
.thread-container {
|
||||
margin: 12px 0 150px 0;
|
||||
}
|
||||
|
||||
.thread-container .hidden-note {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
@ -11,11 +7,6 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.thread-root.note > .body {
|
||||
margin-top: 8px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.thread-root.note > .body .text {
|
||||
font-size: 19px;
|
||||
}
|
||||
@ -31,12 +22,13 @@
|
||||
}
|
||||
|
||||
.thread-note.note {
|
||||
border-radius: 0;
|
||||
margin-bottom: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.light .thread-note.note.card {
|
||||
box-shadow: none;
|
||||
.thread-note.note .zaps-summary,
|
||||
.thread-note.note .footer,
|
||||
.thread-note.note .body {
|
||||
margin-left: 61px;
|
||||
}
|
||||
|
||||
.thread-container .hidden-note {
|
||||
@ -58,83 +50,47 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.line-container {
|
||||
background: var(--note-bg);
|
||||
}
|
||||
|
||||
.subthread-container.subthread-multi .line-container:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 36px;
|
||||
left: calc(48px / 2 + 16px);
|
||||
top: 48px;
|
||||
border-left: 1px solid var(--gray-superdark);
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
@media (min-width: 720px) {
|
||||
.subthread-container.subthread-multi .line-container:before {
|
||||
left: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.subthread-container.subthread-mid:not(.subthread-last) .line-container:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 36px;
|
||||
top: 48px;
|
||||
border-left: 1px solid var(--gray-superdark);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 720px) {
|
||||
.subthread-container.subthread-mid:not(.subthread-last) .line-container:after {
|
||||
left: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.subthread-container.subthread-mid:not(.subthread-last) .line-container:after {
|
||||
.subthread-container.subthread-mid:not(.subthread-last) .line-container:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-left: 1px solid var(--gray-superdark);
|
||||
left: 36px;
|
||||
left: calc(48px / 2 + 16px);
|
||||
top: 0;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
@media (min-width: 720px) {
|
||||
.subthread-container.subthread-mid:not(.subthread-last) .line-container:after {
|
||||
left: 48px;
|
||||
}
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.subthread-container.subthread-last .line-container:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-left: 1px solid var(--gray-superdark);
|
||||
left: 36px;
|
||||
left: calc(48px / 2 + 16px);
|
||||
top: 0;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
@media (min-width: 720px) {
|
||||
.subthread-container.subthread-last .line-container:before {
|
||||
left: 48px;
|
||||
}
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.divider-container {
|
||||
background: var(--note-bg);
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: var(--gray-superdark);
|
||||
margin-left: 28px;
|
||||
margin-right: 22px;
|
||||
}
|
||||
|
||||
.divider.divider-small {
|
||||
margin-left: 80px;
|
||||
margin-left: calc(16px + 61px);
|
||||
}
|
||||
|
||||
.thread-container .collapsed,
|
||||
@ -143,11 +99,6 @@
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
.thread-note.is-last-note {
|
||||
border-bottom-left-radius: 16px;
|
||||
border-bottom-right-radius: 16px;
|
||||
}
|
||||
|
||||
.thread-container .collapsed {
|
||||
background-color: var(--note-bg);
|
||||
}
|
||||
@ -155,13 +106,3 @@
|
||||
.thread-container .hidden-note {
|
||||
padding-left: 48px;
|
||||
}
|
||||
|
||||
.thread-root.thread-root-single.note {
|
||||
border-bottom-left-radius: 16px;
|
||||
border-bottom-right-radius: 16px;
|
||||
}
|
||||
|
||||
.thread-root.ghost-root {
|
||||
border-top-left-radius: 16px;
|
||||
border-top-right-radius: 16px;
|
||||
}
|
||||
|
@ -374,9 +374,11 @@ export default function Thread() {
|
||||
description: "Navigate back button on threads view",
|
||||
});
|
||||
return (
|
||||
<div className="main-content mt10">
|
||||
<BackButton onClick={goBack} text={parent ? parentText : backText} />
|
||||
<div className="thread-container">
|
||||
<>
|
||||
<div className="main-content">
|
||||
<BackButton onClick={goBack} text={parent ? parentText : backText} />
|
||||
</div>
|
||||
<div className="main-content">
|
||||
{root && renderRoot(root)}
|
||||
{root && renderChain(root.id)}
|
||||
|
||||
@ -392,7 +394,7 @@ export default function Thread() {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ export default function Layout() {
|
||||
};
|
||||
|
||||
const shouldHideNoteCreator = useMemo(() => {
|
||||
const hideOn = ["/settings", "/messages", "/new", "/login", "/donate", "/p/", "/e", "/subscribe", "/live"];
|
||||
const hideOn = ["/settings", "/messages", "/new", "/login", "/donate", "/p/", "/e", "/subscribe"];
|
||||
return isReplyNoteCreatorShowing || hideOn.some(a => location.pathname.startsWith(a));
|
||||
}, [location, isReplyNoteCreatorShowing]);
|
||||
|
||||
@ -50,8 +50,8 @@ export default function Layout() {
|
||||
}, [location]);
|
||||
|
||||
useEffect(() => {
|
||||
const widePage = ["/login", "/messages", "/live"];
|
||||
const noScroll = ["/messages", "/live"];
|
||||
const widePage = ["/login", "/messages"];
|
||||
const noScroll = ["/messages"];
|
||||
if (widePage.some(a => location.pathname.startsWith(a))) {
|
||||
setPageClass(noScroll.some(a => location.pathname.startsWith(a)) ? "scroll-lock" : "");
|
||||
} else {
|
||||
|
@ -2,28 +2,21 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
border: 1px solid var(--gray-superdark);
|
||||
}
|
||||
|
||||
.profile .banner {
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
object-fit: cover;
|
||||
margin-bottom: -60px;
|
||||
margin-bottom: -37px;
|
||||
z-index: 0;
|
||||
}
|
||||
@media (min-width: 720px) {
|
||||
.profile .banner {
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.profile .profile-actions {
|
||||
position: absolute;
|
||||
top: 72px;
|
||||
right: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.profile .icon-actions {
|
||||
@ -52,13 +45,13 @@
|
||||
}
|
||||
|
||||
.profile .profile-wrapper {
|
||||
margin: 0 16px;
|
||||
width: calc(100% - 32px);
|
||||
margin: 0 16px 12px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.profile p {
|
||||
@ -66,21 +59,26 @@
|
||||
}
|
||||
|
||||
.details-wrapper > .name > h2 {
|
||||
margin: 12px 0 0 0;
|
||||
margin: 0 0 4px 0;
|
||||
font-weight: 600;
|
||||
font-size: 19px;
|
||||
line-height: 23px;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
.details-wrapper > .name > .nip05 {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.profile-wrapper > .avatar-wrapper {
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.profile-wrapper > .avatar-wrapper .avatar {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-image: var(--img-url);
|
||||
border: 3px solid var(--bg-color);
|
||||
border: 3px solid #fff;
|
||||
}
|
||||
|
||||
.profile .name {
|
||||
@ -88,28 +86,30 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.profile .details {
|
||||
width: 100%;
|
||||
.profile .about {
|
||||
color: var(--font-secondary-color);
|
||||
margin-bottom: 12px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
font-size: 16px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.profile .details p {
|
||||
.profile .about p {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.profile .details a {
|
||||
.profile .about a {
|
||||
color: var(--highlight);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.profile .details a:hover {
|
||||
.profile .about a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.profile .about .text {
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.profile .btn-icon {
|
||||
color: var(--font-color);
|
||||
padding: 6px;
|
||||
@ -118,54 +118,36 @@
|
||||
.profile .details-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: calc(100% - 32px);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.profile .details .text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.profile .links {
|
||||
font-size: 14px;
|
||||
margin-top: 4px;
|
||||
margin-left: 2px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.profile .website {
|
||||
margin: 4px 0;
|
||||
.profile .link-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.profile .link {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.profile .lnurl {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.profile .website a {
|
||||
color: var(--font-color);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.profile .website a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.profile .website a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.profile .lnurl {
|
||||
cursor: pointer;
|
||||
.profile .link svg {
|
||||
color: var(--highlight);
|
||||
}
|
||||
|
||||
.profile .ln-address {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
.profile .lnurl {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.profile .lnurl:hover {
|
||||
@ -177,21 +159,9 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.profile .links svg {
|
||||
color: var(--highlight);
|
||||
margin-right: 8px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.profile .npub {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.profile .copy {
|
||||
margin-top: 12px;
|
||||
.profile .copy .body {
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.qr-modal .pfp {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import "./ProfilePage.css";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useIntl, FormattedMessage } from "react-intl";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import {
|
||||
encodeTLV,
|
||||
@ -109,7 +109,6 @@ function BookMarksTab({ id }: { id: HexKey }) {
|
||||
}
|
||||
|
||||
export default function ProfilePage() {
|
||||
const { formatMessage } = useIntl();
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [id, setId] = useState<string>();
|
||||
@ -144,16 +143,88 @@ export default function ProfilePage() {
|
||||
const follows = useFollowsFeed(id);
|
||||
// tabs
|
||||
const ProfileTab = {
|
||||
Notes: { text: formatMessage({ defaultMessage: "Notes" }), value: NOTES },
|
||||
Reactions: { text: formatMessage(messages.Reactions), value: REACTIONS },
|
||||
Followers: { text: formatMessage(messages.Followers), value: FOLLOWERS },
|
||||
Follows: { text: formatMessage(messages.Follows), value: FOLLOWS },
|
||||
Zaps: { text: formatMessage(messages.Zaps), value: ZAPS },
|
||||
Muted: { text: formatMessage(messages.Muted), value: MUTED },
|
||||
Blocked: { text: formatMessage(messages.BlockedCount, { n: blocked.length }), value: BLOCKED },
|
||||
Relays: { text: formatMessage(messages.Relays), value: RELAYS },
|
||||
Bookmarks: { text: formatMessage(messages.Bookmarks), value: BOOKMARKS },
|
||||
};
|
||||
Notes: {
|
||||
text: (
|
||||
<>
|
||||
<Icon name="pencil" size={16} />
|
||||
<FormattedMessage defaultMessage="Notes" />
|
||||
</>
|
||||
),
|
||||
value: NOTES,
|
||||
},
|
||||
Reactions: {
|
||||
text: (
|
||||
<>
|
||||
<Icon name="reaction" size={16} />
|
||||
<FormattedMessage defaultMessage="Reactions" />
|
||||
</>
|
||||
),
|
||||
value: REACTIONS,
|
||||
},
|
||||
Followers: {
|
||||
text: (
|
||||
<>
|
||||
<Icon name="user-v2" size={16} />
|
||||
<FormattedMessage defaultMessage="Followers" />
|
||||
</>
|
||||
),
|
||||
value: FOLLOWERS,
|
||||
},
|
||||
Follows: {
|
||||
text: (
|
||||
<>
|
||||
<Icon name="stars" size={16} />
|
||||
<FormattedMessage defaultMessage="Follows" />
|
||||
</>
|
||||
),
|
||||
value: FOLLOWS,
|
||||
},
|
||||
Zaps: {
|
||||
text: (
|
||||
<>
|
||||
<Icon name="zap-solid" size={16} />
|
||||
<FormattedMessage defaultMessage="Zaps" />
|
||||
</>
|
||||
),
|
||||
value: ZAPS,
|
||||
},
|
||||
Muted: {
|
||||
text: (
|
||||
<>
|
||||
<Icon name="mute" size={16} />
|
||||
<FormattedMessage defaultMessage="Muted" />
|
||||
</>
|
||||
),
|
||||
value: MUTED,
|
||||
},
|
||||
Blocked: {
|
||||
text: (
|
||||
<>
|
||||
<Icon name="block" size={16} />
|
||||
<FormattedMessage defaultMessage="Blocked" />
|
||||
</>
|
||||
),
|
||||
value: BLOCKED,
|
||||
},
|
||||
Relays: {
|
||||
text: (
|
||||
<>
|
||||
<Icon name="wifi" size={16} />
|
||||
<FormattedMessage defaultMessage="Relays" />
|
||||
</>
|
||||
),
|
||||
value: RELAYS,
|
||||
},
|
||||
Bookmarks: {
|
||||
text: (
|
||||
<>
|
||||
<Icon name="bookmark-solid" size={16} />
|
||||
<FormattedMessage defaultMessage="Bookmarks" />
|
||||
</>
|
||||
),
|
||||
value: BOOKMARKS,
|
||||
},
|
||||
} as { [key: string]: Tab };
|
||||
const [tab, setTab] = useState<Tab>(ProfileTab.Notes);
|
||||
const optionalTabs = [ProfileTab.Zaps, ProfileTab.Relays, ProfileTab.Bookmarks, ProfileTab.Muted].filter(a =>
|
||||
unwrap(a)
|
||||
@ -179,34 +250,48 @@ export default function ProfilePage() {
|
||||
|
||||
function username() {
|
||||
return (
|
||||
<div className="name">
|
||||
<h2>
|
||||
{user?.display_name || user?.name || "Nostrich"}
|
||||
<FollowsYou followsMe={follows.includes(loginPubKey ?? "")} />
|
||||
</h2>
|
||||
{user?.nip05 && <Nip05 nip05={user.nip05} pubkey={user.pubkey} />}
|
||||
<>
|
||||
<div className="name">
|
||||
<h2>
|
||||
{user?.display_name || user?.name || "Nostrich"}
|
||||
<FollowsYou followsMe={follows.includes(loginPubKey ?? "")} />
|
||||
</h2>
|
||||
{user?.nip05 && <Nip05 nip05={user.nip05} pubkey={user.pubkey} />}
|
||||
</div>
|
||||
<BadgeList badges={badges} />
|
||||
<Copy text={npub} />
|
||||
{links()}
|
||||
</div>
|
||||
<div className="link-section">
|
||||
<Copy text={npub} />
|
||||
{links()}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function tryFormatWebsite(url: string) {
|
||||
try {
|
||||
const u = new URL(url);
|
||||
return `${u.hostname}${u.pathname !== "/" ? u.pathname : ""}`;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
function links() {
|
||||
return (
|
||||
<div className="links">
|
||||
<>
|
||||
{user?.website && (
|
||||
<div className="website f-ellipsis">
|
||||
<Icon name="link" />
|
||||
<div className="link website f-ellipsis">
|
||||
<Icon name="link-02" size={16} />
|
||||
<a href={website_url} target="_blank" rel="noreferrer">
|
||||
{user.website}
|
||||
{tryFormatWebsite(user.website)}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{lnurl && (
|
||||
<div className="lnurl f-ellipsis" onClick={() => setShowLnQr(true)}>
|
||||
<Icon name="zap" />
|
||||
<div className="link lnurl f-ellipsis" onClick={() => setShowLnQr(true)}>
|
||||
<Icon name="zapCircle" size={16} />
|
||||
{lnurl.name}
|
||||
</div>
|
||||
)}
|
||||
@ -218,14 +303,14 @@ export default function ProfilePage() {
|
||||
author={id}
|
||||
target={user?.display_name || user?.name}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function bio() {
|
||||
return (
|
||||
aboutText.length > 0 && (
|
||||
<div dir="auto" className="details">
|
||||
<div dir="auto" className="about">
|
||||
{about}
|
||||
</div>
|
||||
)
|
||||
@ -296,8 +381,12 @@ export default function ProfilePage() {
|
||||
|
||||
function avatar() {
|
||||
return (
|
||||
<div className="avatar-wrapper">
|
||||
<div className="avatar-wrapper w-max">
|
||||
<Avatar user={user} />
|
||||
<div className="profile-actions">
|
||||
{renderIcons()}
|
||||
{!isMe && id && <FollowButton pubkey={id} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -356,12 +445,8 @@ export default function ProfilePage() {
|
||||
function userDetails() {
|
||||
if (!id) return;
|
||||
return (
|
||||
<div className="details-wrapper">
|
||||
<div className="details-wrapper w-max">
|
||||
{username()}
|
||||
<div className="profile-actions">
|
||||
{renderIcons()}
|
||||
{!isMe && <FollowButton pubkey={id} />}
|
||||
</div>
|
||||
{bio()}
|
||||
</div>
|
||||
);
|
||||
@ -374,9 +459,9 @@ export default function ProfilePage() {
|
||||
const w = window.document.querySelector(".page")?.clientWidth;
|
||||
return (
|
||||
<>
|
||||
<div className="profile flex">
|
||||
<div className="profile">
|
||||
{user?.banner && <ProxyImg alt="banner" className="banner" src={user.banner} size={w} />}
|
||||
<div className="profile-wrapper flex">
|
||||
<div className="profile-wrapper w-max">
|
||||
{avatar()}
|
||||
{userDetails()}
|
||||
</div>
|
||||
|
@ -31,15 +31,6 @@ export default function RootPage() {
|
||||
const { publicKey: pubKey, tags, preferences } = useLogin();
|
||||
const [rootType, setRootType] = useState<RootPage>("following");
|
||||
|
||||
useEffect(() => {
|
||||
if (location.pathname === "/") {
|
||||
const t = pubKey ? preferences.defaultRootTab ?? "/notes" : "/global";
|
||||
navigate(t, {
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
}, [location]);
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
tab: "following",
|
||||
@ -107,6 +98,18 @@ export default function RootPage() {
|
||||
element: ReactNode;
|
||||
}>;
|
||||
|
||||
useEffect(() => {
|
||||
if (location.pathname === "/") {
|
||||
const t = pubKey ? preferences.defaultRootTab ?? "/notes" : "/global";
|
||||
navigate(t);
|
||||
} else {
|
||||
const currentTab = menuItems.find(a => a.path === location.pathname)?.tab;
|
||||
if (currentTab) {
|
||||
setRootType(currentTab);
|
||||
}
|
||||
}
|
||||
}, [location]);
|
||||
|
||||
function currentMenuItem() {
|
||||
if (location.pathname.startsWith("/t/")) {
|
||||
return (
|
||||
@ -139,8 +142,7 @@ export default function RootPage() {
|
||||
{menuItems.map(a => (
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setRootType(a.tab);
|
||||
navigate(a.path, { replace: true });
|
||||
navigate(a.path);
|
||||
}}>
|
||||
{a.element}
|
||||
</MenuItem>
|
||||
@ -148,8 +150,7 @@ export default function RootPage() {
|
||||
{tags.item.map(v => (
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setRootType("tags");
|
||||
navigate(`/t/${v}`, { replace: true });
|
||||
navigate(`/t/${v}`);
|
||||
}}>
|
||||
<Icon name="hash" />
|
||||
{v}
|
||||
|
@ -9,7 +9,7 @@
|
||||
--font-size-tiny: 11px;
|
||||
--modal-bg-color: rgba(0, 0, 0, 0.8);
|
||||
--note-bg: #0c0c0c;
|
||||
--highlight: #8b5cf6;
|
||||
--highlight: #ac88ff;
|
||||
--error: #ff6053;
|
||||
--success: #2ad544;
|
||||
--warning: #ff8800;
|
||||
@ -23,6 +23,7 @@
|
||||
--gray-tertiary: #444;
|
||||
--gray-dark: #2b2b2b;
|
||||
--gray-superdark: #1a1a1a;
|
||||
--gray-ultradark: #111;
|
||||
--gray-gradient: linear-gradient(to bottom right, var(--gray-superlight), var(--gray), var(--gray-light));
|
||||
--snort-gradient: linear-gradient(90deg, #a178ff 0%, #ff6baf 100%);
|
||||
--dm-gradient: linear-gradient(90deg, #5722d2 0%, #db1771 100%);
|
||||
@ -123,10 +124,17 @@ body #root > div:not(.page) header {
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 16px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--gray-superdark);
|
||||
}
|
||||
|
||||
/* Card inside card */
|
||||
.card .card {
|
||||
border: 1px solid var(--gray-superdark);
|
||||
border-radius: 16px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.card .header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -407,6 +415,11 @@ input:disabled {
|
||||
a {
|
||||
color: inherit;
|
||||
line-height: 1.3em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a.ext {
|
||||
|
@ -17,6 +17,9 @@
|
||||
"+vVZ/G": {
|
||||
"defaultMessage": "Connect"
|
||||
},
|
||||
"+xliwN": {
|
||||
"defaultMessage": "{name} reposted"
|
||||
},
|
||||
"/4tOwT": {
|
||||
"defaultMessage": "Skip"
|
||||
},
|
||||
@ -36,6 +39,9 @@
|
||||
"/n5KSF": {
|
||||
"defaultMessage": "{n} ms"
|
||||
},
|
||||
"00LcfG": {
|
||||
"defaultMessage": "Load more"
|
||||
},
|
||||
"08zn6O": {
|
||||
"defaultMessage": "Export Keys"
|
||||
},
|
||||
@ -438,6 +444,9 @@
|
||||
"IEwZvs": {
|
||||
"defaultMessage": "Are you sure you want to unpin this note?"
|
||||
},
|
||||
"IKKHqV": {
|
||||
"defaultMessage": "Follows"
|
||||
},
|
||||
"INSqIz": {
|
||||
"defaultMessage": "Twitter username..."
|
||||
},
|
||||
|
@ -5,12 +5,14 @@
|
||||
"+vA//S": "Logins",
|
||||
"+vIQlC": "Please make sure to save the following password in order to manage your handle in the future",
|
||||
"+vVZ/G": "Connect",
|
||||
"+xliwN": "{name} reposted",
|
||||
"/4tOwT": "Skip",
|
||||
"/JE/X+": "Account Support",
|
||||
"/PCavi": "Public",
|
||||
"/RD0e2": "Nostr uses digital signature technology to provide tamper proof notes which can safely be replicated to many relays to provide redundant storage of your content.",
|
||||
"/d6vEc": "Make your profile easier to find and share",
|
||||
"/n5KSF": "{n} ms",
|
||||
"00LcfG": "Load more",
|
||||
"08zn6O": "Export Keys",
|
||||
"0Azlrb": "Manage",
|
||||
"0BUTMv": "Search...",
|
||||
@ -87,7 +89,6 @@
|
||||
"B6+XJy": "zapped",
|
||||
"B6H7eJ": "nsec, npub, nip-05, hex",
|
||||
"BGCM48": "Write access to Snort relay, with 1 year of event retention",
|
||||
"BGxpTN": "Stream Chat",
|
||||
"BOUMjw": "No nostr users found for {twitterUsername}",
|
||||
"BOr9z/": "Snort is an open source project built by passionate people in their free time",
|
||||
"BWpuKl": "Update",
|
||||
@ -144,6 +145,7 @@
|
||||
"HbefNb": "Open Wallet",
|
||||
"IDjHJ6": "Thanks for using Snort, please consider donating if you can.",
|
||||
"IEwZvs": "Are you sure you want to unpin this note?",
|
||||
"IKKHqV": "Follows",
|
||||
"INSqIz": "Twitter username...",
|
||||
"IUZC+0": "This means that nobody can modify notes which you have created and everybody can easily verify that the notes they are reading are created by you.",
|
||||
"Ig9/a1": "Sent {n} sats to {name}",
|
||||
@ -268,7 +270,6 @@
|
||||
"c+oiJe": "Install Extension",
|
||||
"c35bj2": "If you have an enquiry about your NIP-05 order please DM {link}",
|
||||
"c3g2hL": "Broadcast Again",
|
||||
"cE4Hfw": "Discover",
|
||||
"cFbU1B": "Using Alby? Go to {link} to get your NWC config!",
|
||||
"cPIKU2": "Following",
|
||||
"cQfLWb": "URL..",
|
||||
@ -285,7 +286,6 @@
|
||||
"eHAneD": "Reaction emoji",
|
||||
"eJj8HD": "Get Verified",
|
||||
"eSzf2G": "A single zap of {nIn} sats will allocate {nOut} sats to the zap pool.",
|
||||
"fBI91o": "Zap",
|
||||
"fOksnD": "Can't vote because LNURL service does not support zaps",
|
||||
"fWZYP5": "Pinned",
|
||||
"filwqD": "Read",
|
||||
@ -366,7 +366,6 @@
|
||||
"pzTOmv": "Followers",
|
||||
"qD9EUF": "Email <> DM bridge for your Snort nostr address",
|
||||
"qDwvZ4": "Unknown error",
|
||||
"qInqHy": "Message...",
|
||||
"qMx1sA": "Default Zap amount",
|
||||
"qUJTsT": "Blocked",
|
||||
"qdGuQo": "Your Private Key Is (do not share this with anyone)",
|
||||
@ -426,4 +425,4 @@
|
||||
"zjJZBd": "You're ready!",
|
||||
"zonsdq": "Failed to load LNURL service",
|
||||
"zvCDao": "Automatically show latest notes"
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user