diff --git a/src/element/chat-message.tsx b/src/element/chat-message.tsx index 9c7801b..b8e11cd 100644 --- a/src/element/chat-message.tsx +++ b/src/element/chat-message.tsx @@ -140,7 +140,14 @@ export function ChatMessage({ onClick={() => setShowZapDialog(true)} > } + icon={ + ev.pubkey === streamer && + // TODO + } pubkey={ev.pubkey} profile={profile} /> diff --git a/src/element/emoji-pack.css b/src/element/emoji-pack.css index 463cfbd..40f3dd9 100644 --- a/src/element/emoji-pack.css +++ b/src/element/emoji-pack.css @@ -4,6 +4,10 @@ justify-content: space-between; } +.emoji-pack-title .name { + margin: 0; +} + .emoji-pack-title a { font-size: 14px; } @@ -30,3 +34,7 @@ .emoji-pack h4 { margin: 0; } + +.emoji-pack .btn { + font-size: 12px; +} diff --git a/src/element/emoji-pack.tsx b/src/element/emoji-pack.tsx index 59b5139..cc506d6 100644 --- a/src/element/emoji-pack.tsx +++ b/src/element/emoji-pack.tsx @@ -1,17 +1,60 @@ import "./emoji-pack.css"; import { type NostrEvent } from "@snort/system"; +import { unixNow } from "@snort/shared"; +import AsyncButton from "element/async-button"; +import { useLogin } from "hooks/login"; +import { toEmojiPack } from "hooks/emoji"; import { Mention } from "element/mention"; import { findTag } from "utils"; +import { USER_EMOJIS } from "const"; +import { Login, System } from "index"; export function EmojiPack({ ev }: { ev: NostrEvent }) { + const login = useLogin(); const name = findTag(ev, "d"); + const isUsed = login.emojis.find( + (e) => e.author === ev.pubkey && e.name === name, + ); const emoji = ev.tags.filter((e) => e.at(0) === "emoji"); + + async function toggleEmojiPack() { + let newPacks = []; + if (isUsed) { + newPacks = login.emojis.filter( + (e) => e.pubkey !== ev.pubkey && e.name !== name, + ); + } else { + newPacks = [...login.emojis, toEmojiPack(ev)]; + } + const pub = login?.publisher(); + if (pub) { + const ev = await pub.generic((eb) => { + eb.kind(USER_EMOJIS).content(""); + for (const e of newPacks) { + eb.tag(["a", e.address]); + } + return eb; + }); + console.debug(ev); + System.BroadcastEvent(ev); + Login.setEmojis(newPacks, unixNow()); + } + } + return (
-

{name}

- +
+

{name}

+ +
+ + {isUsed ? "Remove" : "Add"} +
{emoji.map((e) => { diff --git a/src/element/follow-button.tsx b/src/element/follow-button.tsx index 8361569..f11a7eb 100644 --- a/src/element/follow-button.tsx +++ b/src/element/follow-button.tsx @@ -1,50 +1,47 @@ import { EventKind } from "@snort/system"; +import { unixNow } from "@snort/shared"; + import { useLogin } from "hooks/login"; import AsyncButton from "element/async-button"; -import { System } from "index"; +import { Login, System } from "index"; -export function LoggedInFollowButton({ - pubkey, -}: { - pubkey: string; -}) { +export function LoggedInFollowButton({ pubkey }: { pubkey: string }) { const login = useLogin(); - const tags = login?.follows.tags ?? [] - const relays = login?.relays + const tags = login.follows.tags; const follows = tags.filter((t) => t.at(0) === "p"); const isFollowing = follows.find((t) => t.at(1) === pubkey); async function unfollow() { const pub = login?.publisher(); if (pub) { + const newFollows = tags.filter((t) => t.at(1) !== pubkey); const ev = await pub.generic((eb) => { - eb.kind(EventKind.ContactList).content(JSON.stringify(relays)); - for (const t of tags) { - const isFollow = t.at(0) === "p" && t.at(1) === pubkey; - if (!isFollow) { - eb.tag(t); - } + eb.kind(EventKind.ContactList).content(JSON.stringify(login.relays)); + for (const t of newFollows) { + eb.tag(t); } return eb; }); console.debug(ev); System.BroadcastEvent(ev); + Login.setFollows(newFollows, unixNow()); } } async function follow() { const pub = login?.publisher(); if (pub) { + const newFollows = [...tags, ["p", pubkey]]; const ev = await pub.generic((eb) => { - eb.kind(EventKind.ContactList).content(JSON.stringify(relays)); - for (const tag of tags) { + eb.kind(EventKind.ContactList).content(JSON.stringify(login.relays)); + for (const tag of newFollows) { eb.tag(tag); } - eb.tag(["p", pubkey]); return eb; }); console.debug(ev); System.BroadcastEvent(ev); + Login.setFollows(newFollows, unixNow()); } } diff --git a/src/element/live-chat.css b/src/element/live-chat.css index 99de93f..02924fc 100644 --- a/src/element/live-chat.css +++ b/src/element/live-chat.css @@ -24,10 +24,10 @@ } .live-chat .header .popout-link { - color: #FFFFFF80; + color: #ffffff80; } -.live-chat>.messages { +.live-chat > .messages { display: flex; gap: 12px; flex-direction: column-reverse; @@ -37,12 +37,12 @@ } @media (min-width: 1020px) { - .live-chat>.messages { + .live-chat > .messages { flex-grow: 1; } } -.live-chat>.write-message { +.live-chat > .write-message { display: flex; gap: 8px; margin-top: auto; @@ -51,7 +51,7 @@ border-top: 1px solid var(--border, #171717); } -.live-chat>.write-message>div:nth-child(1) { +.live-chat > .write-message > div:nth-child(1) { height: 32px; flex-grow: 1; } @@ -77,15 +77,15 @@ } .live-chat .message .profile { - color: #34D2FE; + color: #34d2fe; } .live-chat .message.streamer .profile { - color: #F838D9; + color: #f838d9; } .live-chat .message a { - color: #F838D9; + color: #f838d9; } .live-chat .profile img { @@ -93,7 +93,7 @@ height: 24px; } -.live-chat .message>span { +.live-chat .message > span { font-weight: 400; font-size: 15px; line-height: 24px; @@ -172,13 +172,13 @@ position: relative; border-radius: 12px; border: 1px solid transparent; - background: #0A0A0A; + background: #0a0a0a; background-clip: padding-box; padding: 8px 12px; } .zap-container:before { - content: ''; + content: ""; position: absolute; top: 0; right: 0; @@ -186,20 +186,28 @@ left: 0; z-index: -1; margin: -1px; - background: linear-gradient(to bottom right, #FF902B, #F83838); + background: linear-gradient(to bottom right, #ff902b, #f83838); border-radius: inherit; } .zap-container .profile { - color: #FF8D2B; + color: #ff8d2b; } .zap-container .zap-amount { - color: #FF8D2B; + color: #ff8d2b; } .zap-container.big-zap:before { - background: linear-gradient(60deg, #2BD9FF, #8C8DED, #F838D9, #F83838, #FF902B, #DDF838); + background: linear-gradient( + 60deg, + #2bd9ff, + #8c8ded, + #f838d9, + #f83838, + #ff902b, + #ddf838 + ); animation: animatedgradient 3s ease alternate infinite; background-size: 300% 300%; } @@ -224,7 +232,7 @@ .zap-pill { border-radius: 100px; - background: rgba(255, 255, 255, 0.10); + background: rgba(255, 255, 255, 0.1); width: fit-content; display: flex; height: 24px; @@ -236,7 +244,7 @@ .zap-pill-icon { width: 12px; height: 12px; - color: #FF8D2B; + color: #ff8d2b; } .message-zap-container { @@ -252,7 +260,7 @@ margin-top: 4px; width: fit-content; z-index: 1; - transition: opacity .3s ease-out; + transition: opacity 0.3s ease-out; } @media (min-width: 1020px) { @@ -271,7 +279,7 @@ gap: 2px; border-radius: 100px; background: rgba(255, 255, 255, 0.05); - color: #FFFFFF66; + color: #ffffff66; } .message-zap-button:hover { @@ -299,7 +307,7 @@ align-items: center; gap: 2px; border-radius: 100px; - background: rgba(255, 255, 255, 0.10); + background: rgba(255, 255, 255, 0.1); } .message-reaction { @@ -311,7 +319,7 @@ .zap-pill-amount { text-transform: lowercase; - color: #FFF; + color: #fff; font-size: 12px; font-family: Outfit; font-style: normal; @@ -335,10 +343,17 @@ } .write-emoji-button { - color: #FFFFFF80; + color: #ffffff80; cursor: pointer; } .write-emoji-button:hover { color: white; } + +.message .profile .badge-icon { + background: transparent; + width: 18px; + height: 18px; + border-radius: unset; +} diff --git a/src/element/live-chat.tsx b/src/element/live-chat.tsx index ce53178..01cf2d2 100644 --- a/src/element/live-chat.tsx +++ b/src/element/live-chat.tsx @@ -26,7 +26,7 @@ import { ChatMessage } from "./chat-message"; import { Goal } from "./goal"; import { NewGoalDialog } from "./new-goal"; import { WriteMessage } from "./write-message"; -import { findTag, getHost } from "utils"; +import { findTag, getTagValues, getHost } from "utils"; export interface LiveChatOptions { canWrite?: boolean; @@ -80,9 +80,7 @@ export function LiveChat({ }, [feed.zaps]); const mutedPubkeys = useMemo(() => { - return new Set( - login.muted.tags.filter((t) => t.at(0) === "p").map((t) => t.at(1)), - ); + return new Set(getTagValues(login.muted.tags, "p")); }, [login.muted.tags]); const userEmojiPacks = login?.emojis ?? []; const channelEmojiPacks = useEmoji(host); diff --git a/src/hooks/emoji.tsx b/src/hooks/emoji.tsx index 3675f42..3728899 100644 --- a/src/hooks/emoji.tsx +++ b/src/hooks/emoji.tsx @@ -17,7 +17,7 @@ function cleanShortcode(shortcode?: string) { return shortcode?.replace(/\s+/g, "_").replace(/_$/, ""); } -function toEmojiPack(ev: NostrEvent): EmojiPack { +export function toEmojiPack(ev: NostrEvent): EmojiPack { const d = findTag(ev, "d") || ""; return { address: `${ev.kind}:${ev.pubkey}:${d}`, @@ -78,7 +78,8 @@ export function useUserEmojiPacks( }, [relatedData]); const emojis = useMemo(() => { - return uniqBy(emojiPacks.map(toEmojiPack), packId); + const packs = emojiPacks.map(toEmojiPack); + return uniqBy(packs, packId); }, [emojiPacks]); return emojis; diff --git a/src/hooks/login.ts b/src/hooks/login.ts index 84ad78c..14db575 100644 --- a/src/hooks/login.ts +++ b/src/hooks/login.ts @@ -4,15 +4,9 @@ import { EventKind, NoteCollection, RequestBuilder } from "@snort/system"; import { useRequestBuilder } from "@snort/system-react"; import { useUserEmojiPacks } from "hooks/emoji"; -import { USER_EMOJIS } from "const"; +import { MUTED, USER_EMOJIS } from "const"; import { System, Login } from "index"; -import { - getPublisher, - setMuted, - setEmojis, - setFollows, - setRelays, -} from "login"; +import { getPublisher } from "login"; export function useLogin() { const session = useSyncExternalStore( @@ -52,12 +46,7 @@ export function useLoginEvents(pubkey?: string, leaveOpen = false) { }) .withFilter() .authors([pubkey]) - .kinds([ - EventKind.ContactList, - EventKind.Relays, - 10_000 as EventKind, - USER_EMOJIS, - ]); + .kinds([EventKind.ContactList, EventKind.Relays, MUTED, USER_EMOJIS]); return b; }, [pubkey, leaveOpen]); @@ -71,30 +60,25 @@ export function useLoginEvents(pubkey?: string, leaveOpen = false) { if (!data) { return; } - if (!session) { - return; - } for (const ev of data) { if (ev?.kind === USER_EMOJIS) { setUserEmojis(ev.tags); } - if (ev?.kind === 10_000) { + if (ev?.kind === MUTED) { // todo: decrypt ev.content tags - setMuted(session, ev.tags, ev.created_at); + Login.setMuted(ev.tags, ev.created_at); } if (ev?.kind === EventKind.ContactList) { - setFollows(session, ev.tags, ev.created_at); + Login.setFollows(ev.tags, ev.created_at); } if (ev?.kind === EventKind.Relays) { - setRelays(session, ev.tags, ev.created_at); + Login.setRelays(ev.tags, ev.created_at); } } - }, [session, data]); + }, [data]); const emojis = useUserEmojiPacks(pubkey, { tags: userEmojis }); useEffect(() => { - if (session) { - setEmojis(session, emojis); - } - }, [session, emojis]); + Login.setEmojis(emojis); + }, [emojis]); } diff --git a/src/login.ts b/src/login.ts index 3089f5a..4a64bab 100644 --- a/src/login.ts +++ b/src/login.ts @@ -68,13 +68,44 @@ export class LoginStore extends ExternalStore { this.#save(); } - updateSession(s: LoginSession) { - this.#session = s; + takeSnapshot() { + return this.#session ? { ...this.#session } : undefined; + } + + setFollows(follows: Array, ts: number) { + if (this.#session.follows.timestamp >= ts) { + return; + } + this.#session.follows.tags = follows; + this.#session.follows.timestamp = ts; this.#save(); } - takeSnapshot() { - return this.#session ? { ...this.#session } : undefined; + setEmojis(emojis: Array) { + this.#session.emojis = emojis; + this.#save(); + } + + setMuted(muted: Array, ts: number) { + if (this.#session.muted.timestamp >= ts) { + return; + } + this.#session.muted.tags = muted; + this.#session.muted.timestamp = ts; + this.#save(); + } + + setRelays(relays: Array, ts: number) { + if (this.#session.relays.timestamp >= ts) { + return; + } + this.#session.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 } }; + }, {}); + this.#save(); } #save() { @@ -100,47 +131,3 @@ export function getPublisher(session: LoginSession) { } } } - -export function setFollows( - state: LoginSession, - follows: Array, - ts: number, -) { - if (state.follows.timestamp >= ts) { - return; - } - state.follows.tags = follows; - state.follows.timestamp = ts; -} - -export function setEmojis(state: LoginSession, emojis: Array) { - state.emojis = emojis; -} - -export function setMuted( - state: LoginSession, - muted: Array, - ts: number, -) { - if (state.muted.timestamp >= ts) { - return; - } - state.muted.tags = muted; - state.muted.timestamp = ts; -} - -export function setRelays( - state: LoginSession, - relays: Array, - 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 } }; - }, {}); -} diff --git a/src/utils.ts b/src/utils.ts index ce7ef68..6d915b5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -90,3 +90,7 @@ export async function openFile(): Promise { elm.click(); }); } + +export function getTagValues(tags: Array, tag: string) { + return tags.filter((t) => t.at(0) === tag).map((t) => t.at(1)); +}