feat: mute list
This commit is contained in:
@ -7,3 +7,11 @@ export const USER_EMOJIS = 10_030 as EventKind;
|
|||||||
export const GOAL = 9041 as EventKind;
|
export const GOAL = 9041 as EventKind;
|
||||||
export const USER_CARDS = 17_777 as EventKind;
|
export const USER_CARDS = 17_777 as EventKind;
|
||||||
export const CARD = 37_777 as EventKind;
|
export const CARD = 37_777 as EventKind;
|
||||||
|
export const MUTED = 10_000 as EventKind;
|
||||||
|
|
||||||
|
export const defaultRelays = {
|
||||||
|
"wss://relay.snort.social": { read: true, write: true },
|
||||||
|
"wss://nos.lol": { read: true, write: true },
|
||||||
|
"wss://relay.damus.io": { read: true, write: true },
|
||||||
|
"wss://nostr.wine": { read: true, write: true },
|
||||||
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import "./emoji.css";
|
import "./emoji.css";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
import { EmojiTag } from "types";
|
||||||
|
|
||||||
export type EmojiProps = {
|
export type EmojiProps = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -10,8 +11,6 @@ export function Emoji({ name, url }: EmojiProps) {
|
|||||||
return <img alt={name} src={url} className="emoji" />;
|
return <img alt={name} src={url} className="emoji" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EmojiTag = ["emoji", string, string];
|
|
||||||
|
|
||||||
export function Emojify({
|
export function Emojify({
|
||||||
content,
|
content,
|
||||||
emoji,
|
emoji,
|
||||||
|
@ -1,19 +1,16 @@
|
|||||||
import { EventKind } from "@snort/system";
|
import { EventKind } from "@snort/system";
|
||||||
import { useLogin } from "hooks/login";
|
import { useLogin } from "hooks/login";
|
||||||
import useFollows from "hooks/follows";
|
|
||||||
import AsyncButton from "element/async-button";
|
import AsyncButton from "element/async-button";
|
||||||
import { System } from "index";
|
import { System } from "index";
|
||||||
|
|
||||||
export function LoggedInFollowButton({
|
export function LoggedInFollowButton({
|
||||||
loggedIn,
|
|
||||||
pubkey,
|
pubkey,
|
||||||
}: {
|
}: {
|
||||||
loggedIn: string;
|
|
||||||
pubkey: string;
|
pubkey: string;
|
||||||
}) {
|
}) {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const following = useFollows(loggedIn, true);
|
const tags = login?.follows.tags ?? []
|
||||||
const { tags, relays } = following ? following : { tags: [], relays: {} };
|
const relays = login?.relays
|
||||||
const follows = tags.filter((t) => t.at(0) === "p");
|
const follows = tags.filter((t) => t.at(0) === "p");
|
||||||
const isFollowing = follows.find((t) => t.at(1) === pubkey);
|
const isFollowing = follows.find((t) => t.at(1) === pubkey);
|
||||||
|
|
||||||
@ -53,7 +50,7 @@ export function LoggedInFollowButton({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncButton
|
<AsyncButton
|
||||||
disabled={!following}
|
disabled={login.follows.timestamp === 0}
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
onClick={isFollowing ? unfollow : follow}
|
onClick={isFollowing ? unfollow : follow}
|
||||||
|
@ -79,10 +79,15 @@ export function LiveChat({
|
|||||||
return () => System.ProfileLoader.UntrackMetadata(pubkeys);
|
return () => System.ProfileLoader.UntrackMetadata(pubkeys);
|
||||||
}, [feed.zaps]);
|
}, [feed.zaps]);
|
||||||
|
|
||||||
const userEmojiPacks = useEmoji(login?.pubkey);
|
const mutedPubkeys = useMemo(() => {
|
||||||
|
return new Set(
|
||||||
|
login.muted.tags.filter((t) => t.at(0) === "p").map((t) => t.at(1)),
|
||||||
|
);
|
||||||
|
}, [login.muted.tags]);
|
||||||
|
const userEmojiPacks = login?.emojis ?? [];
|
||||||
const channelEmojiPacks = useEmoji(host);
|
const channelEmojiPacks = useEmoji(host);
|
||||||
const allEmojiPacks = useMemo(() => {
|
const allEmojiPacks = useMemo(() => {
|
||||||
return uniqBy(channelEmojiPacks.concat(userEmojiPacks), packId);
|
return uniqBy(userEmojiPacks.concat(channelEmojiPacks), packId);
|
||||||
}, [userEmojiPacks, channelEmojiPacks]);
|
}, [userEmojiPacks, channelEmojiPacks]);
|
||||||
|
|
||||||
const zaps = feed.zaps
|
const zaps = feed.zaps
|
||||||
@ -105,6 +110,9 @@ export function LiveChat({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [ev]);
|
}, [ev]);
|
||||||
|
const filteredEvents = useMemo(() => {
|
||||||
|
return events.filter((e) => !mutedPubkeys.has(e.pubkey));
|
||||||
|
}, [events, mutedPubkeys]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="live-chat" style={height ? { height: `${height}px` } : {}}>
|
<div className="live-chat" style={height ? { height: `${height}px` } : {}}>
|
||||||
@ -135,7 +143,7 @@ export function LiveChat({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="messages">
|
<div className="messages">
|
||||||
{events.map((a) => {
|
{filteredEvents.map((a) => {
|
||||||
switch (a.kind) {
|
switch (a.kind) {
|
||||||
case LIVE_STREAM_CHAT: {
|
case LIVE_STREAM_CHAT: {
|
||||||
return (
|
return (
|
||||||
|
@ -9,7 +9,6 @@ import { bytesToHex } from "@noble/curves/abstract/utils";
|
|||||||
import { formatSats } from "../number";
|
import { formatSats } from "../number";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "./icon";
|
||||||
import AsyncButton from "./async-button";
|
import AsyncButton from "./async-button";
|
||||||
import { Relays } from "index";
|
|
||||||
import QrCode from "./qr-code";
|
import QrCode from "./qr-code";
|
||||||
import { useLogin } from "hooks/login";
|
import { useLogin } from "hooks/login";
|
||||||
import Copy from "./copy";
|
import Copy from "./copy";
|
||||||
@ -21,7 +20,7 @@ export interface LNURLLike {
|
|||||||
getInvoice(
|
getInvoice(
|
||||||
amountInSats: number,
|
amountInSats: number,
|
||||||
comment?: string,
|
comment?: string,
|
||||||
zap?: NostrEvent
|
zap?: NostrEvent,
|
||||||
): Promise<{ pr?: string }>;
|
): Promise<{ pr?: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +54,7 @@ export function SendZaps({
|
|||||||
const [comment, setComment] = useState("");
|
const [comment, setComment] = useState("");
|
||||||
const [invoice, setInvoice] = useState("");
|
const [invoice, setInvoice] = useState("");
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
|
const relays = Object.keys(login.relays);
|
||||||
const name = targetName ?? svc?.name;
|
const name = targetName ?? svc?.name;
|
||||||
async function loadService(lnurl: string) {
|
async function loadService(lnurl: string) {
|
||||||
const s = new LNURL(lnurl);
|
const s = new LNURL(lnurl);
|
||||||
@ -78,7 +77,9 @@ export function SendZaps({
|
|||||||
let pub = login?.publisher();
|
let pub = login?.publisher();
|
||||||
let isAnon = false;
|
let isAnon = false;
|
||||||
if (!pub) {
|
if (!pub) {
|
||||||
pub = EventPublisher.privateKey(bytesToHex(secp256k1.utils.randomPrivateKey()));
|
pub = EventPublisher.privateKey(
|
||||||
|
bytesToHex(secp256k1.utils.randomPrivateKey()),
|
||||||
|
);
|
||||||
isAnon = true;
|
isAnon = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +89,7 @@ export function SendZaps({
|
|||||||
zap = await pub.zap(
|
zap = await pub.zap(
|
||||||
amountInSats * 1000,
|
amountInSats * 1000,
|
||||||
pubkey,
|
pubkey,
|
||||||
Relays,
|
relays,
|
||||||
undefined,
|
undefined,
|
||||||
comment,
|
comment,
|
||||||
(eb) => {
|
(eb) => {
|
||||||
@ -102,7 +103,7 @@ export function SendZaps({
|
|||||||
eb.tag(["anon", ""]);
|
eb.tag(["anon", ""]);
|
||||||
}
|
}
|
||||||
return eb;
|
return eb;
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const invoice = await svc.getInvoice(amountInSats, comment, zap);
|
const invoice = await svc.getInvoice(amountInSats, comment, zap);
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import uniqBy from "lodash.uniqby";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RequestBuilder,
|
RequestBuilder,
|
||||||
ReplaceableNoteStore,
|
ReplaceableNoteStore,
|
||||||
@ -6,23 +9,9 @@ import {
|
|||||||
} from "@snort/system";
|
} from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
import { System } from "index";
|
import { System } from "index";
|
||||||
import { useMemo } from "react";
|
|
||||||
import { findTag } from "utils";
|
import { findTag } from "utils";
|
||||||
import { EMOJI_PACK, USER_EMOJIS } from "const";
|
import { EMOJI_PACK, USER_EMOJIS } from "const";
|
||||||
import type { EmojiTag } from "../element/emoji";
|
import { EmojiPack } from "types";
|
||||||
import uniqBy from "lodash.uniqby";
|
|
||||||
|
|
||||||
export interface Emoji {
|
|
||||||
native?: string;
|
|
||||||
id?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EmojiPack {
|
|
||||||
address: string;
|
|
||||||
name: string;
|
|
||||||
author: string;
|
|
||||||
emojis: EmojiTag[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanShortcode(shortcode?: string) {
|
function cleanShortcode(shortcode?: string) {
|
||||||
return shortcode?.replace(/\s+/g, "_").replace(/_$/, "");
|
return shortcode?.replace(/\s+/g, "_").replace(/_$/, "");
|
||||||
@ -44,22 +33,10 @@ export function packId(pack: EmojiPack): string {
|
|||||||
return `${pack.author}:${pack.name}`;
|
return `${pack.author}:${pack.name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useEmoji(pubkey?: string) {
|
export function useUserEmojiPacks(
|
||||||
const sub = useMemo(() => {
|
pubkey?: string,
|
||||||
if (!pubkey) return null;
|
userEmoji: { tags: string[][] },
|
||||||
const rb = new RequestBuilder(`emoji:${pubkey}`);
|
) {
|
||||||
|
|
||||||
rb.withFilter().authors([pubkey]).kinds([USER_EMOJIS]);
|
|
||||||
|
|
||||||
return rb;
|
|
||||||
}, [pubkey]);
|
|
||||||
|
|
||||||
const { data: userEmoji } = useRequestBuilder<ReplaceableNoteStore>(
|
|
||||||
System,
|
|
||||||
ReplaceableNoteStore,
|
|
||||||
sub,
|
|
||||||
);
|
|
||||||
|
|
||||||
const related = useMemo(() => {
|
const related = useMemo(() => {
|
||||||
if (userEmoji) {
|
if (userEmoji) {
|
||||||
return userEmoji.tags.filter(
|
return userEmoji.tags.filter(
|
||||||
@ -106,3 +83,23 @@ export default function useEmoji(pubkey?: string) {
|
|||||||
|
|
||||||
return emojis;
|
return emojis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function useEmoji(pubkey?: string) {
|
||||||
|
const sub = useMemo(() => {
|
||||||
|
if (!pubkey) return null;
|
||||||
|
const rb = new RequestBuilder(`emoji:${pubkey}`);
|
||||||
|
|
||||||
|
rb.withFilter().authors([pubkey]).kinds([USER_EMOJIS]);
|
||||||
|
|
||||||
|
return rb;
|
||||||
|
}, [pubkey]);
|
||||||
|
|
||||||
|
const { data: userEmoji } = useRequestBuilder<ReplaceableNoteStore>(
|
||||||
|
System,
|
||||||
|
ReplaceableNoteStore,
|
||||||
|
sub,
|
||||||
|
);
|
||||||
|
|
||||||
|
const emojis = useUserEmojiPacks(pubkey, userEmoji ?? { tags: [] });
|
||||||
|
return emojis;
|
||||||
|
}
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
import { useMemo } from "react";
|
|
||||||
import { EventKind, ReplaceableNoteStore, RequestBuilder } from "@snort/system";
|
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
|
||||||
import { System } from "index";
|
|
||||||
|
|
||||||
export default function useFollows(pubkey: string, leaveOpen = false) {
|
|
||||||
const sub = useMemo(() => {
|
|
||||||
const b = new RequestBuilder(`follows:${pubkey.slice(0, 12)}`);
|
|
||||||
b.withOptions({
|
|
||||||
leaveOpen,
|
|
||||||
})
|
|
||||||
.withFilter()
|
|
||||||
.authors([pubkey])
|
|
||||||
.kinds([EventKind.ContactList]);
|
|
||||||
return b;
|
|
||||||
}, [pubkey, leaveOpen]);
|
|
||||||
|
|
||||||
const { data } = useRequestBuilder<ReplaceableNoteStore>(
|
|
||||||
System,
|
|
||||||
ReplaceableNoteStore,
|
|
||||||
sub,
|
|
||||||
);
|
|
||||||
|
|
||||||
const relays = JSON.parse(data?.content.length > 0 ? data?.content : "{}");
|
|
||||||
return data ? { tags: data.tags, relays } : null;
|
|
||||||
}
|
|
@ -1,17 +1,100 @@
|
|||||||
import { Login } from "index";
|
import { useSyncExternalStore, useMemo, useState, useEffect } from "react";
|
||||||
import { getPublisher } from "login";
|
|
||||||
import { useSyncExternalStore } from "react";
|
import { EventKind, NoteCollection, RequestBuilder } from "@snort/system";
|
||||||
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
|
||||||
|
import { useUserEmojiPacks } from "hooks/emoji";
|
||||||
|
import { USER_EMOJIS } from "const";
|
||||||
|
import { System, Login } from "index";
|
||||||
|
import {
|
||||||
|
getPublisher,
|
||||||
|
setMuted,
|
||||||
|
setEmojis,
|
||||||
|
setFollows,
|
||||||
|
setRelays,
|
||||||
|
} from "login";
|
||||||
|
|
||||||
export function useLogin() {
|
export function useLogin() {
|
||||||
const session = useSyncExternalStore(
|
const session = useSyncExternalStore(
|
||||||
(c) => Login.hook(c),
|
(c) => Login.hook(c),
|
||||||
() => Login.snapshot()
|
() => Login.snapshot(),
|
||||||
);
|
);
|
||||||
if (!session) return;
|
if (!session) return;
|
||||||
return {
|
return {
|
||||||
...session,
|
...session,
|
||||||
publisher: () => {
|
publisher: () => {
|
||||||
return getPublisher(session);
|
return getPublisher(session);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLoginEvents(pubkey?: string, leaveOpen = false) {
|
||||||
|
const [userEmojis, setUserEmojis] = useState([]);
|
||||||
|
const session = useSyncExternalStore(
|
||||||
|
(c) => Login.hook(c),
|
||||||
|
() => Login.snapshot(),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (session) {
|
||||||
|
Object.entries(session.relays).forEach((params) => {
|
||||||
|
const [relay, settings] = params;
|
||||||
|
System.ConnectToRelay(relay, settings);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [session]);
|
||||||
|
|
||||||
|
const sub = useMemo(() => {
|
||||||
|
if (!pubkey) return null;
|
||||||
|
const b = new RequestBuilder(`login:${pubkey.slice(0, 12)}`);
|
||||||
|
b.withOptions({
|
||||||
|
leaveOpen,
|
||||||
|
})
|
||||||
|
.withFilter()
|
||||||
|
.authors([pubkey])
|
||||||
|
.kinds([
|
||||||
|
EventKind.ContactList,
|
||||||
|
EventKind.Relays,
|
||||||
|
10_000 as EventKind,
|
||||||
|
USER_EMOJIS,
|
||||||
|
]);
|
||||||
|
return b;
|
||||||
|
}, [pubkey, leaveOpen]);
|
||||||
|
|
||||||
|
const { data } = useRequestBuilder<NoteCollection>(
|
||||||
|
System,
|
||||||
|
NoteCollection,
|
||||||
|
sub,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!session) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const ev of data) {
|
||||||
|
if (ev?.kind === USER_EMOJIS) {
|
||||||
|
setUserEmojis(ev.tags);
|
||||||
|
}
|
||||||
|
if (ev?.kind === 10_000) {
|
||||||
|
// todo: decrypt ev.content tags
|
||||||
|
setMuted(session, ev.tags, ev.created_at);
|
||||||
|
}
|
||||||
|
if (ev?.kind === EventKind.ContactList) {
|
||||||
|
setFollows(session, ev.tags, ev.created_at);
|
||||||
|
}
|
||||||
|
if (ev?.kind === EventKind.Relays) {
|
||||||
|
setRelays(session, ev.tags, ev.created_at);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, [session, data]);
|
||||||
|
|
||||||
|
const emojis = useUserEmojiPacks(pubkey, { tags: userEmojis });
|
||||||
|
useEffect(() => {
|
||||||
|
if (session) {
|
||||||
|
setEmojis(session, emojis);
|
||||||
|
}
|
||||||
|
}, [session, emojis]);
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,14 @@ import ReactDOM from "react-dom/client";
|
|||||||
import { NostrSystem } from "@snort/system";
|
import { NostrSystem } from "@snort/system";
|
||||||
import { RouterProvider, createBrowserRouter } from "react-router-dom";
|
import { RouterProvider, createBrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
import { RootPage } from "./pages/root";
|
import { RootPage } from "pages/root";
|
||||||
import { LayoutPage } from "pages/layout";
|
import { LayoutPage } from "pages/layout";
|
||||||
import { ProfilePage } from "pages/profile-page";
|
import { ProfilePage } from "pages/profile-page";
|
||||||
import { StreamPage } from "pages/stream-page";
|
import { StreamPage } from "pages/stream-page";
|
||||||
import { ChatPopout } from "pages/chat-popout";
|
import { ChatPopout } from "pages/chat-popout";
|
||||||
import { LoginStore } from "login";
|
import { LoginStore } from "login";
|
||||||
import { StreamProvidersPage } from "pages/providers";
|
import { StreamProvidersPage } from "pages/providers";
|
||||||
|
import { defaultRelays } from "const";
|
||||||
|
|
||||||
export enum StreamState {
|
export enum StreamState {
|
||||||
Live = "live",
|
Live = "live",
|
||||||
@ -23,14 +24,10 @@ export enum StreamState {
|
|||||||
export const System = new NostrSystem({});
|
export const System = new NostrSystem({});
|
||||||
export const Login = new LoginStore();
|
export const Login = new LoginStore();
|
||||||
|
|
||||||
export const Relays = [
|
Object.entries(defaultRelays).forEach((params) => {
|
||||||
"wss://relay.snort.social",
|
const [relay, settings] = params;
|
||||||
"wss://nos.lol",
|
System.ConnectToRelay(relay, settings);
|
||||||
"wss://relay.damus.io",
|
});
|
||||||
"wss://nostr.wine",
|
|
||||||
];
|
|
||||||
|
|
||||||
Relays.forEach((r) => System.ConnectToRelay(r, { read: true, write: true }));
|
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@ -64,10 +61,10 @@ const router = createBrowserRouter([
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const root = ReactDOM.createRoot(
|
const root = ReactDOM.createRoot(
|
||||||
document.getElementById("root") as HTMLDivElement
|
document.getElementById("root") as HTMLDivElement,
|
||||||
);
|
);
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
</React.StrictMode>
|
</React.StrictMode>,
|
||||||
);
|
);
|
||||||
|
72
src/login.ts
72
src/login.ts
@ -2,17 +2,27 @@ import { bytesToHex } from "@noble/curves/abstract/utils";
|
|||||||
import { schnorr } from "@noble/curves/secp256k1";
|
import { schnorr } from "@noble/curves/secp256k1";
|
||||||
import { ExternalStore } from "@snort/shared";
|
import { ExternalStore } from "@snort/shared";
|
||||||
import { EventPublisher, Nip7Signer, PrivateKeySigner } from "@snort/system";
|
import { EventPublisher, Nip7Signer, PrivateKeySigner } from "@snort/system";
|
||||||
|
import type { EmojiPack, Relays } from "types";
|
||||||
|
import { defaultRelays } from "const";
|
||||||
|
|
||||||
export enum LoginType {
|
export enum LoginType {
|
||||||
Nip7 = "nip7",
|
Nip7 = "nip7",
|
||||||
PrivateKey = "private-key",
|
PrivateKey = "private-key",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ReplaceableTags {
|
||||||
|
tags: Array<string[]>;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LoginSession {
|
export interface LoginSession {
|
||||||
type: LoginType;
|
type: LoginType;
|
||||||
pubkey: string;
|
pubkey: string;
|
||||||
privateKey?: string;
|
privateKey?: string;
|
||||||
follows: string[];
|
follows: ReplaceableTags;
|
||||||
|
muted: ReplaceableTags;
|
||||||
|
relays: Relays;
|
||||||
|
emojis: Array<EmojiPack>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LoginStore extends ExternalStore<LoginSession | undefined> {
|
export class LoginStore extends ExternalStore<LoginSession | undefined> {
|
||||||
@ -33,7 +43,10 @@ export class LoginStore extends ExternalStore<LoginSession | undefined> {
|
|||||||
this.#session = {
|
this.#session = {
|
||||||
type,
|
type,
|
||||||
pubkey: pk,
|
pubkey: pk,
|
||||||
follows: [],
|
muted: { tags: [], timestamp: 0 },
|
||||||
|
follows: { tags: [], timestamp: 0 },
|
||||||
|
relays: defaultRelays,
|
||||||
|
emojis: [],
|
||||||
};
|
};
|
||||||
this.#save();
|
this.#save();
|
||||||
}
|
}
|
||||||
@ -43,7 +56,9 @@ export class LoginStore extends ExternalStore<LoginSession | undefined> {
|
|||||||
type: LoginType.PrivateKey,
|
type: LoginType.PrivateKey,
|
||||||
pubkey: bytesToHex(schnorr.getPublicKey(key)),
|
pubkey: bytesToHex(schnorr.getPublicKey(key)),
|
||||||
privateKey: key,
|
privateKey: key,
|
||||||
follows: [],
|
follows: { tags: [], timestamp: 0 },
|
||||||
|
muted: { tags: [], timestamp: 0 },
|
||||||
|
emojis: [],
|
||||||
};
|
};
|
||||||
this.#save();
|
this.#save();
|
||||||
}
|
}
|
||||||
@ -53,6 +68,11 @@ export class LoginStore extends ExternalStore<LoginSession | undefined> {
|
|||||||
this.#save();
|
this.#save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateSession(s: LoginSession) {
|
||||||
|
this.#session = s;
|
||||||
|
this.#save();
|
||||||
|
}
|
||||||
|
|
||||||
takeSnapshot() {
|
takeSnapshot() {
|
||||||
return this.#session ? { ...this.#session } : undefined;
|
return this.#session ? { ...this.#session } : undefined;
|
||||||
}
|
}
|
||||||
@ -75,8 +95,52 @@ export function getPublisher(session: LoginSession) {
|
|||||||
case LoginType.PrivateKey: {
|
case LoginType.PrivateKey: {
|
||||||
return new EventPublisher(
|
return new EventPublisher(
|
||||||
new PrivateKeySigner(session.privateKey!),
|
new PrivateKeySigner(session.privateKey!),
|
||||||
session.pubkey
|
session.pubkey,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setFollows(
|
||||||
|
state: LoginSession,
|
||||||
|
follows: Array<string>,
|
||||||
|
ts: number,
|
||||||
|
) {
|
||||||
|
if (state.follows.timestamp >= ts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.follows.tags = follows;
|
||||||
|
state.follows.timestamp = ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setEmojis(state: LoginSession, emojis: Array<EmojiPack>) {
|
||||||
|
state.emojis = emojis;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setMuted(
|
||||||
|
state: LoginSession,
|
||||||
|
muted: Array<string[]>,
|
||||||
|
ts: number,
|
||||||
|
) {
|
||||||
|
if (state.muted.timestamp >= ts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.muted.tags = muted;
|
||||||
|
state.muted.timestamp = ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setRelays(
|
||||||
|
state: LoginSession,
|
||||||
|
relays: Array<string>,
|
||||||
|
ts: number,
|
||||||
|
) {
|
||||||
|
if (state.relays.timestamp >= ts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.relays = relays.reduce((acc, r) => {
|
||||||
|
const [, relay] = r;
|
||||||
|
const write = r.length === 2 || r.includes("write");
|
||||||
|
const read = r.length === 2 || r.includes("read");
|
||||||
|
return { ...acc, [relay]: { read, write } };
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { Outlet, useNavigate } from "react-router-dom";
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
|
|
||||||
import { Icon } from "element/icon";
|
import { Icon } from "element/icon";
|
||||||
import { useLogin } from "hooks/login";
|
import { useLogin, useLoginEvents } from "hooks/login";
|
||||||
import { Profile } from "element/profile";
|
import { Profile } from "element/profile";
|
||||||
import { NewStreamDialog } from "element/new-stream";
|
import { NewStreamDialog } from "element/new-stream";
|
||||||
import { LoginSignup } from "element/login-signup";
|
import { LoginSignup } from "element/login-signup";
|
||||||
@ -17,6 +17,7 @@ export function LayoutPage() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const [showLogin, setShowLogin] = useState(false);
|
const [showLogin, setShowLogin] = useState(false);
|
||||||
|
useLoginEvents(login?.pubkey, true);
|
||||||
|
|
||||||
function loggedIn() {
|
function loggedIn() {
|
||||||
if (!login) return;
|
if (!login) return;
|
||||||
|
22
src/types.ts
Normal file
22
src/types.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export interface RelaySettings {
|
||||||
|
read: boolean;
|
||||||
|
write: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Relays {
|
||||||
|
[key: string]: RelaySettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EmojiTag = ["emoji", string, string];
|
||||||
|
|
||||||
|
export interface Emoji {
|
||||||
|
native?: string;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmojiPack {
|
||||||
|
address: string;
|
||||||
|
name: string;
|
||||||
|
author: string;
|
||||||
|
emojis: EmojiTag[];
|
||||||
|
}
|
Reference in New Issue
Block a user