diff --git a/packages/app/config/default.json b/packages/app/config/default.json index 7dcaecae..4f79e44f 100644 --- a/packages/app/config/default.json +++ b/packages/app/config/default.json @@ -4,5 +4,6 @@ "appTitle": "Snort - Nostr", "nip05Domain": "snort.social", "favicon": "public/favicon.ico", - "appleTouchIconUrl": "/nostrich_512.png" + "appleTouchIconUrl": "/nostrich_512.png", + "httpCache": "" } diff --git a/packages/app/config/iris.json b/packages/app/config/iris.json index 10b7f5be..823281ed 100644 --- a/packages/app/config/iris.json +++ b/packages/app/config/iris.json @@ -4,5 +4,7 @@ "appTitle": "iris", "nip05Domain": "iris.to", "favicon": "public/iris/favicon.ico", - "appleTouchIconUrl": "/img/apple-touch-icon.png" + "appleTouchIconUrl": "/img/apple-touch-icon.png", + "httpCache": "https://api.iris.to", + "animalNamePlaceholders": true } diff --git a/packages/app/src/Const.ts b/packages/app/src/Const.ts index 0af7a091..2eefd906 100644 --- a/packages/app/src/Const.ts +++ b/packages/app/src/Const.ts @@ -15,11 +15,6 @@ export const Day = Hour * 24; */ export const ApiHost = "https://api.snort.social"; -/** - * Iris api for free nip05 names - */ -export const IrisHost = "https://api.iris.to"; - /** * LibreTranslate endpoint */ diff --git a/packages/app/src/Element/Deck/SpotlightMedia.tsx b/packages/app/src/Element/Deck/SpotlightMedia.tsx index 1c748bb2..e37463f0 100644 --- a/packages/app/src/Element/Deck/SpotlightMedia.tsx +++ b/packages/app/src/Element/Deck/SpotlightMedia.tsx @@ -59,7 +59,7 @@ export function SpotlightMedia(props: SpotlightMediaProps) {
- {idx + 1}/{props.images.length} + {props.images.length > 1 && `${idx + 1}/${props.images.length}`}
{props.images.length > 1 && ( @@ -74,7 +74,7 @@ export function SpotlightMedia(props: SpotlightMediaProps) { export function SpotlightMediaModal(props: SpotlightMediaProps) { return ( - + ); diff --git a/packages/app/src/Element/Embed/Mention.tsx b/packages/app/src/Element/Embed/Mention.tsx index a54ec56c..3739917b 100644 --- a/packages/app/src/Element/Embed/Mention.tsx +++ b/packages/app/src/Element/Embed/Mention.tsx @@ -1,21 +1,16 @@ -import { useMemo } from "react"; import { Link } from "react-router-dom"; import { HexKey } from "@snort/system"; import { useUserProfile } from "@snort/system-react"; import { profileLink } from "SnortUtils"; -import { getDisplayName } from "Element/User/ProfileImage"; +import DisplayName from "../User/DisplayName"; export default function Mention({ pubkey, relays }: { pubkey: HexKey; relays?: Array | string }) { const user = useUserProfile(pubkey); - const name = useMemo(() => { - return getDisplayName(user, pubkey); - }, [user, pubkey]); - return ( e.stopPropagation()}> - @{name} + @ ); } diff --git a/packages/app/src/Element/Embed/PubkeyList.tsx b/packages/app/src/Element/Embed/PubkeyList.tsx index d604563d..d3ce0ba8 100644 --- a/packages/app/src/Element/Embed/PubkeyList.tsx +++ b/packages/app/src/Element/Embed/PubkeyList.tsx @@ -7,7 +7,7 @@ import FollowListBase from "Element/User/FollowListBase"; import AsyncButton from "Element/AsyncButton"; import { useWallet } from "Wallet"; import { Toastore } from "Toaster"; -import { getDisplayName } from "Element/User/ProfileImage"; +import { getDisplayName } from "Element/User/DisplayName"; import { UserCache } from "Cache"; import useLogin from "Hooks/useLogin"; import useEventPublisher from "Hooks/useEventPublisher"; diff --git a/packages/app/src/Element/Event/HiddenNote.tsx b/packages/app/src/Element/Event/HiddenNote.tsx new file mode 100644 index 00000000..f55f885c --- /dev/null +++ b/packages/app/src/Element/Event/HiddenNote.tsx @@ -0,0 +1,23 @@ +import messages from "../messages"; +import { useState } from "react"; +import { FormattedMessage } from "react-intl"; + +const HiddenNote = ({ children }: { children: React.ReactNode }) => { + const [show, setShow] = useState(false); + return show ? ( + children + ) : ( +
+
+

+ +

+ +
+
+ ); +}; + +export default HiddenNote; diff --git a/packages/app/src/Element/Event/Note.tsx b/packages/app/src/Element/Event/Note.tsx index 8cf17aa0..aba48e7c 100644 --- a/packages/app/src/Element/Event/Note.tsx +++ b/packages/app/src/Element/Event/Note.tsx @@ -1,45 +1,14 @@ import "./Note.css"; -import React, { useMemo, useState, ReactNode } from "react"; -import { useNavigate, Link } from "react-router-dom"; -import { useInView } from "react-intersection-observer"; -import { useIntl, FormattedMessage } from "react-intl"; -import { TaggedNostrEvent, HexKey, EventKind, NostrPrefix, Lists, EventExt, parseZap, NostrLink } from "@snort/system"; - -import { System } from "index"; -import useEventPublisher from "Hooks/useEventPublisher"; -import Icon from "Icons/Icon"; -import ProfileImage from "Element/User/ProfileImage"; -import Text from "Element/Text"; -import { - getReactions, - dedupeByPubkey, - tagFilterOfTextRepost, - hexToBech32, - normalizeReaction, - Reaction, - profileLink, - findTag, -} from "SnortUtils"; -import NoteFooter from "Element/Event/NoteFooter"; -import NoteTime from "Element/Event/NoteTime"; -import Reveal from "Element/Event/Reveal"; -import useModeration from "Hooks/useModeration"; -import { UserCache } from "Cache"; -import Poll from "Element/Event/Poll"; -import useLogin from "Hooks/useLogin"; -import { setBookmarked, setPinned } from "Login"; +import React from "react"; +import { EventKind, TaggedNostrEvent } from "@snort/system"; import { NostrFileElement } from "Element/Event/NostrFileHeader"; import ZapstrEmbed from "Element/Embed/ZapstrEmbed"; import PubkeyList from "Element/Embed/PubkeyList"; import { LiveEvent } from "Element/LiveEvent"; -import { NoteContextMenu, NoteTranslation } from "Element/Event/NoteContextMenu"; -import Reactions from "Element/Event/Reactions"; import { ZapGoal } from "Element/Event/ZapGoal"; import NoteReaction from "Element/Event/NoteReaction"; import ProfilePreview from "Element/User/ProfilePreview"; -import { ProxyImg } from "Element/ProxyImg"; - -import messages from "../messages"; +import { NoteInner } from "./NoteInner"; export interface NoteProps { data: TaggedNostrEvent; @@ -66,24 +35,6 @@ export interface NoteProps { }; } -const HiddenNote = ({ children }: { children: React.ReactNode }) => { - const [show, setShow] = useState(false); - return show ? ( - children - ) : ( -
-
-

- -

- -
-
- ); -}; - export default function Note(props: NoteProps) { const { data: ev, className } = props; if (ev.kind === EventKind.Repost) { @@ -110,366 +61,3 @@ export default function Note(props: NoteProps) { return ; } - -export function NoteInner(props: NoteProps) { - const { data: ev, related, highlight, options: opt, ignoreModeration = false, className } = props; - - const baseClassName = `note card${className ? ` ${className}` : ""}`; - const navigate = useNavigate(); - const [showReactions, setShowReactions] = useState(false); - const deletions = useMemo(() => getReactions(related, ev.id, EventKind.Deletion), [related]); - const { isEventMuted } = useModeration(); - const { ref, inView } = useInView({ triggerOnce: true }); - const login = useLogin(); - const { pinned, bookmarked } = login; - const publisher = useEventPublisher(); - const [translated, setTranslated] = useState(); - const { formatMessage } = useIntl(); - const reactions = useMemo(() => getReactions(related, ev.id, EventKind.Reaction), [related, ev]); - const groupReactions = useMemo(() => { - const result = reactions?.reduce( - (acc, reaction) => { - const kind = normalizeReaction(reaction.content); - const rs = acc[kind] || []; - return { ...acc, [kind]: [...rs, reaction] }; - }, - { - [Reaction.Positive]: [] as TaggedNostrEvent[], - [Reaction.Negative]: [] as TaggedNostrEvent[], - }, - ); - return { - [Reaction.Positive]: dedupeByPubkey(result[Reaction.Positive]), - [Reaction.Negative]: dedupeByPubkey(result[Reaction.Negative]), - }; - }, [reactions]); - const positive = groupReactions[Reaction.Positive]; - const negative = groupReactions[Reaction.Negative]; - const reposts = useMemo( - () => - dedupeByPubkey([ - ...getReactions(related, ev.id, EventKind.TextNote).filter(e => e.tags.some(tagFilterOfTextRepost(e, ev.id))), - ...getReactions(related, ev.id, EventKind.Repost), - ]), - [related, ev], - ); - const zaps = useMemo(() => { - const sortedZaps = getReactions(related, ev.id, EventKind.ZapReceipt) - .map(a => parseZap(a, UserCache, ev)) - .filter(z => z.valid); - sortedZaps.sort((a, b) => b.amount - a.amount); - return sortedZaps; - }, [related]); - const totalReactions = positive.length + negative.length + reposts.length + zaps.length; - - const options = { - showHeader: true, - showTime: true, - showFooter: true, - canUnpin: false, - canUnbookmark: false, - showContextMenu: true, - ...opt, - }; - - async function unpin(id: HexKey) { - if (options.canUnpin && publisher) { - if (window.confirm(formatMessage(messages.ConfirmUnpin))) { - const es = pinned.item.filter(e => e !== id); - const ev = await publisher.noteList(es, Lists.Pinned); - System.BroadcastEvent(ev); - setPinned(login, es, ev.created_at * 1000); - } - } - } - - async function unbookmark(id: HexKey) { - if (options.canUnbookmark && publisher) { - if (window.confirm(formatMessage(messages.ConfirmUnbookmark))) { - const es = bookmarked.item.filter(e => e !== id); - const ev = await publisher.noteList(es, Lists.Bookmarked); - System.BroadcastEvent(ev); - setBookmarked(login, es, ev.created_at * 1000); - } - } - } - - const innerContent = () => { - if (ev.kind === EventKind.LongFormTextNote) { - const title = findTag(ev, "title"); - const summary = findTag(ev, "simmary"); - const image = findTag(ev, "image"); - return ( -
-

{title}

-
-

{summary}

- - {image && } -
-
- ); - } else { - const body = ev?.content ?? ""; - return ( - - ); - } - }; - - const transformBody = () => { - if (deletions?.length > 0) { - return ( - - - - ); - } - const contentWarning = ev.tags.find(a => a[0] === "content-warning"); - if (contentWarning) { - return ( - - {c}, - }} - /> - {contentWarning[1] && ( - <> -   - {c}, - reason: contentWarning[1], - }} - /> - - )} -   - - - }> - {innerContent()} - - ); - } - return innerContent(); - }; - - function goToEvent( - e: React.MouseEvent, - eTarget: TaggedNostrEvent, - isTargetAllowed: boolean = e.target === e.currentTarget, - ) { - if (!isTargetAllowed || opt?.canClick === false) { - return; - } - - e.stopPropagation(); - if (props.onClick) { - props.onClick(eTarget); - return; - } - - const link = NostrLink.fromEvent(eTarget); - // detect cmd key and open in new tab - if (e.metaKey) { - window.open(`/e/${link.encode()}`, "_blank"); - } else { - navigate(`/e/${link.encode()}`, { - state: eTarget, - }); - } - } - - function replyTag() { - const thread = EventExt.extractThread(ev); - if (thread === undefined) { - return undefined; - } - - const maxMentions = 2; - const replyTo = thread?.replyTo ?? thread?.root; - const replyLink = replyTo - ? NostrLink.fromTag( - [replyTo.key, replyTo.value ?? "", replyTo.relay ?? "", replyTo.marker ?? ""].filter(a => a.length > 0), - ) - : undefined; - const mentions: { pk: string; name: string; link: ReactNode }[] = []; - for (const pk of thread?.pubKeys ?? []) { - const u = UserCache.getFromCache(pk); - const npub = hexToBech32(NostrPrefix.PublicKey, pk); - const shortNpub = npub.substring(0, 12); - mentions.push({ - pk, - name: u?.name ?? shortNpub, - link: {u?.name ? `@${u.name}` : shortNpub}, - }); - } - mentions.sort(a => (a.name.startsWith(NostrPrefix.PublicKey) ? 1 : -1)); - const othersLength = mentions.length - maxMentions; - const renderMention = (m: { link: React.ReactNode; pk: string; name: string }, idx: number) => { - return ( - - {idx > 0 && ", "} - {m.link} - - ); - }; - const pubMentions = - mentions.length > maxMentions ? mentions?.slice(0, maxMentions).map(renderMention) : mentions?.map(renderMention); - const others = mentions.length > maxMentions ? formatMessage(messages.Others, { n: othersLength }) : ""; - return ( -
- re:  - {(mentions?.length ?? 0) > 0 ? ( - <> - {pubMentions} {others} - - ) : ( - replyLink && {replyLink.encode().substring(0, 12)} - )} -
- ); - } - - const canRenderAsTextNote = [EventKind.TextNote, EventKind.Polls, EventKind.LongFormTextNote]; - if (!canRenderAsTextNote.includes(ev.kind)) { - const alt = findTag(ev, "alt"); - if (alt) { - return ( -
- -
- ); - } else { - return ( - <> -

- -

-
{JSON.stringify(ev, undefined, "  ")}
- - ); - } - } - - function translation() { - if (translated && translated.confidence > 0.5) { - return ( - <> -

- -

- {translated.text} - - ); - } else if (translated) { - return ( -

- -

- ); - } - } - - function pollOptions() { - if (ev.kind !== EventKind.Polls) return; - - return ; - } - - function content() { - if (!inView) return undefined; - return ( - <> - {options.showHeader && ( -
- -
- {(options.showTime || options.showBookmarked) && ( - <> - {options.showBookmarked && ( -
unbookmark(ev.id)}> - -
- )} - {!options.showBookmarked && } - - )} - {options.showPinned && ( -
unpin(ev.id)}> - -
- )} - {options.showContextMenu && ( - {}} - onTranslated={t => setTranslated(t)} - setShowReactions={setShowReactions} - /> - )} -
-
- )} -
goToEvent(e, ev, true)}> - {transformBody()} - {translation()} - {pollOptions()} - {options.showReactionsLink && ( -
setShowReactions(true)}> - -
- )} -
- {options.showFooter && } - - - ); - } - - const note = ( -
goToEvent(e, ev)} ref={ref}> - {content()} -
- ); - - return !ignoreModeration && isEventMuted(ev) ? {note} : note; -} diff --git a/packages/app/src/Element/Event/NoteFooter.tsx b/packages/app/src/Element/Event/NoteFooter.tsx index eed07d57..f5e4617e 100644 --- a/packages/app/src/Element/Event/NoteFooter.tsx +++ b/packages/app/src/Element/Event/NoteFooter.tsx @@ -18,7 +18,7 @@ import { useInteractionCache } from "Hooks/useInteractionCache"; import { ZapPoolController } from "ZapPoolController"; import { System } from "index"; import { Zapper, ZapTarget } from "Zapper"; -import { getDisplayName } from "../User/ProfileImage"; +import { getDisplayName } from "Element/User/DisplayName"; import { useNoteCreator } from "State/NoteCreator"; import messages from "../messages"; diff --git a/packages/app/src/Element/Event/NoteInner.tsx b/packages/app/src/Element/Event/NoteInner.tsx new file mode 100644 index 00000000..4fc00af5 --- /dev/null +++ b/packages/app/src/Element/Event/NoteInner.tsx @@ -0,0 +1,397 @@ +import { Link, useNavigate } from "react-router-dom"; +import React, { ReactNode, useMemo, useState } from "react"; +import { + dedupeByPubkey, + findTag, + getReactions, + hexToBech32, + normalizeReaction, + profileLink, + Reaction, + tagFilterOfTextRepost, +} from "../../SnortUtils"; +import useModeration from "../../Hooks/useModeration"; +import { useInView } from "react-intersection-observer"; +import useLogin from "../../Hooks/useLogin"; +import useEventPublisher from "../../Hooks/useEventPublisher"; +import { NoteContextMenu, NoteTranslation } from "./NoteContextMenu"; +import { FormattedMessage, useIntl } from "react-intl"; +import { UserCache } from "../../Cache"; +import messages from "../messages"; +import { System } from "../../index"; +import { setBookmarked, setPinned } from "../../Login"; +import Text from "../Text"; +import { ProxyImg } from "../ProxyImg"; +import Reveal from "./Reveal"; +import Poll from "./Poll"; +import ProfileImage from "../User/ProfileImage"; +import Icon from "../../Icons/Icon"; +import NoteTime from "./NoteTime"; +import NoteFooter from "./NoteFooter"; +import Reactions from "./Reactions"; +import HiddenNote from "./HiddenNote"; +import { NoteProps } from "./Note"; +import { EventExt, EventKind, HexKey, Lists, NostrLink, NostrPrefix, parseZap, TaggedNostrEvent } from "@snort/system"; + +export function NoteInner(props: NoteProps) { + const { data: ev, related, highlight, options: opt, ignoreModeration = false, className } = props; + + const baseClassName = `note card${className ? ` ${className}` : ""}`; + const navigate = useNavigate(); + const [showReactions, setShowReactions] = useState(false); + const deletions = useMemo(() => getReactions(related, ev.id, EventKind.Deletion), [related]); + const { isEventMuted } = useModeration(); + const { ref, inView } = useInView({ triggerOnce: true }); + const login = useLogin(); + const { pinned, bookmarked } = login; + const publisher = useEventPublisher(); + const [translated, setTranslated] = useState(); + const { formatMessage } = useIntl(); + const reactions = useMemo(() => getReactions(related, ev.id, EventKind.Reaction), [related, ev]); + const groupReactions = useMemo(() => { + const result = reactions?.reduce( + (acc, reaction) => { + const kind = normalizeReaction(reaction.content); + const rs = acc[kind] || []; + return { ...acc, [kind]: [...rs, reaction] }; + }, + { + [Reaction.Positive]: [] as TaggedNostrEvent[], + [Reaction.Negative]: [] as TaggedNostrEvent[], + }, + ); + return { + [Reaction.Positive]: dedupeByPubkey(result[Reaction.Positive]), + [Reaction.Negative]: dedupeByPubkey(result[Reaction.Negative]), + }; + }, [reactions]); + const positive = groupReactions[Reaction.Positive]; + const negative = groupReactions[Reaction.Negative]; + const reposts = useMemo( + () => + dedupeByPubkey([ + ...getReactions(related, ev.id, EventKind.TextNote).filter(e => e.tags.some(tagFilterOfTextRepost(e, ev.id))), + ...getReactions(related, ev.id, EventKind.Repost), + ]), + [related, ev], + ); + const zaps = useMemo(() => { + const sortedZaps = getReactions(related, ev.id, EventKind.ZapReceipt) + .map(a => parseZap(a, UserCache, ev)) + .filter(z => z.valid); + sortedZaps.sort((a, b) => b.amount - a.amount); + return sortedZaps; + }, [related]); + const totalReactions = positive.length + negative.length + reposts.length + zaps.length; + + const options = { + showHeader: true, + showTime: true, + showFooter: true, + canUnpin: false, + canUnbookmark: false, + showContextMenu: true, + ...opt, + }; + + async function unpin(id: HexKey) { + if (options.canUnpin && publisher) { + if (window.confirm(formatMessage(messages.ConfirmUnpin))) { + const es = pinned.item.filter(e => e !== id); + const ev = await publisher.noteList(es, Lists.Pinned); + System.BroadcastEvent(ev); + setPinned(login, es, ev.created_at * 1000); + } + } + } + + async function unbookmark(id: HexKey) { + if (options.canUnbookmark && publisher) { + if (window.confirm(formatMessage(messages.ConfirmUnbookmark))) { + const es = bookmarked.item.filter(e => e !== id); + const ev = await publisher.noteList(es, Lists.Bookmarked); + System.BroadcastEvent(ev); + setBookmarked(login, es, ev.created_at * 1000); + } + } + } + + const innerContent = () => { + if (ev.kind === EventKind.LongFormTextNote) { + const title = findTag(ev, "title"); + const summary = findTag(ev, "simmary"); + const image = findTag(ev, "image"); + return ( +
+

{title}

+
+

{summary}

+ + {image && } +
+
+ ); + } else { + const body = ev?.content ?? ""; + return ( + + ); + } + }; + + const transformBody = () => { + if (deletions?.length > 0) { + return ( + + + + ); + } + const contentWarning = ev.tags.find(a => a[0] === "content-warning"); + if (contentWarning) { + return ( + + {c}, + }} + /> + {contentWarning[1] && ( + <> +   + {c}, + reason: contentWarning[1], + }} + /> + + )} +   + + + }> + {innerContent()} + + ); + } + return innerContent(); + }; + + function goToEvent( + e: React.MouseEvent, + eTarget: TaggedNostrEvent, + isTargetAllowed: boolean = e.target === e.currentTarget, + ) { + if (!isTargetAllowed || opt?.canClick === false) { + return; + } + + e.stopPropagation(); + if (props.onClick) { + props.onClick(eTarget); + return; + } + + const link = NostrLink.fromEvent(eTarget); + // detect cmd key and open in new tab + if (e.metaKey) { + window.open(`/e/${link.encode()}`, "_blank"); + } else { + navigate(`/e/${link.encode()}`, { + state: eTarget, + }); + } + } + + function replyTag() { + const thread = EventExt.extractThread(ev); + if (thread === undefined) { + return undefined; + } + + const maxMentions = 2; + const replyTo = thread?.replyTo ?? thread?.root; + const replyLink = replyTo + ? NostrLink.fromTag( + [replyTo.key, replyTo.value ?? "", replyTo.relay ?? "", replyTo.marker ?? ""].filter(a => a.length > 0), + ) + : undefined; + const mentions: { pk: string; name: string; link: ReactNode }[] = []; + for (const pk of thread?.pubKeys ?? []) { + const u = UserCache.getFromCache(pk); + const npub = hexToBech32(NostrPrefix.PublicKey, pk); + const shortNpub = npub.substring(0, 12); + mentions.push({ + pk, + name: u?.name ?? shortNpub, + link: {u?.name ? `@${u.name}` : shortNpub}, + }); + } + mentions.sort(a => (a.name.startsWith(NostrPrefix.PublicKey) ? 1 : -1)); + const othersLength = mentions.length - maxMentions; + const renderMention = (m: { link: React.ReactNode; pk: string; name: string }, idx: number) => { + return ( + + {idx > 0 && ", "} + {m.link} + + ); + }; + const pubMentions = + mentions.length > maxMentions ? mentions?.slice(0, maxMentions).map(renderMention) : mentions?.map(renderMention); + const others = mentions.length > maxMentions ? formatMessage(messages.Others, { n: othersLength }) : ""; + return ( +
+ re:  + {(mentions?.length ?? 0) > 0 ? ( + <> + {pubMentions} {others} + + ) : ( + replyLink && {replyLink.encode().substring(0, 12)} + )} +
+ ); + } + + const canRenderAsTextNote = [EventKind.TextNote, EventKind.Polls, EventKind.LongFormTextNote]; + if (!canRenderAsTextNote.includes(ev.kind)) { + const alt = findTag(ev, "alt"); + if (alt) { + return ( +
+ +
+ ); + } else { + return ( + <> +

+ +

+
{JSON.stringify(ev, undefined, "  ")}
+ + ); + } + } + + function translation() { + if (translated && translated.confidence > 0.5) { + return ( + <> +

+ +

+ {translated.text} + + ); + } else if (translated) { + return ( +

+ +

+ ); + } + } + + function pollOptions() { + if (ev.kind !== EventKind.Polls) return; + + return ; + } + + function content() { + if (!inView) return undefined; + return ( + <> + {options.showHeader && ( +
+ +
+ {(options.showTime || options.showBookmarked) && ( + <> + {options.showBookmarked && ( +
unbookmark(ev.id)}> + +
+ )} + {!options.showBookmarked && } + + )} + {options.showPinned && ( +
unpin(ev.id)}> + +
+ )} + {options.showContextMenu && ( + {}} + onTranslated={t => setTranslated(t)} + setShowReactions={setShowReactions} + /> + )} +
+
+ )} +
goToEvent(e, ev, true)}> + {transformBody()} + {translation()} + {pollOptions()} + {options.showReactionsLink && ( +
setShowReactions(true)}> + +
+ )} +
+ {options.showFooter && } + + + ); + } + + const note = ( +
goToEvent(e, ev)} ref={ref}> + {content()} +
+ ); + + return !ignoreModeration && isEventMuted(ev) ? {note} : note; +} diff --git a/packages/app/src/Element/Event/NoteReaction.tsx b/packages/app/src/Element/Event/NoteReaction.tsx index a398c335..923a51a2 100644 --- a/packages/app/src/Element/Event/NoteReaction.tsx +++ b/packages/app/src/Element/Event/NoteReaction.tsx @@ -4,7 +4,7 @@ import { useMemo } from "react"; import { EventKind, NostrEvent, TaggedNostrEvent, NostrPrefix, EventExt } from "@snort/system"; import Note from "Element/Event/Note"; -import { getDisplayName } from "Element/User/ProfileImage"; +import { getDisplayName } from "Element/User/DisplayName"; import { eventLink, hexToBech32 } from "SnortUtils"; import useModeration from "Hooks/useModeration"; import FormattedMessage from "Element/FormattedMessage"; diff --git a/packages/app/src/Element/Modal.tsx b/packages/app/src/Element/Modal.tsx index d67b3598..f0c6c57f 100644 --- a/packages/app/src/Element/Modal.tsx +++ b/packages/app/src/Element/Modal.tsx @@ -5,6 +5,7 @@ export interface ModalProps { id: string; className?: string; onClose?: (e: React.MouseEvent | KeyboardEvent) => void; + onClick?: (e: React.MouseEvent) => void; children: ReactNode; } @@ -28,7 +29,13 @@ export default function Modal(props: ModalProps) { return (
-
e.stopPropagation()}>{props.children}
+
{ + e.stopPropagation(); + props.onClick?.(e); + }}> + {props.children} +
); diff --git a/packages/app/src/Element/User/AnimalName.ts b/packages/app/src/Element/User/AnimalName.ts new file mode 100644 index 00000000..6b08e55d --- /dev/null +++ b/packages/app/src/Element/User/AnimalName.ts @@ -0,0 +1,1835 @@ +import { sha256 } from "@noble/hashes/sha256"; + +const animals = [ + "canidae", + "felidae", + "cat", + "cattle", + "dog", + "donkey", + "goat", + "horse", + "pig", + "rabbit", + "aardvark", + "aardwolf", + "albatross", + "alligator", + "alpaca", + "amphibian", + "anaconda", + "angelfish", + "anglerfish", + "ant", + "anteater", + "antelope", + "antlion", + "ape", + "aphid", + "armadillo", + "asp", + "baboon", + "badger", + "bandicoot", + "barnacle", + "barracuda", + "basilisk", + "bass", + "bat", + "bear", + "beaver", + "bedbug", + "bee", + "beetle", + "bird", + "bison", + "blackbird", + "boa", + "boar", + "bobcat", + "bobolink", + "bonobo", + "booby", + "bovid", + "bug", + "butterfly", + "buzzard", + "camel", + "canid", + "capybara", + "cardinal", + "caribou", + "carp", + "cat", + "catshark", + "caterpillar", + "catfish", + "cattle", + "centipede", + "cephalopod", + "chameleon", + "cheetah", + "chickadee", + "chicken", + "chimpanzee", + "chinchilla", + "chipmunk", + "clam", + "clownfish", + "cobra", + "cockroach", + "cod", + "condor", + "constrictor", + "coral", + "cougar", + "cow", + "coyote", + "crab", + "crane", + "crawdad", + "crayfish", + "cricket", + "crocodile", + "crow", + "cuckoo", + "cicada", + "damselfly", + "deer", + "dingo", + "dinosaur", + "dog", + "dolphin", + "donkey", + "dormouse", + "dove", + "dragonfly", + "dragon", + "duck", + "eagle", + "earthworm", + "earwig", + "echidna", + "eel", + "egret", + "elephant", + "elk", + "emu", + "ermine", + "falcon", + "ferret", + "finch", + "firefly", + "fish", + "flamingo", + "flea", + "fly", + "flyingfish", + "fowl", + "fox", + "frog", + "gamefowl", + "galliform", + "gazelle", + "gecko", + "gerbil", + "gibbon", + "giraffe", + "goat", + "goldfish", + "goose", + "gopher", + "gorilla", + "grasshopper", + "grouse", + "guan", + "guanaco", + "guineafowl", + "gull", + "guppy", + "haddock", + "halibut", + "hamster", + "hare", + "harrier", + "hawk", + "hedgehog", + "heron", + "herring", + "hippopotamus", + "hookworm", + "hornet", + "horse", + "hoverfly", + "hummingbird", + "hyena", + "iguana", + "impala", + "jackal", + "jaguar", + "jay", + "jellyfish", + "junglefowl", + "kangaroo", + "kingfisher", + "kite", + "kiwi", + "koala", + "koi", + "krill", + "ladybug", + "lamprey", + "landfowl", + "lark", + "leech", + "lemming", + "lemur", + "leopard", + "leopon", + "limpet", + "lion", + "lizard", + "llama", + "lobster", + "locust", + "loon", + "louse", + "lungfish", + "lynx", + "macaw", + "mackerel", + "magpie", + "mammal", + "manatee", + "mandrill", + "marlin", + "marmoset", + "marmot", + "marsupial", + "marten", + "mastodon", + "meadowlark", + "meerkat", + "mink", + "minnow", + "mite", + "mockingbird", + "mole", + "mollusk", + "mongoose", + "monkey", + "moose", + "mosquito", + "moth", + "mouse", + "mule", + "muskox", + "narwhal", + "newt", + "nightingale", + "ocelot", + "octopus", + "opossum", + "orangutan", + "orca", + "ostrich", + "otter", + "owl", + "ox", + "panda", + "panther", + "parakeet", + "parrot", + "parrotfish", + "partridge", + "peacock", + "peafowl", + "pelican", + "penguin", + "perch", + "pheasant", + "pig", + "pigeon", + "pike", + "pinniped", + "piranha", + "planarian", + "platypus", + "pony", + "porcupine", + "porpoise", + "possum", + "prawn", + "primate", + "ptarmigan", + "puffin", + "puma", + "python", + "quail", + "quelea", + "quokka", + "rabbit", + "raccoon", + "rat", + "rattlesnake", + "raven", + "reindeer", + "reptile", + "rhinoceros", + "roadrunner", + "rodent", + "rook", + "rooster", + "roundworm", + "sailfish", + "salamander", + "salmon", + "sawfish", + "scallop", + "scorpion", + "seahorse", + "shark", + "sheep", + "shrew", + "shrimp", + "silkworm", + "silverfish", + "skink", + "skunk", + "sloth", + "slug", + "smelt", + "snail", + "snake", + "snipe", + "sole", + "sparrow", + "spider", + "spoonbill", + "squid", + "squirrel", + "starfish", + "stingray", + "stoat", + "stork", + "sturgeon", + "swallow", + "swan", + "swift", + "swordfish", + "swordtail", + "tahr", + "takin", + "tapir", + "tarantula", + "tarsier", + "termite", + "tern", + "thrush", + "tick", + "tiger", + "tiglon", + "toad", + "tortoise", + "toucan", + "trout", + "tuna", + "turkey", + "turtle", + "tyrannosaurus", + "urial", + "vicuna", + "viper", + "vole", + "vulture", + "wallaby", + "walrus", + "wasp", + "warbler", + "weasel", + "whale", + "whippet", + "whitefish", + "wildcat", + "wildebeest", + "wildfowl", + "wolf", + "wolverine", + "wombat", + "woodpecker", + "worm", + "wren", + "xerinae", + "yak", + "zebra", + "alpaca", + "cat", + "cattle", + "chicken", + "dog", + "donkey", + "ferret", + "gayal", + "goldfish", + "guppy", + "horse", + "koi", + "llama", + "sheep", + "yak", + "unicorn", +]; + +const adjectives = [ + "average", + "big", + "colossal", + "fat", + "giant", + "gigantic", + "great", + "huge", + "immense", + "large", + "little", + "long", + "mammoth", + "massive", + "miniature", + "petite", + "puny", + "short", + "small", + "tall", + "tiny", + "boiling", + "breezy", + "broken", + "bumpy", + "chilly", + "cold", + "cool", + "creepy", + "crooked", + "cuddly", + "curly", + "damaged", + "damp", + "dirty", + "dry", + "dusty", + "filthy", + "flaky", + "fluffy", + "wet", + "broad", + "chubby", + "crooked", + "curved", + "deep", + "flat", + "high", + "hollow", + "low", + "narrow", + "round", + "shallow", + "skinny", + "square", + "steep", + "straight", + "wide", + "ancient", + "brief", + "early", + "fast", + "late", + "long", + "modern", + "old", + "quick", + "rapid", + "short", + "slow", + "swift", + "young", + "abundant", + "empty", + "few", + "heavy", + "light", + "many", + "numerous", + "Sound", + "cooing", + "deafening", + "faint", + "harsh", + "hissing", + "hushed", + "husky", + "loud", + "melodic", + "moaning", + "mute", + "noisy", + "purring", + "quiet", + "raspy", + "resonant", + "screeching", + "shrill", + "silent", + "soft", + "squealing", + "thundering", + "voiceless", + "whispering", + "bitter", + "delicious", + "fresh", + "juicy", + "ripe", + "rotten", + "salty", + "sour", + "spicy", + "stale", + "sticky", + "strong", + "sweet", + "tasteless", + "tasty", + "thirsty", + "fluttering", + "fuzzy", + "greasy", + "grubby", + "hard", + "hot", + "icy", + "loose", + "melted", + "plastic", + "prickly", + "rainy", + "rough", + "scattered", + "shaggy", + "shaky", + "sharp", + "shivering", + "silky", + "slimy", + "slippery", + "smooth", + "soft", + "solid", + "steady", + "sticky", + "tender", + "tight", + "uneven", + "weak", + "wet", + "wooden", + "afraid", + "angry", + "annoyed", + "anxious", + "arrogant", + "ashamed", + "awful", + "bad", + "bewildered", + "bored", + "combative", + "condemned", + "confused", + "creepy", + "cruel", + "dangerous", + "defeated", + "defiant", + "depressed", + "disgusted", + "disturbed", + "eerie", + "embarrassed", + "envious", + "evil", + "fierce", + "foolish", + "frantic", + "frightened", + "grieving", + "helpless", + "homeless", + "hungry", + "hurt", + "ill", + "jealous", + "lonely", + "mysterious", + "naughty", + "nervous", + "obnoxious", + "outrageous", + "panicky", + "repulsive", + "scary", + "scornful", + "selfish", + "sore", + "tense", + "terrible", + "thoughtless", + "tired", + "troubled", + "upset", + "uptight", + "weary", + "wicked", + "worried", + "agreeable", + "amused", + "brave", + "calm", + "charming", + "cheerful", + "comfortable", + "cooperative", + "courageous", + "delightful", + "determined", + "eager", + "elated", + "enchanting", + "encouraging", + "energetic", + "enthusiastic", + "excited", + "exuberant", + "fair", + "faithful", + "fantastic", + "fine", + "friendly", + "funny", + "gentle", + "glorious", + "good", + "happy", + "healthy", + "helpful", + "hilarious", + "jolly", + "joyous", + "kind", + "lively", + "lovely", + "lucky", + "obedient", + "perfect", + "pleasant", + "proud", + "relieved", + "silly", + "smiling", + "splendid", + "successful", + "thoughtful", + "victorious", + "vivacious", + "witty", + "wonderful", + "zealous", + "zany", + "other", + "good", + "new", + "old", + "great", + "high", + "small", + "different", + "large", + "local", + "social", + "important", + "long", + "young", + "national", + "british", + "right", + "early", + "possible", + "big", + "little", + "political", + "able", + "late", + "general", + "full", + "far", + "low", + "public", + "available", + "bad", + "main", + "sure", + "clear", + "major", + "economic", + "only", + "likely", + "real", + "black", + "particular", + "international", + "special", + "difficult", + "certain", + "open", + "whole", + "white", + "free", + "short", + "easy", + "strong", + "european", + "central", + "similar", + "human", + "common", + "necessary", + "single", + "personal", + "hard", + "private", + "poor", + "financial", + "wide", + "foreign", + "simple", + "recent", + "concerned", + "american", + "various", + "close", + "fine", + "english", + "wrong", + "present", + "royal", + "natural", + "individual", + "nice", + "french", + "nihilist", + "solipsist", + "materialist", + "surrealist", + "heroic", + "awesome", + "hedonist", + "absurd", + "current", + "modern", + "labour", + "legal", + "happy", + "final", + "red", + "normal", + "serious", + "previous", + "total", + "prime", + "significant", + "industrial", + "sorry", + "dead", + "specific", + "appropriate", + "top", + "soviet", + "basic", + "military", + "original", + "successful", + "aware", + "hon", + "popular", + "heavy", + "professional", + "direct", + "dark", + "cold", + "ready", + "green", + "useful", + "effective", + "western", + "traditional", + "scottish", + "german", + "independent", + "deep", + "interesting", + "considerable", + "involved", + "physical", + "hot", + "existing", + "responsible", + "complete", + "medical", + "blue", + "extra", + "past", + "male", + "interested", + "fair", + "essential", + "beautiful", + "civil", + "primary", + "obvious", + "future", + "environmental", + "positive", + "senior", + "nuclear", + "annual", + "relevant", + "huge", + "rich", + "commercial", + "safe", + "regional", + "practical", + "official", + "separate", + "key", + "chief", + "regular", + "due", + "additional", + "active", + "powerful", + "complex", + "standard", + "impossible", + "light", + "warm", + "middle", + "fresh", + "sexual", + "front", + "domestic", + "actual", + "united", + "technical", + "ordinary", + "cheap", + "strange", + "internal", + "excellent", + "quiet", + "soft", + "potential", + "northern", + "religious", + "quick", + "very", + "famous", + "cultural", + "proper", + "broad", + "joint", + "formal", + "limited", + "conservative", + "lovely", + "usual", + "ltd", + "unable", + "rural", + "initial", + "substantial", + "bright", + "average", + "leading", + "reasonable", + "immediate", + "suitable", + "equal", + "detailed", + "working", + "overall", + "female", + "afraid", + "democratic", + "growing", + "sufficient", + "scientific", + "eastern", + "correct", + "inc", + "irish", + "expensive", + "educational", + "mental", + "dangerous", + "critical", + "increased", + "familiar", + "unlikely", + "double", + "perfect", + "slow", + "tiny", + "dry", + "historical", + "thin", + "daily", + "southern", + "increasing", + "wild", + "alone", + "urban", + "empty", + "married", + "narrow", + "liberal", + "supposed", + "upper", + "apparent", + "tall", + "busy", + "bloody", + "prepared", + "russian", + "moral", + "careful", + "clean", + "attractive", + "japanese", + "vital", + "thick", + "alternative", + "fast", + "ancient", + "elderly", + "rare", + "external", + "capable", + "brief", + "wonderful", + "grand", + "typical", + "entire", + "grey", + "constant", + "vast", + "surprised", + "ideal", + "terrible", + "academic", + "funny", + "minor", + "pleased", + "severe", + "ill", + "corporate", + "negative", + "permanent", + "weak", + "brown", + "fundamental", + "odd", + "crucial", + "inner", + "used", + "criminal", + "contemporary", + "sharp", + "sick", + "near", + "roman", + "massive", + "unique", + "secondary", + "parliamentary", + "african", + "unknown", + "subsequent", + "angry", + "alive", + "guilty", + "lucky", + "enormous", + "well", + "yellow", + "unusual", + "net", + "tough", + "dear", + "extensive", + "glad", + "remaining", + "agricultural", + "alright", + "healthy", + "italian", + "principal", + "tired", + "efficient", + "comfortable", + "chinese", + "relative", + "friendly", + "conventional", + "willing", + "sudden", + "proposed", + "voluntary", + "slight", + "valuable", + "dramatic", + "golden", + "temporary", + "federal", + "keen", + "flat", + "silent", + "indian", + "worried", + "pale", + "statutory", + "welsh", + "dependent", + "firm", + "wet", + "competitive", + "armed", + "radical", + "outside", + "acceptable", + "sensitive", + "living", + "pure", + "global", + "emotional", + "sad", + "secret", + "rapid", + "adequate", + "fixed", + "sweet", + "administrative", + "wooden", + "remarkable", + "comprehensive", + "surprising", + "solid", + "rough", + "mere", + "mass", + "brilliant", + "maximum", + "absolute", + "electronic", + "visual", + "electric", + "cool", + "spanish", + "literary", + "continuing", + "supreme", + "chemical", + "genuine", + "exciting", + "written", + "advanced", + "extreme", + "classical", + "fit", + "favourite", + "widespread", + "confident", + "straight", + "proud", + "numerous", + "opposite", + "distinct", + "mad", + "helpful", + "given", + "disabled", + "consistent", + "anxious", + "nervous", + "awful", + "stable", + "constitutional", + "satisfied", + "conscious", + "developing", + "strategic", + "holy", + "smooth", + "dominant", + "remote", + "theoretical", + "outstanding", + "pink", + "pretty", + "clinical", + "minimum", + "honest", + "impressive", + "related", + "residential", + "extraordinary", + "plain", + "visible", + "accurate", + "distant", + "still", + "greek", + "complicated", + "musical", + "precise", + "gentle", + "broken", + "live", + "silly", + "fat", + "tight", + "monetary", + "round", + "psychological", + "violent", + "unemployed", + "inevitable", + "junior", + "sensible", + "grateful", + "pleasant", + "dirty", + "structural", + "welcome", + "deaf", + "above", + "continuous", + "blind", + "overseas", + "mean", + "entitled", + "delighted", + "loose", + "occasional", + "evident", + "desperate", + "fellow", + "universal", + "square", + "steady", + "classic", + "equivalent", + "intellectual", + "victorian", + "level", + "ultimate", + "creative", + "lost", + "medieval", + "clever", + "linguistic", + "convinced", + "judicial", + "raw", + "sophisticated", + "asleep", + "vulnerable", + "illegal", + "outer", + "revolutionary", + "bitter", + "changing", + "australian", + "native", + "imperial", + "strict", + "wise", + "informal", + "flexible", + "collective", + "frequent", + "experimental", + "spiritual", + "intense", + "rational", + "generous", + "inadequate", + "prominent", + "logical", + "bare", + "historic", + "modest", + "dutch", + "acute", + "electrical", + "valid", + "weekly", + "gross", + "automatic", + "loud", + "reliable", + "mutual", + "liable", + "multiple", + "ruling", + "curious", + "sole", + "managing", + "pregnant", + "latin", + "nearby", + "exact", + "underlying", + "identical", + "satisfactory", + "marginal", + "distinctive", + "electoral", + "urgent", + "presidential", + "controversial", + "everyday", + "encouraging", + "organic", + "continued", + "expected", + "statistical", + "desirable", + "innocent", + "improved", + "exclusive", + "marked", + "experienced", + "unexpected", + "superb", + "sheer", + "disappointed", + "frightened", + "gastric", + "romantic", + "naked", + "reluctant", + "magnificent", + "convenient", + "established", + "closed", + "uncertain", + "artificial", + "diplomatic", + "tremendous", + "marine", + "mechanical", + "retail", + "institutional", + "mixed", + "required", + "biological", + "known", + "functional", + "straightforward", + "superior", + "digital", + "spectacular", + "unhappy", + "confused", + "unfair", + "aggressive", + "spare", + "painful", + "abstract", + "asian", + "associated", + "legislative", + "monthly", + "intelligent", + "hungry", + "explicit", + "nasty", + "just", + "faint", + "coloured", + "ridiculous", + "amazing", + "comparable", + "successive", + "realistic", + "back", + "decent", + "decentralized", + "bitcoin", + "cypherpunk", + "unnecessary", + "flying", + "random", + "influential", + "dull", + "genetic", + "neat", + "marvellous", + "crazy", + "damp", + "giant", + "secure", + "bottom", + "skilled", + "subtle", + "elegant", + "brave", + "lesser", + "parallel", + "steep", + "intensive", + "casual", + "tropical", + "lonely", + "partial", + "preliminary", + "concrete", + "alleged", + "assistant", + "vertical", + "upset", + "delicate", + "mild", + "occupational", + "excessive", + "progressive", + "exceptional", + "integrated", + "striking", + "continental", + "okay", + "harsh", + "combined", + "fierce", + "handsome", + "characteristic", + "chronic", + "compulsory", + "interim", + "objective", + "splendid", + "magic", + "systematic", + "obliged", + "payable", + "fun", + "horrible", + "primitive", + "fascinating", + "ideological", + "metropolitan", + "surrounding", + "estimated", + "peaceful", + "premier", + "operational", + "technological", + "kind", + "advisory", + "hostile", + "precious", + "accessible", + "determined", + "excited", + "impressed", + "provincial", + "smart", + "endless", + "isolated", + "drunk", + "geographical", + "like", + "dynamic", + "boring", + "forthcoming", + "unfortunate", + "definite", + "super", + "notable", + "indirect", + "stiff", + "wealthy", + "awkward", + "lively", + "neutral", + "artistic", + "content", + "mature", + "colonial", + "ambitious", + "evil", + "magnetic", + "verbal", + "legitimate", + "sympathetic", + "empirical", + "head", + "shallow", + "vague", + "naval", + "depressed", + "shared", + "added", + "shocked", + "mid", + "worthwhile", + "qualified", + "missing", + "blank", + "absent", + "favourable", + "polish", + "israeli", + "developed", + "profound", + "representative", + "enthusiastic", + "dreadful", + "rigid", + "reduced", + "cruel", + "coastal", + "peculiar", + "swiss", + "crude", + "extended", + "selected", + "eager", + "canadian", + "bold", + "relaxed", + "corresponding", + "running", + "planned", + "applicable", + "immense", + "allied", + "comparative", + "uncomfortable", + "conservation", + "productive", + "beneficial", + "bored", + "charming", + "minimal", + "mobile", + "turkish", + "orange", + "rear", + "passive", + "suspicious", + "overwhelming", + "fatal", + "resulting", + "symbolic", + "registered", + "neighbouring", + "calm", + "irrelevant", + "patient", + "compact", + "profitable", + "rival", + "loyal", + "moderate", + "distinguished", + "interior", + "noble", + "insufficient", + "eligible", + "mysterious", + "varying", + "managerial", + "molecular", + "olympic", + "linear", + "prospective", + "printed", + "parental", + "diverse", + "elaborate", + "furious", + "fiscal", + "burning", + "useless", + "semantic", + "embarrassed", + "inherent", + "philosophical", + "deliberate", + "awake", + "variable", + "promising", + "unpleasant", + "varied", + "sacred", + "selective", + "inclined", + "tender", + "hidden", + "worthy", + "intermediate", + "sound", + "protective", + "fortunate", + "slim", + "defensive", + "divine", + "stuck", + "driving", + "invisible", + "misleading", + "circular", + "mathematical", + "inappropriate", + "liquid", + "persistent", + "solar", + "doubtful", + "manual", + "architectural", + "intact", + "incredible", + "devoted", + "prior", + "tragic", + "respectable", + "optimistic", + "convincing", + "unacceptable", + "decisive", + "competent", + "spatial", + "respective", + "binding", + "relieved", + "nursing", + "toxic", + "select", + "redundant", + "integral", + "then", + "probable", + "amateur", + "fond", + "passing", + "specified", + "territorial", + "horizontal", + "inland", + "cognitive", + "regulatory", + "miserable", + "resident", + "polite", + "scared", + "gothic", + "civilian", + "instant", + "lengthy", + "adverse", + "korean", + "unconscious", + "anonymous", + "aesthetic", + "orthodox", + "static", + "unaware", + "costly", + "fantastic", + "foolish", + "fashionable", + "causal", + "compatible", + "wee", + "implicit", + "dual", + "ok", + "cheerful", + "subjective", + "forward", + "surviving", + "exotic", + "purple", + "cautious", + "visiting", + "aggregate", + "ethical", + "teenage", + "dying", + "disastrous", + "delicious", + "confidential", + "underground", + "thorough", + "grim", + "autonomous", + "atomic", + "frozen", + "colourful", + "injured", + "uniform", + "ashamed", + "glorious", + "wicked", + "coherent", + "rising", + "shy", + "novel", + "balanced", + "delightful", + "arbitrary", + "adjacent", + "worrying", + "weird", + "unchanged", + "rolling", + "evolutionary", + "intimate", + "sporting", + "disciplinary", + "formidable", + "lexical", + "noisy", + "gradual", + "accused", + "homeless", + "supporting", + "coming", + "renewed", + "excess", + "retired", + "rubber", + "chosen", + "outdoor", + "embarrassing", + "preferred", + "bizarre", + "appalling", + "agreed", + "imaginative", + "governing", + "accepted", + "vocational", + "mighty", + "puzzled", + "worldwide", + "organisational", + "sunny", + "eldest", + "eventual", + "spontaneous", + "vivid", + "rude", + "faithful", + "ministerial", + "innovative", + "controlled", + "conceptual", + "unwilling", + "civic", + "meaningful", + "alive", + "brainy", + "breakable", + "busy", + "careful", + "cautious", + "clever", + "concerned", + "crazy", + "curious", + "dead", + "different", + "difficult", + "doubtful", + "easy", + "famous", + "fragile", + "helpful", + "helpless", + "important", + "impossible", + "innocent", + "inquisitive", + "modern", + "open", + "outstanding", + "poor", + "powerful", + "puzzled", + "real", + "rich", + "shy", + "sleepy", + "super", + "tame", + "uninterested", + "wandering", + "wild", + "wrong", + "adorable", + "alert", + "average", + "beautiful", + "blonde", + "bloody", + "blushing", + "bright", + "clean", + "clear", + "cloudy", + "colorful", + "crowded", + "cute", + "dark", + "drab", + "distinct", + "dull", + "elegant", + "fancy", + "filthy", + "glamorous", + "gleaming", + "graceful", + "grotesque", + "homely", + "light", + "misty", + "motionless", + "muddy", + "plain", + "poised", + "quaint", + "shiny", + "smoggy", + "sparkling", + "spotless", + "stormy", + "strange", + "ugly", + "unsightly", + "unusual", + "bad", + "better", + "beautiful", + "big", + "black", + "blue", + "bright", + "clumsy", + "crazy", + "dizzy", + "dull", + "fat", + "frail", + "friendly", + "funny", + "great", + "green", + "gigantic", + "gorgeous", + "grumpy", + "handsome", + "happy", + "horrible", + "itchy", + "jittery", + "jolly", + "kind", + "long", + "lazy", + "magnificent", + "magenta", + "many", + "mighty", + "mushy", + "nasty", + "new", + "nice", + "nosy", + "nutty", + "nutritious", + "odd", + "orange", + "ordinary", + "pretty", + "precious", + "prickly", + "purple", + "quaint", + "quiet", + "quick", + "quickest", + "rainy", + "rare", + "ratty", + "red", + "roasted", + "robust", + "round", + "sad", + "scary", + "scrawny", + "short", + "silly", + "stingy", + "strange", + "striped", + "spotty", + "tart", + "tall", + "tame", + "tan", + "tender", + "testy", + "tricky", + "tough", + "ugly", + "ugliest", + "vast", + "watery", + "wasteful", + "wonderful", + "yellow", + "yummy", + "zany", +]; + +function capitalize(s: string) { + return s.charAt(0).toUpperCase() + s.slice(1); +} + +/** + * deterministically create adjective + animal names + */ +export default function (seed: string) { + if (!seed) { + throw new Error("No seed provided"); + } + const hash = sha256(seed); // Uint8Array + const adjective = adjectives[hash[0] % adjectives.length]; + const animal = animals[hash[1] % animals.length]; + return `${capitalize(adjective)} ${capitalize(animal)}`; +} diff --git a/packages/app/src/Element/User/Avatar.tsx b/packages/app/src/Element/User/Avatar.tsx index 2fd201fe..d7c0b736 100644 --- a/packages/app/src/Element/User/Avatar.tsx +++ b/packages/app/src/Element/User/Avatar.tsx @@ -4,7 +4,7 @@ import { CSSProperties, ReactNode, useEffect, useState } from "react"; import type { UserMetadata } from "@snort/system"; import useImgProxy from "Hooks/useImgProxy"; -import { getDisplayName } from "Element/User/ProfileImage"; +import { getDisplayName } from "Element/User/DisplayName"; import { defaultAvatar } from "SnortUtils"; interface AvatarProps { diff --git a/packages/app/src/Element/User/DisplayName.css b/packages/app/src/Element/User/DisplayName.css new file mode 100644 index 00000000..a68803c4 --- /dev/null +++ b/packages/app/src/Element/User/DisplayName.css @@ -0,0 +1,3 @@ +.placeholder { + color: var(--gray-light); +} diff --git a/packages/app/src/Element/User/DisplayName.tsx b/packages/app/src/Element/User/DisplayName.tsx new file mode 100644 index 00000000..b7e58f8f --- /dev/null +++ b/packages/app/src/Element/User/DisplayName.tsx @@ -0,0 +1,39 @@ +import "./DisplayName.css"; + +import React, { useMemo } from "react"; +import { HexKey, UserMetadata, NostrPrefix } from "@snort/system"; +import AnimalName from "Element/User/AnimalName"; +import { hexToBech32 } from "SnortUtils"; + +interface DisplayNameProps { + pubkey: HexKey; + user: UserMetadata | undefined; +} + +export function getDisplayName(user: UserMetadata | undefined, pubkey: HexKey): string { + return getDisplayNameOrPlaceHolder(user, pubkey)[0]; +} + +export function getDisplayNameOrPlaceHolder(user: UserMetadata | undefined, pubkey: HexKey): [string, boolean] { + let name = hexToBech32(NostrPrefix.PublicKey, pubkey).substring(0, 12); + let isPlaceHolder = false; + + if (typeof user?.display_name === "string" && user.display_name.length > 0) { + name = user.display_name; + } else if (typeof user?.name === "string" && user.name.length > 0) { + name = user.name; + } else if (pubkey && process.env.ANIMAL_NAME_PLACEHOLDERS) { + name = AnimalName(pubkey); + isPlaceHolder = true; + } + + return [name.trim(), isPlaceHolder]; +} + +const DisplayName = ({ pubkey, user }: DisplayNameProps) => { + const [name, isPlaceHolder] = useMemo(() => getDisplayNameOrPlaceHolder(user, pubkey), [user, pubkey]); + + return {name}; +}; + +export default DisplayName; diff --git a/packages/app/src/Element/User/ProfileImage.css b/packages/app/src/Element/User/ProfileImage.css index c48468cc..ec37206c 100644 --- a/packages/app/src/Element/User/ProfileImage.css +++ b/packages/app/src/Element/User/ProfileImage.css @@ -13,7 +13,6 @@ height: 48px; cursor: pointer; position: relative; - z-index: 2; } a.pfp { diff --git a/packages/app/src/Element/User/ProfileImage.tsx b/packages/app/src/Element/User/ProfileImage.tsx index 007f50c2..7fc6d6b1 100644 --- a/packages/app/src/Element/User/ProfileImage.tsx +++ b/packages/app/src/Element/User/ProfileImage.tsx @@ -1,15 +1,16 @@ import "./ProfileImage.css"; -import React, { ReactNode, useMemo } from "react"; +import React, { ReactNode } from "react"; import { Link } from "react-router-dom"; -import { HexKey, NostrPrefix, UserMetadata } from "@snort/system"; +import { HexKey, UserMetadata } from "@snort/system"; import { useUserProfile } from "@snort/system-react"; -import { hexToBech32, profileLink } from "SnortUtils"; +import { profileLink } from "SnortUtils"; import Avatar from "Element/User/Avatar"; import Nip05 from "Element/User/Nip05"; import useLogin from "Hooks/useLogin"; import Icon from "Icons/Icon"; +import DisplayName from "./DisplayName"; export interface ProfileImageProps { pubkey: HexKey; @@ -49,10 +50,6 @@ export default function ProfileImage({ const { follows } = useLogin(); const doesFollow = follows.item.includes(pubkey); - const name = useMemo(() => { - return overrideUsername ?? getDisplayName(user, pubkey); - }, [user, pubkey, overrideUsername]); - function handleClick(e: React.MouseEvent) { if (link === "") { e.preventDefault(); @@ -86,7 +83,7 @@ export default function ProfileImage({ {showUsername && (
-
{name.trim()}
+ {overrideUsername ? overrideUsername : } {nip05 && }
{subHeader}
@@ -113,13 +110,3 @@ export default function ProfileImage({ ); } } - -export function getDisplayName(user: UserMetadata | undefined, pubkey: HexKey) { - let name = hexToBech32(NostrPrefix.PublicKey, pubkey).substring(0, 12); - if (typeof user?.display_name === "string" && user.display_name.length > 0) { - name = user.display_name; - } else if (typeof user?.name === "string" && user.name.length > 0) { - name = user.name; - } - return name; -} diff --git a/packages/app/src/Notifications.ts b/packages/app/src/Notifications.ts index 4da9df5a..814d55af 100644 --- a/packages/app/src/Notifications.ts +++ b/packages/app/src/Notifications.ts @@ -1,5 +1,5 @@ import { TaggedNostrEvent, EventKind, MetadataCache } from "@snort/system"; -import { getDisplayName } from "Element/User/ProfileImage"; +import { getDisplayName } from "Element/User/DisplayName"; import { MentionRegex } from "Const"; import { defaultAvatar, tagFilterOfTextRepost, unwrap } from "SnortUtils"; import { UserCache } from "Cache"; diff --git a/packages/app/src/Pages/FreeNostrAddressPage.tsx b/packages/app/src/Pages/FreeNostrAddressPage.tsx index a3d5ca24..664180f7 100644 --- a/packages/app/src/Pages/FreeNostrAddressPage.tsx +++ b/packages/app/src/Pages/FreeNostrAddressPage.tsx @@ -1,12 +1,7 @@ import FormattedMessage from "@snort/app/src/Element/FormattedMessage"; -/* -import { IrisHost } from "Const"; -import Nip5Service from "Element/Nip5Service"; - */ - import messages from "./messages"; -import IrisAccount from "../Element/IrisAccount/IrisAccount"; +import IrisAccount from "Element/IrisAccount/IrisAccount"; export default function FreeNostrAddressPage() { return ( diff --git a/packages/app/src/Pages/MessagesPage.tsx b/packages/app/src/Pages/MessagesPage.tsx index e3c69585..35a09d3c 100644 --- a/packages/app/src/Pages/MessagesPage.tsx +++ b/packages/app/src/Pages/MessagesPage.tsx @@ -7,7 +7,7 @@ import { NostrLink, NostrPrefix, TLVEntryType, UserMetadata, decodeTLV } from "@ import { useUserProfile, useUserSearch } from "@snort/system-react"; import UnreadCount from "Element/UnreadCount"; -import ProfileImage, { getDisplayName } from "Element/User/ProfileImage"; +import ProfileImage from "Element/User/ProfileImage"; import { appendDedupe, debounce, parseId } from "SnortUtils"; import NoteToSelf from "Element/User/NoteToSelf"; import useModeration from "Hooks/useModeration"; @@ -25,6 +25,7 @@ import { useEventFeed } from "Feed/EventFeed"; import { LoginSession, LoginStore } from "Login"; import { Nip28ChatSystem } from "chat/nip28"; import { ChatParticipantProfile } from "Element/Chat/ChatParticipant"; +import { getDisplayName } from "Element/User/DisplayName"; const TwoCol = 768; const ThreeCol = 1500; diff --git a/packages/app/src/Pages/Notifications.tsx b/packages/app/src/Pages/Notifications.tsx index 3e0325a5..a3784007 100644 --- a/packages/app/src/Pages/Notifications.tsx +++ b/packages/app/src/Pages/Notifications.tsx @@ -12,13 +12,14 @@ import { markNotificationsRead } from "Login"; import { Notifications, UserCache } from "Cache"; import { dedupe, findTag, orderDescending } from "SnortUtils"; import Icon from "Icons/Icon"; -import ProfileImage, { getDisplayName } from "Element/User/ProfileImage"; +import ProfileImage from "Element/User/ProfileImage"; import useModeration from "Hooks/useModeration"; import { useEventFeed } from "Feed/EventFeed"; import Text from "Element/Text"; import { formatShort } from "Number"; import { LiveEvent } from "Element/LiveEvent"; import ProfilePreview from "Element/User/ProfilePreview"; +import { getDisplayName } from "Element/User/DisplayName"; function notificationContext(ev: TaggedNostrEvent) { switch (ev.kind) { diff --git a/packages/app/src/Pages/ProfilePage.tsx b/packages/app/src/Pages/ProfilePage.tsx index d1c8d066..5c2501ad 100644 --- a/packages/app/src/Pages/ProfilePage.tsx +++ b/packages/app/src/Pages/ProfilePage.tsx @@ -58,7 +58,7 @@ import { ZapTarget } from "Zapper"; import { useStatusFeed } from "Feed/StatusFeed"; import messages from "./messages"; -import { SpotlightMediaModal } from "../Element/Deck/SpotlightMedia"; +import { SpotlightMediaModal } from "Element/Deck/SpotlightMedia"; const NOTES = 0; const REACTIONS = 1; diff --git a/packages/app/src/ZapPoolController.ts b/packages/app/src/ZapPoolController.ts index 99a290d4..6667e76e 100644 --- a/packages/app/src/ZapPoolController.ts +++ b/packages/app/src/ZapPoolController.ts @@ -1,5 +1,5 @@ import { UserCache } from "Cache"; -import { getDisplayName } from "Element/User/ProfileImage"; +import { getDisplayName } from "Element/User/DisplayName"; import { LNURL, ExternalStore, unixNow } from "@snort/shared"; import { Toastore } from "Toaster"; import { LNWallet, WalletInvoiceState, Wallets } from "Wallet"; diff --git a/packages/app/webpack.config.js b/packages/app/webpack.config.js index ab9ec892..aa115b0b 100644 --- a/packages/app/webpack.config.js +++ b/packages/app/webpack.config.js @@ -90,6 +90,8 @@ const config = { "process.env.APP_NAME": JSON.stringify(appConfig.get("appName")), "process.env.APP_NAME_CAPITALIZED": JSON.stringify(appConfig.get("appNameCapitalized")), "process.env.NIP05_DOMAIN": JSON.stringify(appConfig.get("nip05Domain")), + "process.env.HTTP_CACHE": JSON.stringify(appConfig.get("httpCache")), + "process.env.ANIMAL_NAME_PLACEHOLDERS": JSON.stringify(appConfig.get("animalNamePlaceholders")), }), ], module: { diff --git a/packages/system-react/src/useUserProfile.ts b/packages/system-react/src/useUserProfile.ts index 5d718761..d6b12585 100644 --- a/packages/system-react/src/useUserProfile.ts +++ b/packages/system-react/src/useUserProfile.ts @@ -11,6 +11,24 @@ export function useUserProfile(pubKey?: HexKey): MetadataCache | undefined { h => { if (pubKey) { system.ProfileLoader.TrackMetadata(pubKey); + if (process.env.HTTP_CACHE && !system.ProfileLoader.Cache.getFromCache(pubKey)) { + fetch(`${process.env.HTTP_CACHE}/profile/${pubKey}`) + .then(async r => { + if (r.ok) { + try { + const data = await r.json(); + if (data) { + system.ProfileLoader.onProfileEvent(data); + } + } catch (e) { + console.error(e); + } + } + }) + .catch(e => { + console.error(e); + }); + } } const release = system.ProfileLoader.Cache.hook(h, pubKey); return () => {