DM styles update
This commit is contained in:
parent
0e7aefb036
commit
e8e65c71cd
@ -3,7 +3,6 @@ import { FormattedMessage } from "react-intl";
|
|||||||
|
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
interface Token {
|
interface Token {
|
||||||
token: Array<{
|
token: Array<{
|
||||||
@ -17,7 +16,7 @@ interface Token {
|
|||||||
|
|
||||||
export default function CashuNuts({ token }: { token: string }) {
|
export default function CashuNuts({ token }: { token: string }) {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const profile = useUserProfile(System, login.publicKey);
|
const profile = useUserProfile(login.publicKey);
|
||||||
|
|
||||||
async function copyToken(e: React.MouseEvent<HTMLButtonElement>, token: string) {
|
async function copyToken(e: React.MouseEvent<HTMLButtonElement>, token: string) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
.dm-window > div:nth-child(1) {
|
||||||
|
padding: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.dm-window > div:nth-child(2) {
|
.dm-window > div:nth-child(2) {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@ -15,7 +18,6 @@
|
|||||||
.dm-window > div:nth-child(3) {
|
.dm-window > div:nth-child(3) {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: var(--bg-color);
|
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,13 @@ import DM from "Element/DM";
|
|||||||
import NoteToSelf from "Element/NoteToSelf";
|
import NoteToSelf from "Element/NoteToSelf";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import WriteMessage from "Element/WriteMessage";
|
import WriteMessage from "Element/WriteMessage";
|
||||||
import { Chat, ChatParticipant, useChatSystem } from "chat";
|
import { Chat, ChatParticipant, createEmptyChatObject, useChatSystem } from "chat";
|
||||||
import { Nip4ChatSystem } from "chat/nip4";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
export default function DmWindow({ id }: { id: string }) {
|
export default function DmWindow({ id }: { id: string }) {
|
||||||
const pubKey = useLogin().publicKey;
|
const pubKey = useLogin().publicKey;
|
||||||
const dms = useChatSystem();
|
const dms = useChatSystem();
|
||||||
const chat = dms.find(a => a.id === id) ?? Nip4ChatSystem.createChatObj(id, []);
|
const chat = dms.find(a => a.id === id) ?? createEmptyChatObject(id);
|
||||||
|
|
||||||
function participant(p: ChatParticipant) {
|
function participant(p: ChatParticipant) {
|
||||||
if (p.id === pubKey) {
|
if (p.id === pubKey) {
|
||||||
@ -37,7 +36,7 @@ export default function DmWindow({ id }: { id: string }) {
|
|||||||
{chat.participants.map(v => (
|
{chat.participants.map(v => (
|
||||||
<ProfileImage pubkey={v.id} showUsername={false} />
|
<ProfileImage pubkey={v.id} showUsername={false} />
|
||||||
))}
|
))}
|
||||||
{chat.title ?? <FormattedMessage defaultMessage="Group Chat" />}
|
{chat.title ?? <FormattedMessage defaultMessage="Secret Group Chat" />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ export interface FollowListBaseProps {
|
|||||||
showAbout?: boolean;
|
showAbout?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
actions?: ReactNode;
|
actions?: ReactNode;
|
||||||
|
profileActions?: (pk: string) => ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FollowListBase({
|
export default function FollowListBase({
|
||||||
@ -25,6 +26,7 @@ export default function FollowListBase({
|
|||||||
showAbout,
|
showAbout,
|
||||||
className,
|
className,
|
||||||
actions,
|
actions,
|
||||||
|
profileActions,
|
||||||
}: FollowListBaseProps) {
|
}: FollowListBaseProps) {
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
const { follows, relays } = useLogin();
|
const { follows, relays } = useLogin();
|
||||||
@ -48,7 +50,7 @@ export default function FollowListBase({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{pubkeys?.map(a => (
|
{pubkeys?.map(a => (
|
||||||
<ProfilePreview pubkey={a} key={a} options={{ about: showAbout }} />
|
<ProfilePreview pubkey={a} key={a} options={{ about: showAbout }} actions={profileActions?.(a)} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -5,10 +5,9 @@ import { HexKey } from "@snort/system";
|
|||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
import { profileLink } from "SnortUtils";
|
import { profileLink } from "SnortUtils";
|
||||||
import { getDisplayName } from "Element/ProfileImage";
|
import { getDisplayName } from "Element/ProfileImage";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
export default function Mention({ pubkey, relays }: { pubkey: HexKey; relays?: Array<string> | string }) {
|
export default function Mention({ pubkey, relays }: { pubkey: HexKey; relays?: Array<string> | string }) {
|
||||||
const user = useUserProfile(System, pubkey);
|
const user = useUserProfile(pubkey);
|
||||||
|
|
||||||
const name = useMemo(() => {
|
const name = useMemo(() => {
|
||||||
return getDisplayName(user, pubkey);
|
return getDisplayName(user, pubkey);
|
||||||
|
@ -3,10 +3,9 @@ import { HexKey } from "@snort/system";
|
|||||||
|
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
export function useIsVerified(pubkey: HexKey, bypassCheck?: boolean) {
|
export function useIsVerified(pubkey: HexKey, bypassCheck?: boolean) {
|
||||||
const profile = useUserProfile(System, pubkey);
|
const profile = useUserProfile(pubkey);
|
||||||
return { isVerified: bypassCheck || profile?.isNostrAddressValid };
|
return { isVerified: bypassCheck || profile?.isNostrAddressValid };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ export default function Nip5Service(props: Nip05ServiceProps) {
|
|||||||
const { helpText = true } = props;
|
const { helpText = true } = props;
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const pubkey = useLogin().publicKey;
|
const pubkey = useLogin().publicKey;
|
||||||
const user = useUserProfile(System, pubkey);
|
const user = useUserProfile(pubkey);
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
const svc = useMemo(() => new ServiceProvider(props.service), [props.service]);
|
const svc = useMemo(() => new ServiceProvider(props.service), [props.service]);
|
||||||
const [serviceConfig, setServiceConfig] = useState<ServiceConfig>();
|
const [serviceConfig, setServiceConfig] = useState<ServiceConfig>();
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
min-height: 110px;
|
min-height: 110px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note:hover {
|
.note:hover {
|
||||||
|
@ -50,7 +50,7 @@ export default function NoteFooter(props: NoteFooterProps) {
|
|||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const { publicKey, preferences: prefs, relays } = login;
|
const { publicKey, preferences: prefs, relays } = login;
|
||||||
const author = useUserProfile(System, ev.pubkey);
|
const author = useUserProfile(ev.pubkey);
|
||||||
const interactionCache = useInteractionCache(publicKey, ev.id);
|
const interactionCache = useInteractionCache(publicKey, ev.id);
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
const showNoteCreatorModal = useSelector((s: RootState) => s.noteCreator.show);
|
const showNoteCreatorModal = useSelector((s: RootState) => s.noteCreator.show);
|
||||||
|
@ -10,7 +10,6 @@ import useModeration from "Hooks/useModeration";
|
|||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
export interface NoteReactionProps {
|
export interface NoteReactionProps {
|
||||||
data: TaggedNostrEvent;
|
data: TaggedNostrEvent;
|
||||||
@ -19,7 +18,7 @@ export interface NoteReactionProps {
|
|||||||
export default function NoteReaction(props: NoteReactionProps) {
|
export default function NoteReaction(props: NoteReactionProps) {
|
||||||
const { data: ev } = props;
|
const { data: ev } = props;
|
||||||
const { isMuted } = useModeration();
|
const { isMuted } = useModeration();
|
||||||
const profile = useUserProfile(System, ev.pubkey);
|
const profile = useUserProfile(ev.pubkey);
|
||||||
|
|
||||||
const refEvent = useMemo(() => {
|
const refEvent = useMemo(() => {
|
||||||
if (ev) {
|
if (ev) {
|
||||||
|
@ -12,7 +12,6 @@ import { formatShort } from "Number";
|
|||||||
import Spinner from "Icons/Spinner";
|
import Spinner from "Icons/Spinner";
|
||||||
import SendSats from "Element/SendSats";
|
import SendSats from "Element/SendSats";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
interface PollProps {
|
interface PollProps {
|
||||||
ev: TaggedNostrEvent;
|
ev: TaggedNostrEvent;
|
||||||
@ -24,7 +23,7 @@ export default function Poll(props: PollProps) {
|
|||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
const { wallet } = useWallet();
|
const { wallet } = useWallet();
|
||||||
const { preferences: prefs, publicKey: myPubKey, relays } = useLogin();
|
const { preferences: prefs, publicKey: myPubKey, relays } = useLogin();
|
||||||
const pollerProfile = useUserProfile(System, props.ev.pubkey);
|
const pollerProfile = useUserProfile(props.ev.pubkey);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [invoice, setInvoice] = useState("");
|
const [invoice, setInvoice] = useState("");
|
||||||
const [voting, setVoting] = useState<number>();
|
const [voting, setVoting] = useState<number>();
|
||||||
|
@ -8,7 +8,6 @@ import { useUserProfile } from "@snort/system-react";
|
|||||||
import { hexToBech32, profileLink } from "SnortUtils";
|
import { hexToBech32, profileLink } from "SnortUtils";
|
||||||
import Avatar from "Element/Avatar";
|
import Avatar from "Element/Avatar";
|
||||||
import Nip05 from "Element/Nip05";
|
import Nip05 from "Element/Nip05";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
export interface ProfileImageProps {
|
export interface ProfileImageProps {
|
||||||
pubkey: HexKey;
|
pubkey: HexKey;
|
||||||
@ -21,6 +20,7 @@ export interface ProfileImageProps {
|
|||||||
overrideUsername?: string;
|
overrideUsername?: string;
|
||||||
profile?: UserMetadata;
|
profile?: UserMetadata;
|
||||||
size?: number;
|
size?: number;
|
||||||
|
onClick?: (e: React.MouseEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ProfileImage({
|
export default function ProfileImage({
|
||||||
@ -34,8 +34,9 @@ export default function ProfileImage({
|
|||||||
overrideUsername,
|
overrideUsername,
|
||||||
profile,
|
profile,
|
||||||
size,
|
size,
|
||||||
|
onClick,
|
||||||
}: ProfileImageProps) {
|
}: ProfileImageProps) {
|
||||||
const user = profile ?? useUserProfile(System, pubkey);
|
const user = useUserProfile(profile ? "" : pubkey) ?? profile;
|
||||||
const nip05 = defaultNip ? defaultNip : user?.nip05;
|
const nip05 = defaultNip ? defaultNip : user?.nip05;
|
||||||
|
|
||||||
const name = useMemo(() => {
|
const name = useMemo(() => {
|
||||||
@ -45,6 +46,7 @@ export default function ProfileImage({
|
|||||||
function handleClick(e: React.MouseEvent) {
|
function handleClick(e: React.MouseEvent) {
|
||||||
if (link === "") {
|
if (link === "") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
onClick?.(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +70,11 @@ export default function ProfileImage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (link === "") {
|
if (link === "") {
|
||||||
return <div className={`pfp${className ? ` ${className}` : ""}`}>{inner()}</div>;
|
return (
|
||||||
|
<div className={`pfp${className ? ` ${className}` : ""}`} onClick={handleClick}>
|
||||||
|
{inner()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
|
@ -6,32 +6,42 @@ import { useInView } from "react-intersection-observer";
|
|||||||
|
|
||||||
import ProfileImage from "Element/ProfileImage";
|
import ProfileImage from "Element/ProfileImage";
|
||||||
import FollowButton from "Element/FollowButton";
|
import FollowButton from "Element/FollowButton";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
export interface ProfilePreviewProps {
|
export interface ProfilePreviewProps {
|
||||||
pubkey: HexKey;
|
pubkey: HexKey;
|
||||||
options?: {
|
options?: {
|
||||||
about?: boolean;
|
about?: boolean;
|
||||||
|
linkToProfile?: boolean;
|
||||||
};
|
};
|
||||||
actions?: ReactNode;
|
actions?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
}
|
}
|
||||||
export default function ProfilePreview(props: ProfilePreviewProps) {
|
export default function ProfilePreview(props: ProfilePreviewProps) {
|
||||||
const pubkey = props.pubkey;
|
const pubkey = props.pubkey;
|
||||||
const { ref, inView } = useInView({ triggerOnce: true });
|
const { ref, inView } = useInView({ triggerOnce: true });
|
||||||
const user = useUserProfile(System, inView ? pubkey : undefined);
|
const user = useUserProfile(inView ? pubkey : undefined);
|
||||||
const options = {
|
const options = {
|
||||||
about: true,
|
about: true,
|
||||||
...props.options,
|
...props.options,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function handleClick(e: React.MouseEvent<HTMLDivElement>) {
|
||||||
|
if (props.onClick) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
props.onClick(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`profile-preview${props.className ? ` ${props.className}` : ""}`} ref={ref}>
|
<div className={`profile-preview${props.className ? ` ${props.className}` : ""}`} ref={ref} onClick={handleClick}>
|
||||||
{inView && (
|
{inView && (
|
||||||
<>
|
<>
|
||||||
<ProfileImage
|
<ProfileImage
|
||||||
pubkey={pubkey}
|
pubkey={pubkey}
|
||||||
|
link={options.linkToProfile ?? true ? undefined : ""}
|
||||||
subHeader={options.about ? <div className="about">{user?.about}</div> : undefined}
|
subHeader={options.about ? <div className="about">{user?.about}</div> : undefined}
|
||||||
/>
|
/>
|
||||||
{props.actions ?? (
|
{props.actions ?? (
|
||||||
|
@ -5,10 +5,9 @@ import { HexKey } from "@snort/system";
|
|||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
|
|
||||||
import { profileLink } from "SnortUtils";
|
import { profileLink } from "SnortUtils";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
export default function Username({ pubkey, onLinkVisit }: { pubkey: HexKey; onLinkVisit(): void }) {
|
export default function Username({ pubkey, onLinkVisit }: { pubkey: HexKey; onLinkVisit(): void }) {
|
||||||
const user = useUserProfile(System, pubkey);
|
const user = useUserProfile(pubkey);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
function onClick(ev: MouseEvent) {
|
function onClick(ev: MouseEvent) {
|
||||||
|
@ -5,7 +5,6 @@ import { useUserProfile } from "@snort/system-react";
|
|||||||
|
|
||||||
import SendSats from "Element/SendSats";
|
import SendSats from "Element/SendSats";
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
const ZapButton = ({
|
const ZapButton = ({
|
||||||
pubkey,
|
pubkey,
|
||||||
@ -18,7 +17,7 @@ const ZapButton = ({
|
|||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
event?: string;
|
event?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const profile = useUserProfile(System, pubkey);
|
const profile = useUserProfile(pubkey);
|
||||||
const [zap, setZap] = useState(false);
|
const [zap, setZap] = useState(false);
|
||||||
const service = lnurl ?? (profile?.lud16 || profile?.lud06);
|
const service = lnurl ?? (profile?.lud16 || profile?.lud06);
|
||||||
if (!service) return null;
|
if (!service) return null;
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { EventKind, HexKey, Lists, RequestBuilder, FlatNoteStore, ReplaceableNoteStore } from "@snort/system";
|
import { EventKind, HexKey, Lists, RequestBuilder, ReplaceableNoteStore, NoteCollection } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
|
||||||
import { unwrap, findTag, chunks } from "SnortUtils";
|
import { unwrap, findTag, chunks } from "SnortUtils";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
type BadgeAwards = {
|
type BadgeAwards = {
|
||||||
pubkeys: string[];
|
pubkeys: string[];
|
||||||
@ -18,7 +17,7 @@ export default function useProfileBadges(pubkey?: HexKey) {
|
|||||||
return b;
|
return b;
|
||||||
}, [pubkey]);
|
}, [pubkey]);
|
||||||
|
|
||||||
const profileBadges = useRequestBuilder<ReplaceableNoteStore>(System, ReplaceableNoteStore, sub);
|
const profileBadges = useRequestBuilder(ReplaceableNoteStore, sub);
|
||||||
|
|
||||||
const profile = useMemo(() => {
|
const profile = useMemo(() => {
|
||||||
if (profileBadges.data) {
|
if (profileBadges.data) {
|
||||||
@ -58,7 +57,7 @@ export default function useProfileBadges(pubkey?: HexKey) {
|
|||||||
return b;
|
return b;
|
||||||
}, [profile, ds]);
|
}, [profile, ds]);
|
||||||
|
|
||||||
const awards = useRequestBuilder<FlatNoteStore>(System, FlatNoteStore, awardsSub);
|
const awards = useRequestBuilder(NoteCollection, awardsSub);
|
||||||
|
|
||||||
const result = useMemo(() => {
|
const result = useMemo(() => {
|
||||||
if (awards.data) {
|
if (awards.data) {
|
||||||
|
@ -3,7 +3,6 @@ import { NostrPrefix, RequestBuilder, ReplaceableNoteStore, NostrLink } from "@s
|
|||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
|
||||||
import { unwrap } from "SnortUtils";
|
import { unwrap } from "SnortUtils";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
export default function useEventFeed(link: NostrLink) {
|
export default function useEventFeed(link: NostrLink) {
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
@ -28,5 +27,5 @@ export default function useEventFeed(link: NostrLink) {
|
|||||||
return b;
|
return b;
|
||||||
}, [link]);
|
}, [link]);
|
||||||
|
|
||||||
return useRequestBuilder<ReplaceableNoteStore>(System, ReplaceableNoteStore, sub);
|
return useRequestBuilder(ReplaceableNoteStore, sub);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { HexKey, EventKind, NoteCollection, RequestBuilder } from "@snort/system";
|
import { HexKey, EventKind, NoteCollection, RequestBuilder } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
export default function useFollowersFeed(pubkey?: HexKey) {
|
export default function useFollowersFeed(pubkey?: HexKey) {
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
@ -11,7 +10,7 @@ export default function useFollowersFeed(pubkey?: HexKey) {
|
|||||||
return b;
|
return b;
|
||||||
}, [pubkey]);
|
}, [pubkey]);
|
||||||
|
|
||||||
const followersFeed = useRequestBuilder<NoteCollection>(System, NoteCollection, sub);
|
const followersFeed = useRequestBuilder(NoteCollection, sub);
|
||||||
|
|
||||||
const followers = useMemo(() => {
|
const followers = useMemo(() => {
|
||||||
const contactLists = followersFeed.data?.filter(
|
const contactLists = followersFeed.data?.filter(
|
||||||
|
@ -3,7 +3,6 @@ import { HexKey, TaggedNostrEvent, EventKind, NoteCollection, RequestBuilder } f
|
|||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
export default function useFollowsFeed(pubkey?: HexKey) {
|
export default function useFollowsFeed(pubkey?: HexKey) {
|
||||||
const { publicKey, follows } = useLogin();
|
const { publicKey, follows } = useLogin();
|
||||||
@ -16,7 +15,7 @@ export default function useFollowsFeed(pubkey?: HexKey) {
|
|||||||
return b;
|
return b;
|
||||||
}, [isMe, pubkey]);
|
}, [isMe, pubkey]);
|
||||||
|
|
||||||
const contactFeed = useRequestBuilder<NoteCollection>(System, NoteCollection, sub);
|
const contactFeed = useRequestBuilder(NoteCollection, sub);
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (isMe) {
|
if (isMe) {
|
||||||
return follows.item;
|
return follows.item;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { EventKind, FlatNoteStore, NostrLink, RequestBuilder } from "@snort/system";
|
import { EventKind, FlatNoteStore, NostrLink, RequestBuilder } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
import { System } from "index";
|
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
export function useLiveChatFeed(link: NostrLink) {
|
export function useLiveChatFeed(link: NostrLink) {
|
||||||
@ -16,5 +15,5 @@ export function useLiveChatFeed(link: NostrLink) {
|
|||||||
return rb;
|
return rb;
|
||||||
}, [link]);
|
}, [link]);
|
||||||
|
|
||||||
return useRequestBuilder<FlatNoteStore>(System, FlatNoteStore, sub);
|
return useRequestBuilder(FlatNoteStore, sub);
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ export default function useLoginFeed() {
|
|||||||
return b;
|
return b;
|
||||||
}, [pubKey]);
|
}, [pubKey]);
|
||||||
|
|
||||||
const loginFeed = useRequestBuilder(System, NoteCollection, subLogin);
|
const loginFeed = useRequestBuilder(NoteCollection, subLogin);
|
||||||
|
|
||||||
// update relays and follow lists
|
// update relays and follow lists
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -156,7 +156,7 @@ export default function useLoginFeed() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const listsFeed = useRequestBuilder<FlatNoteStore>(System, FlatNoteStore, subLists);
|
const listsFeed = useRequestBuilder(FlatNoteStore, subLists);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (listsFeed.data) {
|
if (listsFeed.data) {
|
||||||
|
@ -4,7 +4,6 @@ import { useRequestBuilder } from "@snort/system-react";
|
|||||||
|
|
||||||
import { getNewest } from "SnortUtils";
|
import { getNewest } from "SnortUtils";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
export default function useMutedFeed(pubkey?: HexKey) {
|
export default function useMutedFeed(pubkey?: HexKey) {
|
||||||
const { publicKey, muted } = useLogin();
|
const { publicKey, muted } = useLogin();
|
||||||
@ -17,7 +16,7 @@ export default function useMutedFeed(pubkey?: HexKey) {
|
|||||||
return b;
|
return b;
|
||||||
}, [pubkey]);
|
}, [pubkey]);
|
||||||
|
|
||||||
const mutedFeed = useRequestBuilder<NoteCollection>(System, NoteCollection, sub);
|
const mutedFeed = useRequestBuilder(NoteCollection, sub);
|
||||||
|
|
||||||
const mutedList = useMemo(() => {
|
const mutedList = useMemo(() => {
|
||||||
if (pubkey && mutedFeed.data) {
|
if (pubkey && mutedFeed.data) {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { HexKey, FullRelaySettings, EventKind, RequestBuilder, ReplaceableNoteStore } from "@snort/system";
|
import { HexKey, FullRelaySettings, EventKind, RequestBuilder, ReplaceableNoteStore } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
export default function useRelaysFeed(pubkey?: HexKey) {
|
export default function useRelaysFeed(pubkey?: HexKey) {
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
@ -11,7 +10,7 @@ export default function useRelaysFeed(pubkey?: HexKey) {
|
|||||||
return b;
|
return b;
|
||||||
}, [pubkey]);
|
}, [pubkey]);
|
||||||
|
|
||||||
const relays = useRequestBuilder<ReplaceableNoteStore>(System, ReplaceableNoteStore, sub);
|
const relays = useRequestBuilder(ReplaceableNoteStore, sub);
|
||||||
|
|
||||||
if (!relays.data?.content) {
|
if (!relays.data?.content) {
|
||||||
return [] as FullRelaySettings[];
|
return [] as FullRelaySettings[];
|
||||||
|
@ -13,7 +13,6 @@ import debug from "debug";
|
|||||||
|
|
||||||
import { sanitizeRelayUrl } from "SnortUtils";
|
import { sanitizeRelayUrl } from "SnortUtils";
|
||||||
import { UserRelays } from "Cache";
|
import { UserRelays } from "Cache";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
interface RelayList {
|
interface RelayList {
|
||||||
pubkey: string;
|
pubkey: string;
|
||||||
@ -80,7 +79,7 @@ export default function useRelaysFeedFollows(pubkeys: HexKey[]): Array<RelayList
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const relays = useRequestBuilder<NoteCollection>(System, NoteCollection, sub);
|
const relays = useRequestBuilder(NoteCollection, sub);
|
||||||
const notesRelays = relays.data?.filter(a => a.kind === EventKind.Relays) ?? [];
|
const notesRelays = relays.data?.filter(a => a.kind === EventKind.Relays) ?? [];
|
||||||
const notesContactLists = relays.data?.filter(a => a.kind === EventKind.ContactList) ?? [];
|
const notesContactLists = relays.data?.filter(a => a.kind === EventKind.ContactList) ?? [];
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
|
@ -4,7 +4,6 @@ import { useRequestBuilder } from "@snort/system-react";
|
|||||||
|
|
||||||
import { appendDedupe } from "SnortUtils";
|
import { appendDedupe } from "SnortUtils";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
interface RelayTaggedEventId {
|
interface RelayTaggedEventId {
|
||||||
id: u256;
|
id: u256;
|
||||||
@ -58,7 +57,7 @@ export default function useThreadFeed(link: NostrLink) {
|
|||||||
return sub;
|
return sub;
|
||||||
}, [trackingEvents, trackingATags, allEvents, pref]);
|
}, [trackingEvents, trackingATags, allEvents, pref]);
|
||||||
|
|
||||||
const store = useRequestBuilder<FlatNoteStore>(System, FlatNoteStore, sub);
|
const store = useRequestBuilder(FlatNoteStore, sub);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (link.type === NostrPrefix.Address) {
|
if (link.type === NostrPrefix.Address) {
|
||||||
|
@ -5,7 +5,6 @@ import { useRequestBuilder } from "@snort/system-react";
|
|||||||
import { unixNow, unwrap, tagFilterOfTextRepost } from "SnortUtils";
|
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 { System } from "index";
|
|
||||||
import { SearchRelays } from "Const";
|
import { SearchRelays } from "Const";
|
||||||
|
|
||||||
export interface TimelineFeedOptions {
|
export interface TimelineFeedOptions {
|
||||||
@ -117,7 +116,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
return rb?.builder ?? null;
|
return rb?.builder ?? null;
|
||||||
}, [until, since, options.method, pref, createBuilder]);
|
}, [until, since, options.method, pref, createBuilder]);
|
||||||
|
|
||||||
const main = useRequestBuilder<NoteCollection>(System, NoteCollection, sub);
|
const main = useRequestBuilder(NoteCollection, sub);
|
||||||
|
|
||||||
const subRealtime = useMemo(() => {
|
const subRealtime = useMemo(() => {
|
||||||
const rb = createBuilder();
|
const rb = createBuilder();
|
||||||
@ -131,7 +130,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
return rb?.builder ?? null;
|
return rb?.builder ?? null;
|
||||||
}, [pref.autoShowLatest, createBuilder]);
|
}, [pref.autoShowLatest, createBuilder]);
|
||||||
|
|
||||||
const latest = useRequestBuilder<NoteCollection>(System, NoteCollection, subRealtime);
|
const latest = useRequestBuilder(NoteCollection, subRealtime);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// clear store if changing relays
|
// clear store if changing relays
|
||||||
@ -177,7 +176,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
return rb.numFilters > 0 ? rb : null;
|
return rb.numFilters > 0 ? rb : null;
|
||||||
}, [main.data, pref, subject.type]);
|
}, [main.data, pref, subject.type]);
|
||||||
|
|
||||||
const related = useRequestBuilder<NoteCollection>(System, NoteCollection, subNext);
|
const related = useRequestBuilder(NoteCollection, subNext);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
main: main.data,
|
main: main.data,
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { EventKind, RequestBuilder, parseZap, NostrLink, NostrPrefix, NoteCollection } from "@snort/system";
|
import { EventKind, RequestBuilder, parseZap, NostrLink, NostrPrefix, NoteCollection } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
|
||||||
import { System } from "index";
|
|
||||||
import { UserCache } from "Cache";
|
import { UserCache } from "Cache";
|
||||||
|
|
||||||
export default function useZapsFeed(link?: NostrLink) {
|
export default function useZapsFeed(link?: NostrLink) {
|
||||||
@ -17,7 +15,7 @@ export default function useZapsFeed(link?: NostrLink) {
|
|||||||
return b;
|
return b;
|
||||||
}, [link]);
|
}, [link]);
|
||||||
|
|
||||||
const zapsFeed = useRequestBuilder(System, NoteCollection, sub);
|
const zapsFeed = useRequestBuilder(NoteCollection, sub);
|
||||||
|
|
||||||
const zaps = useMemo(() => {
|
const zaps = useMemo(() => {
|
||||||
if (zapsFeed.data) {
|
if (zapsFeed.data) {
|
||||||
|
@ -3,7 +3,6 @@ import { HexKey, Lists, EventKind, FlatNoteStore, NoteCollection, RequestBuilder
|
|||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
export default function useNotelistSubscription(pubkey: HexKey | undefined, l: Lists, defaultIds: HexKey[]) {
|
export default function useNotelistSubscription(pubkey: HexKey | undefined, l: Lists, defaultIds: HexKey[]) {
|
||||||
const { preferences, publicKey } = useLogin();
|
const { preferences, publicKey } = useLogin();
|
||||||
@ -17,7 +16,7 @@ export default function useNotelistSubscription(pubkey: HexKey | undefined, l: L
|
|||||||
return rb;
|
return rb;
|
||||||
}, [pubkey]);
|
}, [pubkey]);
|
||||||
|
|
||||||
const listStore = useRequestBuilder<NoteCollection>(System, NoteCollection, sub);
|
const listStore = useRequestBuilder(NoteCollection, sub);
|
||||||
const etags = useMemo(() => {
|
const etags = useMemo(() => {
|
||||||
if (isMe) return defaultIds;
|
if (isMe) return defaultIds;
|
||||||
// there should only be a single event here because we only load 1 pubkey
|
// there should only be a single event here because we only load 1 pubkey
|
||||||
@ -39,7 +38,7 @@ export default function useNotelistSubscription(pubkey: HexKey | undefined, l: L
|
|||||||
return s;
|
return s;
|
||||||
}, [etags, pubkey, preferences]);
|
}, [etags, pubkey, preferences]);
|
||||||
|
|
||||||
const store = useRequestBuilder<FlatNoteStore>(System, FlatNoteStore, esub);
|
const store = useRequestBuilder(FlatNoteStore, esub);
|
||||||
|
|
||||||
return store.data ?? [];
|
return store.data ?? [];
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
header {
|
header {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 10px 16px;
|
padding: var(--header-padding-tb) 16px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
|
@ -142,7 +142,7 @@ const AccountHeader = () => {
|
|||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
const { publicKey, latestNotification, readNotifications } = useLogin();
|
const { publicKey, latestNotification, readNotifications } = useLogin();
|
||||||
const profile = useUserProfile(System, publicKey);
|
const profile = useUserProfile(publicKey);
|
||||||
|
|
||||||
const hasNotifications = useMemo(
|
const hasNotifications = useMemo(
|
||||||
() => latestNotification > readNotifications,
|
() => latestNotification > readNotifications,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
.dm-page {
|
.dm-page {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 350px auto;
|
grid-template-columns: 350px auto;
|
||||||
height: calc(100vh - 57px);
|
height: calc(100vh - 42px - var(--header-padding-tb) - var(--header-padding-tb) - 16px);
|
||||||
/* 100vh - header - padding */
|
/* 100vh - header - padding */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@ -26,13 +26,14 @@
|
|||||||
/* User list */
|
/* User list */
|
||||||
.dm-page > div:nth-child(1) {
|
.dm-page > div:nth-child(1) {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
margin: 0 10px;
|
padding: 0 5px;
|
||||||
padding: 0 10px 0 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chat window */
|
/* Chat window */
|
||||||
.dm-page > div:nth-child(2) {
|
.dm-page > div:nth-child(2) {
|
||||||
height: calc(100vh - 57px);
|
padding: 0 12px;
|
||||||
|
background-color: var(--gray-superdark);
|
||||||
|
border-radius: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Profile pannel */
|
/* Profile pannel */
|
||||||
@ -48,3 +49,53 @@
|
|||||||
.dm-page > div:nth-child(3) .card {
|
.dm-page > div:nth-child(3) .card {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dm-page .new-chat {
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-page .chat-list > div.active {
|
||||||
|
background-color: var(--gray-superdark);
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-chat-modal .user-list {
|
||||||
|
max-height: 50vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-chat-modal .user-list > div {
|
||||||
|
padding: 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* user in list selected */
|
||||||
|
.new-chat-modal .user-list > div.active {
|
||||||
|
background-color: var(--gray-dark);
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-chat-modal .modal-body {
|
||||||
|
padding: 24px 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-chat-modal h2,
|
||||||
|
.new-chat-modal h3,
|
||||||
|
.new-chat-modal p {
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-chat-modal h2 {
|
||||||
|
font-size: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-chat-modal h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-chat-modal p {
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 1.21px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
|
import "./MessagesPage.css";
|
||||||
|
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { TLVEntryType, decodeTLV } from "@snort/system";
|
import { TLVEntryType, decodeTLV } from "@snort/system";
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile, useUserSearch } from "@snort/system-react";
|
||||||
|
|
||||||
import UnreadCount from "Element/UnreadCount";
|
import UnreadCount from "Element/UnreadCount";
|
||||||
import ProfileImage, { getDisplayName } from "Element/ProfileImage";
|
import ProfileImage, { getDisplayName } from "Element/ProfileImage";
|
||||||
import { parseId } from "SnortUtils";
|
import { appendDedupe, debounce, parseId } from "SnortUtils";
|
||||||
import NoteToSelf from "Element/NoteToSelf";
|
import NoteToSelf from "Element/NoteToSelf";
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
@ -16,11 +18,9 @@ import DmWindow from "Element/DmWindow";
|
|||||||
import Avatar from "Element/Avatar";
|
import Avatar from "Element/Avatar";
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import Text from "Element/Text";
|
import Text from "Element/Text";
|
||||||
import { System } from "index";
|
import { Chat, ChatType, createChatLink, useChatSystem } from "chat";
|
||||||
import { Chat, ChatType, useChatSystem } from "chat";
|
import Modal from "Element/Modal";
|
||||||
|
import ProfilePreview from "Element/ProfilePreview";
|
||||||
import "./MessagesPage.css";
|
|
||||||
import messages from "./messages";
|
|
||||||
|
|
||||||
const TwoCol = 768;
|
const TwoCol = 768;
|
||||||
const ThreeCol = 1500;
|
const ThreeCol = 1500;
|
||||||
@ -49,15 +49,15 @@ export default function MessagesPage() {
|
|||||||
|
|
||||||
function noteToSelf(chat: Chat) {
|
function noteToSelf(chat: Chat) {
|
||||||
return (
|
return (
|
||||||
<div className="flex mb10" key={chat.id} onClick={e => openChat(e, chat.type, chat.id)}>
|
<div className="flex p" key={chat.id} onClick={e => openChat(e, chat.type, chat.id)}>
|
||||||
<NoteToSelf clickable={true} className="f-grow" link="" pubkey={chat.id} />
|
<NoteToSelf clickable={true} className="f-grow" link="" pubkey={chat.id} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function conversationIdent(chat: Chat) {
|
function conversationIdent(cx: Chat) {
|
||||||
if (chat.participants.length === 1) {
|
if (cx.participants.length === 1) {
|
||||||
const p = chat.participants[0];
|
const p = cx.participants[0];
|
||||||
|
|
||||||
if (p.type === "pubkey") {
|
if (p.type === "pubkey") {
|
||||||
return <ProfileImage pubkey={p.id} className="f-grow" link="" />;
|
return <ProfileImage pubkey={p.id} className="f-grow" link="" />;
|
||||||
@ -67,27 +67,29 @@ export default function MessagesPage() {
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div className="flex f-grow pfp-overlap">
|
<div className="flex f-grow pfp-overlap">
|
||||||
{chat.participants.map(v => (
|
{cx.participants.map(v => (
|
||||||
<ProfileImage pubkey={v.id} link="" showUsername={false} />
|
<ProfileImage pubkey={v.id} link="" showUsername={false} />
|
||||||
))}
|
))}
|
||||||
<div className="f-grow">{chat.title}</div>
|
{cx.title ?? <FormattedMessage defaultMessage="Group Chat" />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function conversation(chat: Chat) {
|
function conversation(cx: Chat) {
|
||||||
if (!login.publicKey) return null;
|
if (!login.publicKey) return null;
|
||||||
const participants = chat.participants.map(a => a.id);
|
const participants = cx.participants.map(a => a.id);
|
||||||
if (participants.length === 1 && participants[0] === login.publicKey) return noteToSelf(chat);
|
if (participants.length === 1 && participants[0] === login.publicKey) return noteToSelf(cx);
|
||||||
|
|
||||||
|
const isActive = cx.id === chat;
|
||||||
return (
|
return (
|
||||||
<div className="flex mb10" key={chat.id} onClick={e => openChat(e, chat.type, chat.id)}>
|
<div className={`flex p${isActive ? " active" : ""}`} key={cx.id} onClick={e => openChat(e, cx.type, cx.id)}>
|
||||||
{conversationIdent(chat)}
|
{conversationIdent(cx)}
|
||||||
<div className="nowrap">
|
<div className="nowrap">
|
||||||
<small>
|
<small>
|
||||||
<NoteTime from={chat.lastMessage * 1000} fallback={formatMessage({ defaultMessage: "Just now" })} />
|
<NoteTime from={cx.lastMessage * 1000} fallback={formatMessage({ defaultMessage: "Just now" })} />
|
||||||
</small>
|
</small>
|
||||||
{chat.unread > 0 && <UnreadCount unread={chat.unread} />}
|
{cx.unread > 0 && <UnreadCount unread={cx.unread} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -96,14 +98,12 @@ export default function MessagesPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="dm-page">
|
<div className="dm-page">
|
||||||
{(pageWidth >= TwoCol || !chat) && (
|
{(pageWidth >= TwoCol || !chat) && (
|
||||||
<div>
|
<div className="chat-list">
|
||||||
<div className="flex">
|
<div className="flex p f-space">
|
||||||
<h3 className="f-grow">
|
|
||||||
<FormattedMessage {...messages.Messages} />
|
|
||||||
</h3>
|
|
||||||
<button disabled={unreadCount <= 0} type="button">
|
<button disabled={unreadCount <= 0} type="button">
|
||||||
<FormattedMessage {...messages.MarkAllRead} />
|
<FormattedMessage defaultMessage="Mark all read" />
|
||||||
</button>
|
</button>
|
||||||
|
<NewChatWindow />
|
||||||
</div>
|
</div>
|
||||||
{chats
|
{chats
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
@ -117,7 +117,7 @@ export default function MessagesPage() {
|
|||||||
.map(conversation)}
|
.map(conversation)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{chat && <DmWindow id={chat} />}
|
{chat ? <DmWindow id={chat} /> : pageWidth >= TwoCol && <div></div>}
|
||||||
{pageWidth >= ThreeCol && chat && (
|
{pageWidth >= ThreeCol && chat && (
|
||||||
<div>
|
<div>
|
||||||
<ProfileDmActions id={chat} />
|
<ProfileDmActions id={chat} />
|
||||||
@ -132,7 +132,7 @@ function ProfileDmActions({ id }: { id: string }) {
|
|||||||
.filter(a => a.type === TLVEntryType.Author)
|
.filter(a => a.type === TLVEntryType.Author)
|
||||||
.map(a => a.value as string);
|
.map(a => a.value as string);
|
||||||
const pubkey = authors[0];
|
const pubkey = authors[0];
|
||||||
const profile = useUserProfile(System, pubkey);
|
const profile = useUserProfile(pubkey);
|
||||||
const { block, unblock, isBlocked } = useModeration();
|
const { block, unblock, isBlocked } = useModeration();
|
||||||
|
|
||||||
function truncAbout(s?: string) {
|
function truncAbout(s?: string) {
|
||||||
@ -158,3 +158,105 @@ function ProfileDmActions({ id }: { id: string }) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function NewChatWindow() {
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
|
const [newChat, setNewChat] = useState<string[]>([]);
|
||||||
|
const [results, setResults] = useState<string[]>([]);
|
||||||
|
const [term, setSearchTerm] = useState("");
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const search = useUserSearch();
|
||||||
|
const { follows } = useLogin();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setNewChat([]);
|
||||||
|
setSearchTerm("");
|
||||||
|
setResults(follows.item);
|
||||||
|
}, [show]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return debounce(500, () => {
|
||||||
|
if (term) {
|
||||||
|
search(term).then(setResults);
|
||||||
|
} else {
|
||||||
|
setResults(follows.item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [term]);
|
||||||
|
|
||||||
|
function togglePubkey(a: string) {
|
||||||
|
setNewChat(c => (c.includes(a) ? c.filter(v => v !== a) : appendDedupe(c, [a])));
|
||||||
|
}
|
||||||
|
|
||||||
|
function startChat() {
|
||||||
|
setShow(false);
|
||||||
|
if (newChat.length === 1) {
|
||||||
|
navigate(createChatLink(ChatType.DirectMessage, newChat[0]));
|
||||||
|
} else {
|
||||||
|
navigate(createChatLink(ChatType.PrivateGroupChat, ...newChat));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button type="button" className="new-chat" onClick={() => setShow(true)}>
|
||||||
|
<Icon name="plus" size={16} />
|
||||||
|
</button>
|
||||||
|
{show && (
|
||||||
|
<Modal onClose={() => setShow(false)} className="new-chat-modal">
|
||||||
|
<div className="flex-column g16">
|
||||||
|
<div className="flex f-space">
|
||||||
|
<h2>
|
||||||
|
<FormattedMessage defaultMessage="New Chat" />
|
||||||
|
</h2>
|
||||||
|
<button onClick={startChat}>
|
||||||
|
<FormattedMessage defaultMessage="Start chat" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex-column g8">
|
||||||
|
<h3>
|
||||||
|
<FormattedMessage defaultMessage="Search users" />
|
||||||
|
</h3>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="npub/nprofile/nostr address"
|
||||||
|
value={term}
|
||||||
|
onChange={e => setSearchTerm(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
{newChat.map(a => (
|
||||||
|
<ProfileImage
|
||||||
|
key={`selected-${a}`}
|
||||||
|
pubkey={a}
|
||||||
|
showUsername={false}
|
||||||
|
link=""
|
||||||
|
onClick={() => togglePubkey(a)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<FormattedMessage defaultMessage="People you follow" />
|
||||||
|
</p>
|
||||||
|
<div className="user-list flex-column g2">
|
||||||
|
{results.map(a => {
|
||||||
|
return (
|
||||||
|
<ProfilePreview
|
||||||
|
pubkey={a}
|
||||||
|
key={`option-${a}`}
|
||||||
|
options={{ about: false, linkToProfile: false }}
|
||||||
|
actions={<></>}
|
||||||
|
onClick={() => togglePubkey(a)}
|
||||||
|
className={newChat.includes(a) ? "active" : undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
NostrEvent,
|
NostrEvent,
|
||||||
NostrLink,
|
NostrLink,
|
||||||
NostrPrefix,
|
NostrPrefix,
|
||||||
TaggedRawEvent,
|
TaggedNostrEvent,
|
||||||
createNostrLink,
|
createNostrLink,
|
||||||
parseZap,
|
parseZap,
|
||||||
} from "@snort/system";
|
} from "@snort/system";
|
||||||
@ -22,13 +22,12 @@ import { dedupe, findTag, orderDescending } from "SnortUtils";
|
|||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import ProfileImage, { getDisplayName } from "Element/ProfileImage";
|
import ProfileImage, { getDisplayName } from "Element/ProfileImage";
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
import { System } from "index";
|
|
||||||
import useEventFeed from "Feed/EventFeed";
|
import useEventFeed from "Feed/EventFeed";
|
||||||
import Text from "Element/Text";
|
import Text from "Element/Text";
|
||||||
import { formatShort } from "Number";
|
import { formatShort } from "Number";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
function notificationContext(ev: TaggedRawEvent) {
|
function notificationContext(ev: TaggedNostrEvent) {
|
||||||
switch (ev.kind) {
|
switch (ev.kind) {
|
||||||
case EventKind.ZapReceipt: {
|
case EventKind.ZapReceipt: {
|
||||||
const aTag = findTag(ev, "a");
|
const aTag = findTag(ev, "a");
|
||||||
@ -88,14 +87,14 @@ export default function NotificationsPage() {
|
|||||||
return orderDescending([...notifications])
|
return orderDescending([...notifications])
|
||||||
.filter(a => !isMuted(a.pubkey) && findTag(a, "p") === login.publicKey)
|
.filter(a => !isMuted(a.pubkey) && findTag(a, "p") === login.publicKey)
|
||||||
.reduce((acc, v) => {
|
.reduce((acc, v) => {
|
||||||
const key = `${timeKey(v)}:${notificationContext(v as TaggedRawEvent)?.encode()}:${v.kind}`;
|
const key = `${timeKey(v)}:${notificationContext(v as TaggedNostrEvent)?.encode()}:${v.kind}`;
|
||||||
if (acc.has(key)) {
|
if (acc.has(key)) {
|
||||||
unwrap(acc.get(key)).push(v as TaggedRawEvent);
|
unwrap(acc.get(key)).push(v as TaggedNostrEvent);
|
||||||
} else {
|
} else {
|
||||||
acc.set(key, [v as TaggedRawEvent]);
|
acc.set(key, [v as TaggedNostrEvent]);
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, new Map<string, Array<TaggedRawEvent>>());
|
}, new Map<string, Array<TaggedNostrEvent>>());
|
||||||
}, [notifications]);
|
}, [notifications]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -105,7 +104,7 @@ export default function NotificationsPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function NotificationGroup({ evs }: { evs: Array<TaggedRawEvent> }) {
|
function NotificationGroup({ evs }: { evs: Array<TaggedNostrEvent> }) {
|
||||||
const { ref, inView } = useInView({ triggerOnce: true });
|
const { ref, inView } = useInView({ triggerOnce: true });
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const kind = evs[0].kind;
|
const kind = evs[0].kind;
|
||||||
@ -123,7 +122,7 @@ function NotificationGroup({ evs }: { evs: Array<TaggedRawEvent> }) {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
const firstPubkey = pubkeys[0];
|
const firstPubkey = pubkeys[0];
|
||||||
const firstPubkeyProfile = useUserProfile(System, inView ? (firstPubkey === "anon" ? "" : firstPubkey) : "");
|
const firstPubkeyProfile = useUserProfile(inView ? (firstPubkey === "anon" ? "" : firstPubkey) : "");
|
||||||
const context = notificationContext(evs[0]);
|
const context = notificationContext(evs[0]);
|
||||||
const totalZaps = zaps.reduce((acc, v) => acc + v.amount, 0);
|
const totalZaps = zaps.reduce((acc, v) => acc + v.amount, 0);
|
||||||
|
|
||||||
@ -187,13 +186,13 @@ function NotificationGroup({ evs }: { evs: Array<TaggedRawEvent> }) {
|
|||||||
<div className="card notification-group" ref={ref}>
|
<div className="card notification-group" ref={ref}>
|
||||||
{inView && (
|
{inView && (
|
||||||
<>
|
<>
|
||||||
<div className="flex f-col g12">
|
<div className="flex-column g12">
|
||||||
<div>
|
<div>
|
||||||
<Icon name={iconName()} size={24} className={iconName()} />
|
<Icon name={iconName()} size={24} className={iconName()} />
|
||||||
</div>
|
</div>
|
||||||
<div>{kind === EventKind.ZapReceipt && formatShort(totalZaps)}</div>
|
<div>{kind === EventKind.ZapReceipt && formatShort(totalZaps)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex f-col g12 w-max">
|
<div className="flex-column g12">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{pubkeys
|
{pubkeys
|
||||||
.filter(a => a !== "anon")
|
.filter(a => a !== "anon")
|
||||||
|
@ -56,7 +56,6 @@ import { getNip05PubKey } from "Pages/LoginPage";
|
|||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
const NOTES = 0;
|
const NOTES = 0;
|
||||||
const REACTIONS = 1;
|
const REACTIONS = 1;
|
||||||
@ -113,7 +112,7 @@ export default function ProfilePage() {
|
|||||||
const params = useParams();
|
const params = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [id, setId] = useState<string>();
|
const [id, setId] = useState<string>();
|
||||||
const user = useUserProfile(System, id);
|
const user = useUserProfile(id);
|
||||||
const loginPubKey = useLogin().publicKey;
|
const loginPubKey = useLogin().publicKey;
|
||||||
const isMe = loginPubKey === id;
|
const isMe = loginPubKey === id;
|
||||||
const [showLnQr, setShowLnQr] = useState<boolean>(false);
|
const [showLnQr, setShowLnQr] = useState<boolean>(false);
|
||||||
|
@ -35,7 +35,7 @@ const DataProviders = [
|
|||||||
|
|
||||||
function ZapTarget({ target }: { target: ZapPoolRecipient }) {
|
function ZapTarget({ target }: { target: ZapPoolRecipient }) {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const profile = useUserProfile(System, target.pubkey);
|
const profile = useUserProfile(target.pubkey);
|
||||||
const hasAddress = profile?.lud16 || profile?.lud06;
|
const hasAddress = profile?.lud16 || profile?.lud06;
|
||||||
const defaultZapMount = Math.ceil(login.preferences.defaultZapAmount * (target.split / 100));
|
const defaultZapMount = Math.ceil(login.preferences.defaultZapAmount * (target.split / 100));
|
||||||
return (
|
return (
|
||||||
|
@ -10,12 +10,11 @@ import ProfileImage from "Element/ProfileImage";
|
|||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
export default function GetVerified() {
|
export default function GetVerified() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { publicKey } = useLogin();
|
const { publicKey } = useLogin();
|
||||||
const user = useUserProfile(System, publicKey);
|
const user = useUserProfile(publicKey);
|
||||||
const [isVerified, setIsVerified] = useState(false);
|
const [isVerified, setIsVerified] = useState(false);
|
||||||
const name = user?.name || "nostrich";
|
const name = user?.name || "nostrich";
|
||||||
const [nip05, setNip05] = useState(`${name}@snort.social`);
|
const [nip05, setNip05] = useState(`${name}@snort.social`);
|
||||||
|
@ -16,7 +16,7 @@ import messages from "./messages";
|
|||||||
|
|
||||||
export default function ProfileSetup() {
|
export default function ProfileSetup() {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const myProfile = useUserProfile(System, login.publicKey);
|
const myProfile = useUserProfile(login.publicKey);
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [picture, setPicture] = useState("");
|
const [picture, setPicture] = useState("");
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
@ -24,7 +24,7 @@ export interface ProfileSettingsProps {
|
|||||||
export default function ProfileSettings(props: ProfileSettingsProps) {
|
export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { publicKey: id } = useLogin();
|
const { publicKey: id } = useLogin();
|
||||||
const user = useUserProfile(System, id ?? "");
|
const user = useUserProfile(id ?? "");
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
const uploader = useFileUpload();
|
const uploader = useFileUpload();
|
||||||
|
|
||||||
|
@ -6,14 +6,13 @@ import Icon from "Icons/Icon";
|
|||||||
import { UITask } from "Tasks";
|
import { UITask } from "Tasks";
|
||||||
import { DonateTask } from "./DonateTask";
|
import { DonateTask } from "./DonateTask";
|
||||||
import { Nip5Task } from "./Nip5Task";
|
import { Nip5Task } from "./Nip5Task";
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
const AllTasks: Array<UITask> = [new Nip5Task(), new DonateTask()];
|
const AllTasks: Array<UITask> = [new Nip5Task(), new DonateTask()];
|
||||||
AllTasks.forEach(a => a.load());
|
AllTasks.forEach(a => a.load());
|
||||||
|
|
||||||
export const TaskList = () => {
|
export const TaskList = () => {
|
||||||
const publicKey = useLogin().publicKey;
|
const publicKey = useLogin().publicKey;
|
||||||
const user = useUserProfile(System, publicKey);
|
const user = useUserProfile(publicKey);
|
||||||
const [, setTick] = useState<number>(0);
|
const [, setTick] = useState<number>(0);
|
||||||
|
|
||||||
function muteTask(t: UITask) {
|
function muteTask(t: UITask) {
|
||||||
|
@ -4,10 +4,14 @@ import {
|
|||||||
EventKind,
|
EventKind,
|
||||||
EventPublisher,
|
EventPublisher,
|
||||||
NostrEvent,
|
NostrEvent,
|
||||||
|
NostrPrefix,
|
||||||
RequestBuilder,
|
RequestBuilder,
|
||||||
SystemInterface,
|
SystemInterface,
|
||||||
|
TLVEntry,
|
||||||
|
TLVEntryType,
|
||||||
TaggedRawEvent,
|
TaggedRawEvent,
|
||||||
UserMetadata,
|
UserMetadata,
|
||||||
|
encodeTLVEntries,
|
||||||
} from "@snort/system";
|
} from "@snort/system";
|
||||||
import { unwrap } from "@snort/shared";
|
import { unwrap } from "@snort/shared";
|
||||||
import { Chats, GiftsCache } from "Cache";
|
import { Chats, GiftsCache } from "Cache";
|
||||||
@ -102,6 +106,57 @@ export function setLastReadIn(id: string) {
|
|||||||
window.localStorage.setItem(k, now.toString());
|
window.localStorage.setItem(k, now.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createChatLink(type: ChatType, ...params: Array<string>) {
|
||||||
|
switch (type) {
|
||||||
|
case ChatType.DirectMessage: {
|
||||||
|
if (params.length > 1) throw new Error("Must only contain one pubkey");
|
||||||
|
return `/messages/${encodeTLVEntries(
|
||||||
|
"chat4" as NostrPrefix,
|
||||||
|
{
|
||||||
|
type: TLVEntryType.Author,
|
||||||
|
length: params[0].length,
|
||||||
|
value: params[0],
|
||||||
|
} as TLVEntry
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
case ChatType.PrivateDirectMessage: {
|
||||||
|
if (params.length > 1) throw new Error("Must only contain one pubkey");
|
||||||
|
return `/messages/${encodeTLVEntries(
|
||||||
|
"chat24" as NostrPrefix,
|
||||||
|
{
|
||||||
|
type: TLVEntryType.Author,
|
||||||
|
length: params[0].length,
|
||||||
|
value: params[0],
|
||||||
|
} as TLVEntry
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
case ChatType.PrivateGroupChat: {
|
||||||
|
return `/messages/${encodeTLVEntries(
|
||||||
|
"chat24" as NostrPrefix,
|
||||||
|
...params.map(
|
||||||
|
a =>
|
||||||
|
({
|
||||||
|
type: TLVEntryType.Author,
|
||||||
|
length: a.length,
|
||||||
|
value: a,
|
||||||
|
} as TLVEntry)
|
||||||
|
)
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("Unknown chat type");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEmptyChatObject(id: string) {
|
||||||
|
if (id.startsWith("chat4")) {
|
||||||
|
return Nip4ChatSystem.createChatObj(id, []);
|
||||||
|
}
|
||||||
|
if (id.startsWith("chat24")) {
|
||||||
|
return Nip24ChatSystem.createChatObj(id, []);
|
||||||
|
}
|
||||||
|
throw new Error("Cant create new empty chat, unknown id");
|
||||||
|
}
|
||||||
|
|
||||||
export function useNip4Chat() {
|
export function useNip4Chat() {
|
||||||
const { publicKey } = useLogin();
|
const { publicKey } = useLogin();
|
||||||
return useSyncExternalStore(
|
return useSyncExternalStore(
|
||||||
|
@ -68,7 +68,10 @@ export class Nip24ChatSystem extends ExternalStore<Array<Chat>> implements ChatS
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
t: 0,
|
t: 0,
|
||||||
title: "",
|
title: undefined,
|
||||||
|
} as {
|
||||||
|
t: number;
|
||||||
|
title: string | undefined;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
|
@ -42,6 +42,8 @@
|
|||||||
);
|
);
|
||||||
--expired-invoice-gradient: linear-gradient(45deg, var(--gray-superdark) 50%, var(--gray), var(--gray-superdark));
|
--expired-invoice-gradient: linear-gradient(45deg, var(--gray-superdark) 50%, var(--gray), var(--gray-superdark));
|
||||||
--strike-army-gradient: linear-gradient(to bottom right, #ccff00, #a1c900);
|
--strike-army-gradient: linear-gradient(to bottom right, #ccff00, #a1c900);
|
||||||
|
|
||||||
|
--header-padding-tb: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
@ -115,10 +117,6 @@ code {
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
body #root > div:not(.page) header {
|
|
||||||
padding: 2px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.page {
|
.page {
|
||||||
width: 640px;
|
width: 640px;
|
||||||
@ -411,6 +409,10 @@ input:disabled {
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.g16 {
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.g24 {
|
.g24 {
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
}
|
}
|
||||||
@ -681,8 +683,8 @@ button.tall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rta__textarea {
|
.rta__textarea {
|
||||||
/* Fix width calculation to account for 12px padding on input */
|
/* Fix width calculation to account for 32px padding on input */
|
||||||
width: calc(100% - 24px) !important;
|
width: calc(100% - 32px) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ctx-menu {
|
.ctx-menu {
|
||||||
|
@ -34,6 +34,7 @@ import DebugPage from "Pages/Debug";
|
|||||||
import { db } from "Db";
|
import { db } from "Db";
|
||||||
import { preload, RelayMetrics, UserCache, UserRelays } from "Cache";
|
import { preload, RelayMetrics, UserCache, UserRelays } from "Cache";
|
||||||
import { LoginStore } from "Login";
|
import { LoginStore } from "Login";
|
||||||
|
import { SnortContext } from "@snort/system-react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Singleton nostr system
|
* Singleton nostr system
|
||||||
@ -164,7 +165,9 @@ root.render(
|
|||||||
<StrictMode>
|
<StrictMode>
|
||||||
<Provider store={Store}>
|
<Provider store={Store}>
|
||||||
<IntlProvider>
|
<IntlProvider>
|
||||||
<RouterProvider router={router} />
|
<SnortContext.Provider value={System}>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</SnortContext.Provider>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
|
@ -164,7 +164,7 @@ export function bech32ToText(str: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchNip05Pubkey(name: string, domain: string, timeout = 2_000) {
|
export async function fetchNip05Pubkey(name: string, domain: string, timeout = 2_000): Promise<string | undefined> {
|
||||||
interface NostrJson {
|
interface NostrJson {
|
||||||
names: Record<string, string>;
|
names: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useRequestBuilder, useUserProfile } from "../src";
|
import { SnortContext, useRequestBuilder, useUserProfile } from "../src";
|
||||||
|
|
||||||
import { FlatNoteStore, NostrSystem, RequestBuilder, TaggedNostrEvent } from "@snort/system";
|
import { NostrSystem, NoteCollection, RequestBuilder, TaggedNostrEvent } from "@snort/system";
|
||||||
|
|
||||||
const System = new NostrSystem({});
|
const System = new NostrSystem({});
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ const System = new NostrSystem({});
|
|||||||
["wss://relay.snort.social", "wss://nos.lol"].forEach(r => System.ConnectToRelay(r, { read: true, write: false }));
|
["wss://relay.snort.social", "wss://nos.lol"].forEach(r => System.ConnectToRelay(r, { read: true, write: false }));
|
||||||
|
|
||||||
export function Note({ ev }: { ev: TaggedNostrEvent }) {
|
export function Note({ ev }: { ev: TaggedNostrEvent }) {
|
||||||
const profile = useUserProfile(System, ev.pubkey);
|
const profile = useUserProfile(ev.pubkey);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -27,7 +27,7 @@ export function UserPosts(props: { pubkey: string }) {
|
|||||||
return rb;
|
return rb;
|
||||||
}, [props.pubkey]);
|
}, [props.pubkey]);
|
||||||
|
|
||||||
const data = useRequestBuilder<FlatNoteStore>(System, FlatNoteStore, sub);
|
const data = useRequestBuilder(NoteCollection, sub);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{data.data.map(a => (
|
{data.data.map(a => (
|
||||||
@ -38,5 +38,7 @@ export function UserPosts(props: { pubkey: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function MyApp() {
|
export function MyApp() {
|
||||||
return <UserPosts pubkey="63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed" />;
|
return <SnortContext.Provider value={System}>
|
||||||
|
<UserPosts pubkey="63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed" />
|
||||||
|
</SnortContext.Provider>;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@snort/system-react",
|
"name": "@snort/system-react",
|
||||||
"version": "1.0.11",
|
"version": "1.0.12",
|
||||||
"description": "React hooks for @snort/system",
|
"description": "React hooks for @snort/system",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
@ -15,10 +15,12 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@snort/shared": "^1.0.4",
|
|
||||||
"@snort/system": "^1.0.16",
|
|
||||||
"react": "^18.2.0"
|
"react": "^18.2.0"
|
||||||
},
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@snort/shared": "^1.0.4",
|
||||||
|
"@snort/system": "^1.0.17"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.2.14",
|
"@types/react": "^18.2.14",
|
||||||
"typescript": "^5.1.6"
|
"typescript": "^5.1.6"
|
||||||
|
4
packages/system-react/src/context.tsx
Normal file
4
packages/system-react/src/context.tsx
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { createContext } from "react";
|
||||||
|
import { NostrSystem, SystemInterface } from "@snort/system";
|
||||||
|
|
||||||
|
export const SnortContext = createContext<SystemInterface>(new NostrSystem({}));
|
@ -1,3 +1,5 @@
|
|||||||
export * from "./useRequestBuilder";
|
export * from "./useRequestBuilder";
|
||||||
export * from "./useSystemState";
|
export * from "./useSystemState";
|
||||||
export * from "./useUserProfile";
|
export * from "./useUserProfile";
|
||||||
|
export * from "./context";
|
||||||
|
export * from "./useUserSearch";
|
@ -1,15 +1,16 @@
|
|||||||
import { useSyncExternalStore } from "react";
|
import { useContext, useSyncExternalStore } from "react";
|
||||||
import { RequestBuilder, EmptySnapshot, NoteStore, StoreSnapshot, SystemInterface } from "@snort/system";
|
import { RequestBuilder, EmptySnapshot, NoteStore, StoreSnapshot } from "@snort/system";
|
||||||
import { unwrap } from "@snort/shared";
|
import { unwrap } from "@snort/shared";
|
||||||
|
import { SnortContext } from "./context";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a query to the relays and wait for data
|
* Send a query to the relays and wait for data
|
||||||
*/
|
*/
|
||||||
const useRequestBuilder = <TStore extends NoteStore, TSnapshot = ReturnType<TStore["getSnapshotData"]>>(
|
const useRequestBuilder = <TStore extends NoteStore, TSnapshot = ReturnType<TStore["getSnapshotData"]>>(
|
||||||
system: SystemInterface,
|
type: { new(): TStore },
|
||||||
type: { new (): TStore },
|
|
||||||
rb: RequestBuilder | null,
|
rb: RequestBuilder | null,
|
||||||
) => {
|
) => {
|
||||||
|
const system = useContext(SnortContext);
|
||||||
const subscribe = (onChanged: () => void) => {
|
const subscribe = (onChanged: () => void) => {
|
||||||
if (rb) {
|
if (rb) {
|
||||||
const q = system.Query<TStore>(type, rb);
|
const q = system.Query<TStore>(type, rb);
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { useSyncExternalStore } from "react";
|
import { useContext, useSyncExternalStore } from "react";
|
||||||
import { HexKey, MetadataCache, NostrSystem } from "@snort/system";
|
import { HexKey, MetadataCache } from "@snort/system";
|
||||||
|
import { SnortContext } from "./context";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a profile from cache or requests it from the relays
|
* Gets a profile from cache or requests it from the relays
|
||||||
*/
|
*/
|
||||||
export function useUserProfile(system: NostrSystem, pubKey?: HexKey): MetadataCache | undefined {
|
export function useUserProfile(pubKey?: HexKey): MetadataCache | undefined {
|
||||||
|
const system = useContext(SnortContext);
|
||||||
return useSyncExternalStore<MetadataCache | undefined>(
|
return useSyncExternalStore<MetadataCache | undefined>(
|
||||||
h => {
|
h => {
|
||||||
if (pubKey) {
|
if (pubKey) {
|
||||||
|
37
packages/system-react/src/useUserSearch.tsx
Normal file
37
packages/system-react/src/useUserSearch.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { NostrPrefix, UserProfileCache, tryParseNostrLink } from "@snort/system";
|
||||||
|
import { fetchNip05Pubkey } from "@snort/shared";
|
||||||
|
import { SnortContext } from "./context";
|
||||||
|
|
||||||
|
export function useUserSearch() {
|
||||||
|
const system = useContext(SnortContext);
|
||||||
|
const cache = system.ProfileLoader.Cache as UserProfileCache;
|
||||||
|
|
||||||
|
async function search(input: string): Promise<Array<string>> {
|
||||||
|
// try exact match first
|
||||||
|
if (input.length === 64 && [...input].every(c => !isNaN(parseInt(c, 16)))) {
|
||||||
|
return [input];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.startsWith(NostrPrefix.PublicKey) || input.startsWith(NostrPrefix.Profile)) {
|
||||||
|
const link = tryParseNostrLink(input);
|
||||||
|
if (link) {
|
||||||
|
return [link.id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.includes("@")) {
|
||||||
|
const [name, domain] = input.split("@");
|
||||||
|
const pk = await fetchNip05Pubkey(name, domain);
|
||||||
|
if (pk) {
|
||||||
|
return [pk];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// search cache
|
||||||
|
const cacheResults = await cache.search(input);
|
||||||
|
return cacheResults.map(v => v.pubkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return search;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@snort/system",
|
"name": "@snort/system",
|
||||||
"version": "1.0.16",
|
"version": "1.0.17",
|
||||||
"description": "Snort nostr system package",
|
"description": "Snort nostr system package",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
@ -30,10 +30,12 @@
|
|||||||
"@noble/curves": "^1.0.0",
|
"@noble/curves": "^1.0.0",
|
||||||
"@noble/hashes": "^1.3.1",
|
"@noble/hashes": "^1.3.1",
|
||||||
"@scure/base": "^1.1.1",
|
"@scure/base": "^1.1.1",
|
||||||
"@snort/shared": "^1.0.4",
|
|
||||||
"@stablelib/xchacha20": "^1.0.1",
|
"@stablelib/xchacha20": "^1.0.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"dexie": "^3.2.4",
|
"dexie": "^3.2.4",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@snort/shared": "^1.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import { RequestBuilder } from "./request-builder";
|
|||||||
import { NoteStore } from "./note-collection";
|
import { NoteStore } from "./note-collection";
|
||||||
import { Query } from "./query";
|
import { Query } from "./query";
|
||||||
import { NostrEvent, ReqFilter } from "./nostr";
|
import { NostrEvent, ReqFilter } from "./nostr";
|
||||||
|
import { ProfileLoaderService } from "./profile-cache";
|
||||||
|
|
||||||
export * from "./nostr-system";
|
export * from "./nostr-system";
|
||||||
export { default as EventKind } from "./event-kind";
|
export { default as EventKind } from "./event-kind";
|
||||||
@ -46,6 +47,7 @@ export interface SystemInterface {
|
|||||||
DisconnectRelay(address: string): void;
|
DisconnectRelay(address: string): void;
|
||||||
BroadcastEvent(ev: NostrEvent): void;
|
BroadcastEvent(ev: NostrEvent): void;
|
||||||
WriteOnceToRelay(relay: string, ev: NostrEvent): Promise<void>;
|
WriteOnceToRelay(relay: string, ev: NostrEvent): Promise<void>;
|
||||||
|
get ProfileLoader(): ProfileLoaderService
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SystemSnapshot {
|
export interface SystemSnapshot {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ExternalStore } from "@snort/shared";
|
import { ExternalStore } from "@snort/shared";
|
||||||
|
|
||||||
import { SystemSnapshot, SystemInterface } from ".";
|
import { SystemSnapshot, SystemInterface, ProfileLoaderService } from ".";
|
||||||
import { AuthHandler, ConnectionStateSnapshot, RelaySettings } from "./connection";
|
import { AuthHandler, ConnectionStateSnapshot, RelaySettings } from "./connection";
|
||||||
import { NostrEvent } from "./nostr";
|
import { NostrEvent } from "./nostr";
|
||||||
import { NoteStore } from "./note-collection";
|
import { NoteStore } from "./note-collection";
|
||||||
@ -20,6 +20,10 @@ export class SystemWorker extends ExternalStore<SystemSnapshot> implements Syste
|
|||||||
throw new Error("SharedWorker is not supported");
|
throw new Error("SharedWorker is not supported");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get ProfileLoader(): ProfileLoaderService {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
HandleAuth?: AuthHandler;
|
HandleAuth?: AuthHandler;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user