diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1 @@ +{} diff --git a/package.json b/package.json index 9b34a33..e7f9ad0 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,10 @@ "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^13.0.0", "@testing-library/user-event": "^13.2.1", + "@types/webscopeio__react-textarea-autocomplete": "^4.7.2", + "@webscopeio/react-textarea-autocomplete": "^4.9.2", "hls.js": "^1.4.6", + "lodash": "^4.17.21", "qr-code-styling": "^1.6.0-rc.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -43,7 +46,9 @@ }, "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@types/lodash": "^4.14.195", "@webbtc/webln-types": "^1.0.12", + "prettier": "^2.8.8", "typescript": "^5.1.3" } } diff --git a/src/element/emoji.css b/src/element/emoji.css new file mode 100644 index 0000000..bf193f2 --- /dev/null +++ b/src/element/emoji.css @@ -0,0 +1,6 @@ +.emoji { + width: 21px; + height: 21px; + display: inline-block; + margin-bottom: -5px; +} diff --git a/src/element/emoji.tsx b/src/element/emoji.tsx new file mode 100644 index 0000000..1c04b25 --- /dev/null +++ b/src/element/emoji.tsx @@ -0,0 +1,33 @@ +import "./emoji.css"; +import { useMemo } from "react"; + +export type EmojiProps = { + name: string; + url: string; +}; + +export function Emoji({ name, url }: EmojiProps) { + return {name}; +} + +export type EmojiTag = ["emoji", string, string]; + +export function Emojify({ + content, + emoji, +}: { + content: string; + emoji: EmojiTag[]; +}) { + const emojified = useMemo(() => { + return content.split(/:(\w+):/g).map((i) => { + const t = emoji.find((t) => t[1] === i); + if (t) { + return ; + } else { + return i; + } + }); + }, [content, emoji]); + return <>{emojified}; +} diff --git a/src/element/live-chat.css b/src/element/live-chat.css index b07728a..3f66675 100644 --- a/src/element/live-chat.css +++ b/src/element/live-chat.css @@ -74,4 +74,4 @@ display: flex; align-items: center; gap: 8px; -} \ No newline at end of file +} diff --git a/src/element/live-chat.tsx b/src/element/live-chat.tsx index 86723cb..2fa0705 100644 --- a/src/element/live-chat.tsx +++ b/src/element/live-chat.tsx @@ -1,99 +1,75 @@ import "./live-chat.css"; -import { EventKind, NostrLink, TaggedRawEvent, EventPublisher, parseZap } from "@snort/system"; -import { useState } from "react"; +import { + EventKind, + NostrLink, + TaggedRawEvent, + EventPublisher, + parseZap, +} from "@snort/system"; +import { useState, type KeyboardEvent, type ChangeEvent } from "react"; +import useEmoji from "hooks/emoji"; import { System } from "index"; import { useLiveChatFeed } from "hooks/live-chat"; import AsyncButton from "./async-button"; import { Profile } from "./profile"; import { Icon } from "./icon"; import { Text } from "./text"; +import { Textarea } from "./textarea"; import Spinner from "./spinner"; import { useLogin } from "hooks/login"; import { useUserProfile } from "@snort/system-react"; -import { formatSats, formatShort } from "number"; +import { formatSats } from "number"; export interface LiveChatOptions { - canWrite?: boolean, - showHeader?: boolean + canWrite?: boolean; + showHeader?: boolean; } -export function LiveChat({ link, options }: { link: NostrLink, options?: LiveChatOptions }) { - const [chat, setChat] = useState(""); +export function LiveChat({ + link, + options, +}: { + link: NostrLink; + options?: LiveChatOptions; +}) { const messages = useLiveChatFeed(link); const login = useLogin(); - - async function sendChatMessage() { - const pub = await EventPublisher.nip7(); - if (chat.length > 1) { - const reply = await pub?.generic(eb => { - return eb - .kind(1311 as EventKind) - .content(chat) - .tag(["a", `${link.kind}:${link.author}:${link.id}`, "", "root"]) - .processContent(); - }); - if (reply) { - console.debug(reply); - System.BroadcastEvent(reply); - } - setChat(""); - } - } - - function writeMessage() { - return <> -
- setChat(v.target.value)} - value={chat} - placeholder="Message" - onKeyDown={async e => { - if (e.code === "Enter") { - e.preventDefault(); - await sendChatMessage(); - } - }} - /> - -
- - Send - - - } - return (
- {(options?.showHeader ?? true) &&
- Stream Chat -
} + {(options?.showHeader ?? true) && ( +
Stream Chat
+ )}
{[...(messages.data ?? [])] .sort((a, b) => b.created_at - a.created_at) - .map(a => { + .map((a) => { switch (a.kind) { case 1311: { return ; } case EventKind.ZapReceipt: { - return + return ; } } return null; })} {messages.data === undefined && }
- {(options?.canWrite ?? true) &&
- {login ? writeMessage() :

Please login to write messages!

} -
} + {(options?.canWrite ?? true) && ( +
+ {login ? ( + + ) : ( +

Please login to write messages!

+ )} +
+ )}
); } -function ChatMessage({ ev, link }: { ev: TaggedRawEvent, link: NostrLink }) { +function ChatMessage({ ev, link }: { ev: TaggedRawEvent; link: NostrLink }) { return (
@@ -113,19 +89,82 @@ function ChatZap({ ev }: { ev: TaggedRawEvent }) {
- - zapped -   + + zapped   {formatSats(parsed.amount)} -   - sats +   sats
- {parsed.content &&

- {parsed.content} -

} + {parsed.content &&

{parsed.content}

}
); -} \ No newline at end of file +} + +function WriteMessage({ link }: { link: NostrLink }) { + const [chat, setChat] = useState(""); + const login = useLogin(); + const emojis = useEmoji(login!.pubkey); + const names = emojis.map((t) => t.at(1)); + + async function sendChatMessage() { + const pub = await EventPublisher.nip7(); + if (chat.length > 1) { + let messageEmojis: string[][] = []; + for (const name of names) { + if (chat.includes(`:${name}:`)) { + const e = emojis.find((t) => t.at(1) === name); + messageEmojis.push(e as string[]); + } + } + const reply = await pub?.generic((eb) => { + eb.kind(1311 as EventKind) + .content(chat) + .tag(["a", `${link.kind}:${link.author}:${link.id}`, "", "root"]) + .processContent(); + for (const e of messageEmojis) { + eb.tag(e); + } + return eb; + }); + if (reply) { + console.debug(reply); + System.BroadcastEvent(reply); + } + setChat(""); + } + } + + async function onKeyDown(e: KeyboardEvent) { + if (e.code === "Enter") { + e.preventDefault(); + await sendChatMessage(); + } + } + + async function onChange(e: ChangeEvent) { + // @ts-expect-error + setChat(e.target.value); + } + + return ( + <> +
+