fix: show channel emoji first
This commit is contained in:
@ -20,6 +20,7 @@
|
|||||||
"emoji-mart": "^5.5.2",
|
"emoji-mart": "^5.5.2",
|
||||||
"hls.js": "^1.4.6",
|
"hls.js": "^1.4.6",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"lodash.uniqby": "^4.7.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"qr-code-styling": "^1.6.0-rc.1",
|
"qr-code-styling": "^1.6.0-rc.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@ -65,6 +66,7 @@
|
|||||||
"@formatjs/cli": "^6.0.1",
|
"@formatjs/cli": "^6.0.1",
|
||||||
"@formatjs/ts-transformer": "^3.13.1",
|
"@formatjs/ts-transformer": "^3.13.1",
|
||||||
"@types/lodash": "^4.14.195",
|
"@types/lodash": "^4.14.195",
|
||||||
|
"@types/lodash.uniqby": "^4.7.7",
|
||||||
"@webbtc/webln-types": "^1.0.12",
|
"@webbtc/webln-types": "^1.0.12",
|
||||||
"babel-loader": "^9.1.2",
|
"babel-loader": "^9.1.2",
|
||||||
"babel-plugin-formatjs": "^10.5.1",
|
"babel-plugin-formatjs": "^10.5.1",
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import {NostrLink, EventPublisher, EventKind} from "@snort/system";
|
import { NostrLink, EventPublisher, EventKind } from "@snort/system";
|
||||||
import { useRef, useState, ChangeEvent } from "react";
|
import { useRef, useState, useMemo, ChangeEvent } from "react";
|
||||||
|
import uniqBy from "lodash.uniqby";
|
||||||
|
|
||||||
import { LIVE_STREAM_CHAT } from "../const";
|
import { LIVE_STREAM_CHAT } from "../const";
|
||||||
import useEmoji from "../hooks/emoji";
|
import useEmoji, { packId } from "../hooks/emoji";
|
||||||
import { useLogin } from "../hooks/login";
|
import { useLogin } from "../hooks/login";
|
||||||
import { System } from "../index";
|
import { System } from "../index";
|
||||||
import AsyncButton from "./async-button";
|
import AsyncButton from "./async-button";
|
||||||
@ -11,115 +12,116 @@ import { Textarea } from "./textarea";
|
|||||||
import { EmojiPicker } from "./emoji-picker";
|
import { EmojiPicker } from "./emoji-picker";
|
||||||
|
|
||||||
interface Emoji {
|
interface Emoji {
|
||||||
id: string;
|
id: string;
|
||||||
native?: string;
|
native?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WriteMessage({ link }: { link: NostrLink }) {
|
export function WriteMessage({ link }: { link: NostrLink }) {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const emojiRef = useRef(null);
|
const emojiRef = useRef(null);
|
||||||
const [chat, setChat] = useState("");
|
const [chat, setChat] = useState("");
|
||||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const userEmojiPacks = useEmoji(login!.pubkey);
|
const userEmojiPacks = useEmoji(login!.pubkey);
|
||||||
const userEmojis = userEmojiPacks.map((pack) => pack.emojis).flat();
|
const userEmojis = userEmojiPacks.map((pack) => pack.emojis).flat();
|
||||||
const channelEmojiPacks = useEmoji(link.author!);
|
const channelEmojiPacks = useEmoji(link.author!);
|
||||||
const channelEmojis = channelEmojiPacks.map((pack) => pack.emojis).flat();
|
const channelEmojis = channelEmojiPacks.map((pack) => pack.emojis).flat();
|
||||||
const emojis = userEmojis.concat(channelEmojis);
|
const emojis = userEmojis.concat(channelEmojis);
|
||||||
const names = emojis.map((t) => t.at(1));
|
const names = emojis.map((t) => t.at(1));
|
||||||
const allEmojiPacks = userEmojiPacks.concat(channelEmojiPacks);
|
const allEmojiPacks = useMemo(() => {
|
||||||
// @ts-expect-error
|
return uniqBy(channelEmojiPacks.concat(userEmojiPacks), packId);
|
||||||
const topOffset = ref.current?.getBoundingClientRect().top;
|
}, [userEmojiPacks, channelEmojiPacks]);
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
const leftOffset = ref.current?.getBoundingClientRect().left;
|
const topOffset = ref.current?.getBoundingClientRect().top;
|
||||||
|
// @ts-expect-error
|
||||||
|
const leftOffset = ref.current?.getBoundingClientRect().left;
|
||||||
|
|
||||||
async function sendChatMessage() {
|
async function sendChatMessage() {
|
||||||
const pub = await EventPublisher.nip7();
|
const pub = await EventPublisher.nip7();
|
||||||
if (chat.length > 1) {
|
if (chat.length > 1) {
|
||||||
let emojiNames = new Set();
|
let emojiNames = new Set();
|
||||||
|
|
||||||
for (const name of names) {
|
for (const name of names) {
|
||||||
if (chat.includes(`:${name}:`)) {
|
if (chat.includes(`:${name}:`)) {
|
||||||
emojiNames.add(name);
|
emojiNames.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const reply = await pub?.generic((eb) => {
|
||||||
|
const emoji = [...emojiNames].map((name) =>
|
||||||
|
emojis.find((e) => e.at(1) === name)
|
||||||
|
);
|
||||||
|
eb.kind(LIVE_STREAM_CHAT as EventKind)
|
||||||
|
.content(chat)
|
||||||
|
.tag(["a", `${link.kind}:${link.author}:${link.id}`, "", "root"])
|
||||||
|
.processContent();
|
||||||
|
for (const e of emoji) {
|
||||||
|
if (e) {
|
||||||
|
eb.tag(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return eb;
|
||||||
const reply = await pub?.generic((eb) => {
|
});
|
||||||
const emoji = [...emojiNames].map((name) =>
|
if (reply) {
|
||||||
emojis.find((e) => e.at(1) === name)
|
console.debug(reply);
|
||||||
);
|
System.BroadcastEvent(reply);
|
||||||
eb.kind(LIVE_STREAM_CHAT as EventKind)
|
|
||||||
.content(chat)
|
|
||||||
.tag(["a", `${link.kind}:${link.author}:${link.id}`, "", "root"])
|
|
||||||
.processContent();
|
|
||||||
for (const e of emoji) {
|
|
||||||
if (e) {
|
|
||||||
eb.tag(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return eb;
|
|
||||||
});
|
|
||||||
if (reply) {
|
|
||||||
console.debug(reply);
|
|
||||||
System.BroadcastEvent(reply);
|
|
||||||
}
|
|
||||||
setChat("");
|
|
||||||
}
|
}
|
||||||
|
setChat("");
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEmojiSelect(emoji: Emoji) {
|
|
||||||
if (emoji.native) {
|
|
||||||
setChat(`${chat}${emoji.native}`);
|
|
||||||
} else {
|
|
||||||
setChat(`${chat}:${emoji.id}:`);
|
|
||||||
}
|
|
||||||
setShowEmojiPicker(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onKeyDown(e: React.KeyboardEvent) {
|
|
||||||
if (e.code === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
await sendChatMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onChange(e: ChangeEvent) {
|
|
||||||
// @ts-expect-error
|
|
||||||
setChat(e.target.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function pickEmoji(ev: any) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
setShowEmojiPicker(!showEmojiPicker);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="paper" ref={ref}>
|
|
||||||
<Textarea
|
|
||||||
emojis={emojis}
|
|
||||||
value={chat}
|
|
||||||
onKeyDown={onKeyDown}
|
|
||||||
onChange={onChange}
|
|
||||||
/>
|
|
||||||
<div onClick={pickEmoji}>
|
|
||||||
<Icon name="face" className="write-emoji-button" />
|
|
||||||
</div>
|
|
||||||
{showEmojiPicker && (
|
|
||||||
<EmojiPicker
|
|
||||||
topOffset={topOffset}
|
|
||||||
leftOffset={leftOffset}
|
|
||||||
emojiPacks={allEmojiPacks}
|
|
||||||
onEmojiSelect={onEmojiSelect}
|
|
||||||
onClickOutside={() => setShowEmojiPicker(false)}
|
|
||||||
ref={emojiRef}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<AsyncButton onClick={sendChatMessage} className="btn btn-border">
|
|
||||||
Send
|
|
||||||
</AsyncButton>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onEmojiSelect(emoji: Emoji) {
|
||||||
|
if (emoji.native) {
|
||||||
|
setChat(`${chat}${emoji.native}`);
|
||||||
|
} else {
|
||||||
|
setChat(`${chat}:${emoji.id}:`);
|
||||||
|
}
|
||||||
|
setShowEmojiPicker(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onKeyDown(e: React.KeyboardEvent) {
|
||||||
|
if (e.code === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
await sendChatMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onChange(e: ChangeEvent) {
|
||||||
|
// @ts-expect-error
|
||||||
|
setChat(e.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickEmoji(ev: any) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
setShowEmojiPicker(!showEmojiPicker);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="paper" ref={ref}>
|
||||||
|
<Textarea
|
||||||
|
emojis={emojis}
|
||||||
|
value={chat}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
<div onClick={pickEmoji}>
|
||||||
|
<Icon name="face" className="write-emoji-button" />
|
||||||
|
</div>
|
||||||
|
{showEmojiPicker && (
|
||||||
|
<EmojiPicker
|
||||||
|
topOffset={topOffset}
|
||||||
|
leftOffset={leftOffset}
|
||||||
|
emojiPacks={allEmojiPacks}
|
||||||
|
onEmojiSelect={onEmojiSelect}
|
||||||
|
onClickOutside={() => setShowEmojiPicker(false)}
|
||||||
|
ref={emojiRef}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<AsyncButton onClick={sendChatMessage} className="btn btn-border">
|
||||||
|
Send
|
||||||
|
</AsyncButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -3,12 +3,14 @@ import {
|
|||||||
EventKind,
|
EventKind,
|
||||||
ReplaceableNoteStore,
|
ReplaceableNoteStore,
|
||||||
NoteCollection,
|
NoteCollection,
|
||||||
|
NostrEvent,
|
||||||
} 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 { useMemo } from "react";
|
||||||
import { findTag } from "utils";
|
import { findTag } from "utils";
|
||||||
import type { EmojiTag } from "../element/emoji";
|
import type { EmojiTag } from "../element/emoji";
|
||||||
|
import uniqBy from "lodash.uniqby";
|
||||||
|
|
||||||
export interface EmojiPack {
|
export interface EmojiPack {
|
||||||
address: string;
|
address: string;
|
||||||
@ -17,6 +19,20 @@ export interface EmojiPack {
|
|||||||
emojis: EmojiTag[];
|
emojis: EmojiTag[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toEmojiPack(ev: NostrEvent): EmojiPack {
|
||||||
|
const d = findTag(ev, "d") || "";
|
||||||
|
return {
|
||||||
|
address: `${ev.kind}:${ev.pubkey}:${d}`,
|
||||||
|
name: d,
|
||||||
|
author: ev.pubkey,
|
||||||
|
emojis: ev.tags.filter((t) => t.at(0) === "emoji") as EmojiTag[],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function packId(pack: EmojiPack): string {
|
||||||
|
return `${pack.author}:${pack.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
export default function useEmoji(pubkey: string) {
|
export default function useEmoji(pubkey: string) {
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
const rb = new RequestBuilder(`emoji:${pubkey}`);
|
const rb = new RequestBuilder(`emoji:${pubkey}`);
|
||||||
@ -61,31 +77,26 @@ export default function useEmoji(pubkey: string) {
|
|||||||
.authors(authors)
|
.authors(authors)
|
||||||
.tag("d", identifiers);
|
.tag("d", identifiers);
|
||||||
|
|
||||||
|
rb.withFilter()
|
||||||
|
.kinds([30030 as EventKind])
|
||||||
|
.authors([pubkey]);
|
||||||
|
|
||||||
return rb;
|
return rb;
|
||||||
}, [pubkey, related]);
|
}, [pubkey, related]);
|
||||||
|
|
||||||
const { data: relatedData } =
|
const { data: relatedData } = useRequestBuilder<NoteCollection>(
|
||||||
useRequestBuilder<NoteCollection>(
|
System,
|
||||||
System,
|
NoteCollection,
|
||||||
NoteCollection,
|
subRelated
|
||||||
subRelated
|
);
|
||||||
);
|
|
||||||
|
|
||||||
const emojiPacks = useMemo(() => {
|
const emojiPacks = useMemo(() => {
|
||||||
return relatedData ?? [];
|
return relatedData ?? [];
|
||||||
}, [relatedData]);
|
}, [relatedData]);
|
||||||
|
|
||||||
const emojis = useMemo(() => {
|
const emojis = useMemo(() => {
|
||||||
return emojiPacks.map((ev) => {
|
return uniqBy(emojiPacks.map(toEmojiPack), packId);
|
||||||
const d = findTag(ev, "d");
|
}, [emojiPacks]);
|
||||||
return {
|
|
||||||
address: `${ev.kind}:${ev.pubkey}:${d}`,
|
|
||||||
name: d,
|
|
||||||
author: ev.pubkey,
|
|
||||||
emojis: ev.tags.filter((t) => t.at(0) === "emoji") as EmojiTag[],
|
|
||||||
} as EmojiPack;
|
|
||||||
});
|
|
||||||
}, [userEmoji, emojiPacks]);
|
|
||||||
|
|
||||||
return emojis;
|
return emojis;
|
||||||
}
|
}
|
||||||
|
14
yarn.lock
14
yarn.lock
@ -1827,7 +1827,14 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.34.tgz#c0fb25e4d957e0ee2e497c1f553d7f8bb668fd75"
|
resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.34.tgz#c0fb25e4d957e0ee2e497c1f553d7f8bb668fd75"
|
||||||
integrity sha512-s2cfwagOQAS8o06TcwKfr9Wx11dNGbH2E9vJz1cqV+a/LOyhWNLUNd6JSRYNzvB4d29UuJX2M0Dj9vE1T8fRXw==
|
integrity sha512-s2cfwagOQAS8o06TcwKfr9Wx11dNGbH2E9vJz1cqV+a/LOyhWNLUNd6JSRYNzvB4d29UuJX2M0Dj9vE1T8fRXw==
|
||||||
|
|
||||||
"@types/lodash@^4.14.195":
|
"@types/lodash.uniqby@^4.7.7":
|
||||||
|
version "4.7.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash.uniqby/-/lodash.uniqby-4.7.7.tgz#48dbb652c41cc8fb30aa61a44174368081835ab5"
|
||||||
|
integrity sha512-sv2g6vkCIvEUsK5/Vq17haoZaisfj2EWW8mP7QWlnKi6dByoNmeuHDDXHR7sabuDqwO4gvU7ModIL22MmnOocg==
|
||||||
|
dependencies:
|
||||||
|
"@types/lodash" "*"
|
||||||
|
|
||||||
|
"@types/lodash@*", "@types/lodash@^4.14.195":
|
||||||
version "4.14.195"
|
version "4.14.195"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632"
|
||||||
integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==
|
integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==
|
||||||
@ -4558,6 +4565,11 @@ lodash.uniq@^4.5.0:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||||
integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==
|
integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==
|
||||||
|
|
||||||
|
lodash.uniqby@^4.7.0:
|
||||||
|
version "4.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302"
|
||||||
|
integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==
|
||||||
|
|
||||||
lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21:
|
lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21:
|
||||||
version "4.17.21"
|
version "4.17.21"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
|
Reference in New Issue
Block a user