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 &&
+ //
+ }
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));
+}