Style tweaks
This commit is contained in:
parent
4e21c4834b
commit
4b85a16904
@ -1,20 +1,21 @@
|
|||||||
import "./Avatar.css";
|
import "./Avatar.css";
|
||||||
import Nostrich from "nostrich.webp";
|
|
||||||
|
|
||||||
import { CSSProperties, useEffect, useState } from "react";
|
import { CSSProperties, useEffect, useState } from "react";
|
||||||
import type { UserMetadata } from "@snort/system";
|
import type { UserMetadata } from "@snort/system";
|
||||||
|
|
||||||
import useImgProxy from "Hooks/useImgProxy";
|
import useImgProxy from "Hooks/useImgProxy";
|
||||||
import { getDisplayName } from "Element/ProfileImage";
|
import { getDisplayName } from "Element/ProfileImage";
|
||||||
|
import { defaultAvatar } from "SnortUtils";
|
||||||
|
|
||||||
interface AvatarProps {
|
interface AvatarProps {
|
||||||
|
pubkey: string;
|
||||||
user?: UserMetadata;
|
user?: UserMetadata;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
size?: number;
|
size?: number;
|
||||||
image?: string;
|
image?: string;
|
||||||
}
|
}
|
||||||
const Avatar = ({ user, size, onClick, image }: AvatarProps) => {
|
const Avatar = ({ pubkey, user, size, onClick, image }: AvatarProps) => {
|
||||||
const [url, setUrl] = useState<string>(Nostrich);
|
const [url, setUrl] = useState("");
|
||||||
const { proxy } = useImgProxy();
|
const { proxy } = useImgProxy();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -23,7 +24,7 @@ const Avatar = ({ user, size, onClick, image }: AvatarProps) => {
|
|||||||
const proxyUrl = proxy(url, size ?? 120);
|
const proxyUrl = proxy(url, size ?? 120);
|
||||||
setUrl(proxyUrl);
|
setUrl(proxyUrl);
|
||||||
} else {
|
} else {
|
||||||
setUrl(Nostrich);
|
setUrl(defaultAvatar(pubkey));
|
||||||
}
|
}
|
||||||
}, [user, image]);
|
}, [user, image]);
|
||||||
|
|
||||||
|
@ -22,6 +22,6 @@
|
|||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-body button:hover {
|
.modal-body button.secondary:hover {
|
||||||
background-color: var(--gray);
|
background-color: var(--gray);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import "./Nip05.css";
|
import "./Nip05.css";
|
||||||
import { HexKey } from "@snort/system";
|
import { HexKey } from "@snort/system";
|
||||||
|
|
||||||
import Icon from "Icons/Icon";
|
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
|
|
||||||
export function useIsVerified(pubkey: HexKey, bypassCheck?: boolean) {
|
export function useIsVerified(pubkey: HexKey, bypassCheck?: boolean) {
|
||||||
@ -28,7 +26,6 @@ const Nip05 = ({ nip05, pubkey, verifyNip = true }: Nip05Params) => {
|
|||||||
<span className="domain" data-domain={domain?.toLowerCase()}>
|
<span className="domain" data-domain={domain?.toLowerCase()}>
|
||||||
{domain}
|
{domain}
|
||||||
</span>
|
</span>
|
||||||
<Icon name="check-verified" className="badge" size={16} />
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -81,6 +81,7 @@
|
|||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
background-color: var(--highlight);
|
background-color: var(--highlight);
|
||||||
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
@ -19,9 +19,6 @@ a.pfp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pfp .username {
|
.pfp .username {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,11 +54,11 @@ export default function ProfileImage({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="avatar-wrapper">
|
<div className="avatar-wrapper">
|
||||||
<Avatar user={user} size={size} />
|
<Avatar pubkey={pubkey} user={user} size={size} />
|
||||||
</div>
|
</div>
|
||||||
{showUsername && (
|
{showUsername && (
|
||||||
<div className="f-ellipsis">
|
<div className="f-ellipsis">
|
||||||
<div className="username">
|
<div className="flex g4 username">
|
||||||
<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>
|
||||||
|
@ -4,9 +4,11 @@ import { NostrEvent, TaggedNostrEvent } from "@snort/system";
|
|||||||
import PageSpinner from "Element/PageSpinner";
|
import PageSpinner from "Element/PageSpinner";
|
||||||
import Note from "Element/Note";
|
import Note from "Element/Note";
|
||||||
import NostrBandApi from "External/NostrBand";
|
import NostrBandApi from "External/NostrBand";
|
||||||
|
import { useReactions } from "Feed/FeedReactions";
|
||||||
|
|
||||||
export default function TrendingNotes() {
|
export default function TrendingNotes() {
|
||||||
const [posts, setPosts] = useState<Array<NostrEvent>>();
|
const [posts, setPosts] = useState<Array<NostrEvent>>();
|
||||||
|
const related = useReactions("trending", posts?.map(a => a.id) ?? []);
|
||||||
|
|
||||||
async function loadTrendingNotes() {
|
async function loadTrendingNotes() {
|
||||||
const api = new NostrBandApi();
|
const api = new NostrBandApi();
|
||||||
@ -23,7 +25,7 @@ export default function TrendingNotes() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{posts.map(e => (
|
{posts.map(e => (
|
||||||
<Note key={e.id} data={e as TaggedNostrEvent} related={[]} depth={0} />
|
<Note key={e.id} data={e as TaggedNostrEvent} related={related?.data ?? []} depth={0} />
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
25
packages/app/src/Feed/FeedReactions.ts
Normal file
25
packages/app/src/Feed/FeedReactions.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { RequestBuilder, EventKind, NoteCollection } from "@snort/system";
|
||||||
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
import useLogin from "Hooks/useLogin";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
export function useReactions(subId: string, ids: Array<string>, others?: (rb: RequestBuilder) => void) {
|
||||||
|
const { preferences: pref } = useLogin();
|
||||||
|
|
||||||
|
const sub = useMemo(() => {
|
||||||
|
const rb = new RequestBuilder(subId);
|
||||||
|
if (ids.length > 0) {
|
||||||
|
rb.withFilter()
|
||||||
|
.kinds(
|
||||||
|
pref.enableReactions
|
||||||
|
? [EventKind.Reaction, EventKind.Repost, EventKind.ZapReceipt]
|
||||||
|
: [EventKind.ZapReceipt, EventKind.Repost]
|
||||||
|
)
|
||||||
|
.tag("e", ids);
|
||||||
|
}
|
||||||
|
others?.(rb);
|
||||||
|
return rb.numFilters > 0 ? rb : null;
|
||||||
|
}, [ids]);
|
||||||
|
|
||||||
|
return useRequestBuilder(NoteCollection, sub);
|
||||||
|
}
|
@ -6,6 +6,7 @@ import { unixNow, unwrap, tagFilterOfTextRepost } from "SnortUtils";
|
|||||||
import useTimelineWindow from "Hooks/useTimelineWindow";
|
import useTimelineWindow from "Hooks/useTimelineWindow";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { SearchRelays } from "Const";
|
import { SearchRelays } from "Const";
|
||||||
|
import { useReactions } from "./FeedReactions";
|
||||||
|
|
||||||
export interface TimelineFeedOptions {
|
export interface TimelineFeedOptions {
|
||||||
method: "TIME_RANGE" | "LIMIT_UNTIL";
|
method: "TIME_RANGE" | "LIMIT_UNTIL";
|
||||||
@ -157,26 +158,13 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const subNext = useMemo(() => {
|
const trackingEvents = main.data?.map(a => a.id) ?? [];
|
||||||
const rb = new RequestBuilder(`timeline-related:${subject.type}:${subject.discriminator}`);
|
const related = useReactions(`timeline-related:${subject.type}:${subject.discriminator}`, trackingEvents, rb => {
|
||||||
const trackingEvents = main.data?.map(a => a.id) ?? [];
|
|
||||||
if (trackingEvents.length > 0) {
|
|
||||||
rb.withFilter()
|
|
||||||
.kinds(
|
|
||||||
pref.enableReactions
|
|
||||||
? [EventKind.Reaction, EventKind.Repost, EventKind.ZapReceipt]
|
|
||||||
: [EventKind.ZapReceipt, EventKind.Repost]
|
|
||||||
)
|
|
||||||
.tag("e", trackingEvents);
|
|
||||||
}
|
|
||||||
const trackingParentEvents = getParentEvents();
|
const trackingParentEvents = getParentEvents();
|
||||||
if (trackingParentEvents.length > 0) {
|
if (trackingParentEvents.length > 0) {
|
||||||
rb.withFilter().ids(trackingParentEvents);
|
rb.withFilter().ids(trackingParentEvents);
|
||||||
}
|
}
|
||||||
return rb.numFilters > 0 ? rb : null;
|
});
|
||||||
}, [main.data, pref, subject.type]);
|
|
||||||
|
|
||||||
const related = useRequestBuilder(NoteCollection, subNext);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
main: main.data,
|
main: main.data,
|
||||||
|
@ -77,7 +77,7 @@ export const DefaultPreferences = {
|
|||||||
language: "en",
|
language: "en",
|
||||||
enableReactions: true,
|
enableReactions: true,
|
||||||
reactionEmoji: "+",
|
reactionEmoji: "+",
|
||||||
autoLoadMedia: "follows-only",
|
autoLoadMedia: "all",
|
||||||
theme: "system",
|
theme: "system",
|
||||||
confirmReposts: false,
|
confirmReposts: false,
|
||||||
showDebugMenus: false,
|
showDebugMenus: false,
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import Nostrich from "nostrich.webp";
|
|
||||||
|
|
||||||
import { TaggedNostrEvent, EventKind, MetadataCache } from "@snort/system";
|
import { TaggedNostrEvent, EventKind, MetadataCache } from "@snort/system";
|
||||||
import { getDisplayName } from "Element/ProfileImage";
|
import { getDisplayName } from "Element/ProfileImage";
|
||||||
import { MentionRegex } from "Const";
|
import { MentionRegex } from "Const";
|
||||||
import { tagFilterOfTextRepost, unwrap } from "SnortUtils";
|
import { defaultAvatar, tagFilterOfTextRepost, unwrap } from "SnortUtils";
|
||||||
import { UserCache } from "Cache";
|
import { UserCache } from "Cache";
|
||||||
import { LoginSession } from "Login";
|
import { LoginSession } from "Login";
|
||||||
|
|
||||||
@ -28,7 +26,7 @@ export async function makeNotification(ev: TaggedNostrEvent): Promise<Notificati
|
|||||||
.map(a => unwrap(a));
|
.map(a => unwrap(a));
|
||||||
const fromUser = UserCache.getFromCache(ev.pubkey);
|
const fromUser = UserCache.getFromCache(ev.pubkey);
|
||||||
const name = getDisplayName(fromUser, ev.pubkey);
|
const name = getDisplayName(fromUser, ev.pubkey);
|
||||||
const avatarUrl = fromUser?.picture || Nostrich;
|
const avatarUrl = fromUser?.picture || defaultAvatar(ev.pubkey);
|
||||||
return {
|
return {
|
||||||
title: `Reply from ${name}`,
|
title: `Reply from ${name}`,
|
||||||
body: replaceTagsWithUser(ev, allUsers).substring(0, 50),
|
body: replaceTagsWithUser(ev, allUsers).substring(0, 50),
|
||||||
|
@ -27,7 +27,7 @@ const HashTagsPage = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="main-content">
|
<div className="main-content p">
|
||||||
<div className="action-heading">
|
<div className="action-heading">
|
||||||
<h2>#{tag}</h2>
|
<h2>#{tag}</h2>
|
||||||
{isFollowing ? (
|
{isFollowing ? (
|
||||||
|
@ -179,6 +179,7 @@ const AccountHeader = () => {
|
|||||||
{hasNotifications && <span className="has-unread"></span>}
|
{hasNotifications && <span className="has-unread"></span>}
|
||||||
</Link>
|
</Link>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
pubkey={publicKey ?? ""}
|
||||||
user={profile}
|
user={profile}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (profile) {
|
if (profile) {
|
||||||
|
@ -382,7 +382,7 @@ export default function ProfilePage() {
|
|||||||
function avatar() {
|
function avatar() {
|
||||||
return (
|
return (
|
||||||
<div className="avatar-wrapper w-max">
|
<div className="avatar-wrapper w-max">
|
||||||
<Avatar user={user} />
|
<Avatar pubkey={id ?? ""} user={user} />
|
||||||
<div className="profile-actions">
|
<div className="profile-actions">
|
||||||
{renderIcons()}
|
{renderIcons()}
|
||||||
{!isMe && id && <FollowButton pubkey={id} />}
|
{!isMe && id && <FollowButton pubkey={id} />}
|
||||||
|
@ -36,6 +36,7 @@ export default function RootPage() {
|
|||||||
{
|
{
|
||||||
tab: "following",
|
tab: "following",
|
||||||
path: "/notes",
|
path: "/notes",
|
||||||
|
show: Boolean(pubKey),
|
||||||
element: (
|
element: (
|
||||||
<>
|
<>
|
||||||
<Icon name="user-v2" />
|
<Icon name="user-v2" />
|
||||||
@ -46,6 +47,7 @@ export default function RootPage() {
|
|||||||
{
|
{
|
||||||
tab: "trending-notes",
|
tab: "trending-notes",
|
||||||
path: "/trending/notes",
|
path: "/trending/notes",
|
||||||
|
show: true,
|
||||||
element: (
|
element: (
|
||||||
<>
|
<>
|
||||||
<Icon name="fire" />
|
<Icon name="fire" />
|
||||||
@ -56,6 +58,7 @@ export default function RootPage() {
|
|||||||
{
|
{
|
||||||
tab: "conversations",
|
tab: "conversations",
|
||||||
path: "/conversations",
|
path: "/conversations",
|
||||||
|
show: Boolean(pubKey),
|
||||||
element: (
|
element: (
|
||||||
<>
|
<>
|
||||||
<Icon name="message-chat-circle" />
|
<Icon name="message-chat-circle" />
|
||||||
@ -66,6 +69,7 @@ export default function RootPage() {
|
|||||||
{
|
{
|
||||||
tab: "trending-people",
|
tab: "trending-people",
|
||||||
path: "/trending/people",
|
path: "/trending/people",
|
||||||
|
show: true,
|
||||||
element: (
|
element: (
|
||||||
<>
|
<>
|
||||||
<Icon name="user-up" />
|
<Icon name="user-up" />
|
||||||
@ -76,6 +80,7 @@ export default function RootPage() {
|
|||||||
{
|
{
|
||||||
tab: "suggested",
|
tab: "suggested",
|
||||||
path: "/suggested",
|
path: "/suggested",
|
||||||
|
show: Boolean(pubKey),
|
||||||
element: (
|
element: (
|
||||||
<>
|
<>
|
||||||
<Icon name="thumbs-up" />
|
<Icon name="thumbs-up" />
|
||||||
@ -86,6 +91,7 @@ export default function RootPage() {
|
|||||||
{
|
{
|
||||||
tab: "global",
|
tab: "global",
|
||||||
path: "/global",
|
path: "/global",
|
||||||
|
show: true,
|
||||||
element: (
|
element: (
|
||||||
<>
|
<>
|
||||||
<Icon name="globe" />
|
<Icon name="globe" />
|
||||||
@ -96,12 +102,13 @@ export default function RootPage() {
|
|||||||
] as Array<{
|
] as Array<{
|
||||||
tab: RootPage;
|
tab: RootPage;
|
||||||
path: string;
|
path: string;
|
||||||
|
show: boolean;
|
||||||
element: ReactNode;
|
element: ReactNode;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (location.pathname === "/") {
|
if (location.pathname === "/") {
|
||||||
const t = pubKey ? preferences.defaultRootTab ?? "/notes" : "/global";
|
const t = pubKey ? preferences.defaultRootTab ?? "/notes" : "/trending/notes";
|
||||||
navigate(t);
|
navigate(t);
|
||||||
} else {
|
} else {
|
||||||
const currentTab = menuItems.find(a => a.path === location.pathname)?.tab;
|
const currentTab = menuItems.find(a => a.path === location.pathname)?.tab;
|
||||||
@ -140,14 +147,16 @@ export default function RootPage() {
|
|||||||
<div className="close-menu" />
|
<div className="close-menu" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</div>
|
</div>
|
||||||
{menuItems.map(a => (
|
{menuItems
|
||||||
<MenuItem
|
.filter(a => a.show)
|
||||||
onClick={() => {
|
.map(a => (
|
||||||
navigate(a.path);
|
<MenuItem
|
||||||
}}>
|
onClick={() => {
|
||||||
{a.element}
|
navigate(a.path);
|
||||||
</MenuItem>
|
}}>
|
||||||
))}
|
{a.element}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
{tags.item.map(v => (
|
{tags.item.map(v => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
background: linear-gradient(90deg, #60a5fa 0%, #a78bfa 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings .image-settings {
|
.settings .image-settings {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import "./Profile.css";
|
import "./Profile.css";
|
||||||
import Nostrich from "nostrich.webp";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
@ -168,7 +167,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
|||||||
{(props.banner ?? true) && (
|
{(props.banner ?? true) && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${(banner?.length ?? 0) === 0 ? Nostrich : banner})`,
|
backgroundImage: (banner?.length ?? 0) > 0 ? `url(${banner})` : undefined,
|
||||||
}}
|
}}
|
||||||
className="banner">
|
className="banner">
|
||||||
<AsyncButton type="button" onClick={() => setNewBanner()}>
|
<AsyncButton type="button" onClick={() => setNewBanner()}>
|
||||||
@ -178,7 +177,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
|||||||
)}
|
)}
|
||||||
{(props.avatar ?? true) && (
|
{(props.avatar ?? true) && (
|
||||||
<div className="avatar-stack">
|
<div className="avatar-stack">
|
||||||
<Avatar user={user} image={picture} />
|
<Avatar pubkey={id} user={user} image={picture} />
|
||||||
<AsyncButton type="button" className="btn-rnd" onClick={() => setNewAvatar()}>
|
<AsyncButton type="button" className="btn-rnd" onClick={() => setNewAvatar()}>
|
||||||
<Icon name="upload-01" />
|
<Icon name="upload-01" />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
|
@ -505,3 +505,7 @@ export function kvToObject<T>(o: string, sep?: string) {
|
|||||||
})
|
})
|
||||||
) as T;
|
) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function defaultAvatar(input: string) {
|
||||||
|
return `https://robohash.v0l.io/${input}.png`;
|
||||||
|
}
|
||||||
|
@ -123,6 +123,10 @@ code {
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
border: 1px solid var(--gray-superdark);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.p {
|
.p {
|
||||||
@ -161,9 +165,9 @@ button {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: white;
|
color: var(--bg-color);
|
||||||
font-size: var(--font-size);
|
font-size: var(--font-size);
|
||||||
background-color: var(--highlight);
|
background-color: var(--font-color);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
outline: none;
|
outline: none;
|
||||||
@ -194,8 +198,7 @@ button:disabled:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
background-color: var(--font-color);
|
background-color: var(--gray-light);
|
||||||
color: var(--bg-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button.secondary {
|
button.secondary {
|
||||||
@ -595,10 +598,6 @@ small.xs {
|
|||||||
color: var(--highlight);
|
color: var(--highlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content {
|
|
||||||
border: 1px solid var(--gray-superdark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bold {
|
.bold {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user