From 08d554aa8fd064ebb5b4b3698861c8fcd07f963f Mon Sep 17 00:00:00 2001 From: verbiricha Date: Wed, 2 Aug 2023 18:59:01 +0200 Subject: [PATCH] feat: collapsible event references in chat --- package.json | 1 + public/icons.svg | 12 +++++++ src/element/Event.tsx | 65 +++++++++++++++++++++++++++++++----- src/element/address.tsx | 24 ------------- src/element/chat-message.tsx | 11 +++++- src/element/collapsible.css | 24 +++++++++++++ src/element/collapsible.tsx | 49 ++++++++++++++++++++++++++- src/element/markdown.tsx | 33 ++++++++++++++---- src/element/text.tsx | 46 ++++++++++++++++++------- src/index.css | 6 ++++ yarn.lock | 28 ++++++++++++++++ 11 files changed, 246 insertions(+), 53 deletions(-) delete mode 100644 src/element/address.tsx diff --git a/package.json b/package.json index e68b515..64c7716 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@emoji-mart/react": "^1.1.1", "@noble/curves": "^1.1.0", "@noble/hashes": "^1.3.1", + "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-tabs": "^1.0.4", diff --git a/public/icons.svg b/public/icons.svg index 291ba8d..8de597e 100644 --- a/public/icons.svg +++ b/public/icons.svg @@ -73,5 +73,17 @@ fill="currentColor" /> + + + + + + + + + + + + diff --git a/src/element/Event.tsx b/src/element/Event.tsx index c9160fb..f07fae3 100644 --- a/src/element/Event.tsx +++ b/src/element/Event.tsx @@ -1,33 +1,80 @@ import "./event.css"; -import { type NostrLink, EventKind } from "@snort/system"; -import { useEvent } from "hooks/event"; -import { GOAL } from "const"; +import { + type NostrLink, + type NostrEvent as NostrEventType, + EventKind, +} from "@snort/system"; + +import { Icon } from "element/icon"; import { Goal } from "element/goal"; import { Note } from "element/note"; +import { EmojiPack } from "element/emoji-pack"; +import { Badge } from "element/badge"; +import { useEvent } from "hooks/event"; +import { GOAL, EMOJI_PACK } from "const"; interface EventProps { link: NostrLink; } -export function Event({ link }: EventProps) { - const event = useEvent(link); +export function EventIcon({ kind }: { kind: EventKind }) { + if (kind === GOAL) { + return ; + } - if (event?.kind === GOAL) { + if (kind === EMOJI_PACK) { + return ; + } + + if (kind === EventKind.Badge) { + return ; + } + + if (kind === EventKind.TextNote) { + return ; + } + + return null; +} + +export function NostrEvent({ ev }: { ev: NostrEventType }) { + if (ev?.kind === GOAL) { return (
- +
); } - if (event?.kind === EventKind.TextNote) { + if (ev?.kind === EMOJI_PACK) { return (
- + +
+ ); + } + + if (ev?.kind === EventKind.Badge) { + return ( +
+ +
+ ); + } + + if (ev?.kind === EventKind.TextNote) { + return ( +
+
); } return null; } + +export function Event({ link }: EventProps) { + const event = useEvent(link); + return event ? : null; +} diff --git a/src/element/address.tsx b/src/element/address.tsx deleted file mode 100644 index 548a7cd..0000000 --- a/src/element/address.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { type NostrLink, EventKind } from "@snort/system"; - -import { useEvent } from "hooks/event"; -import { EMOJI_PACK } from "const"; -import { EmojiPack } from "element/emoji-pack"; -import { Badge } from "element/badge"; - -interface AddressProps { - link: NostrLink; -} - -export function Address({ link }: AddressProps) { - const event = useEvent(link); - - if (event?.kind === EMOJI_PACK) { - return ; - } - - if (event?.kind === EventKind.Badge) { - return ; - } - - return null; -} diff --git a/src/element/chat-message.tsx b/src/element/chat-message.tsx index 6e752f1..36a6bc8 100644 --- a/src/element/chat-message.tsx +++ b/src/element/chat-message.tsx @@ -14,6 +14,7 @@ import { Emoji as EmojiComponent } from "element/emoji"; import { Profile } from "./profile"; import { Text } from "element/text"; import { SendZapsDialog } from "element/send-zap"; +import { CollapsibleEvent } from "element/collapsible"; import { useLogin } from "hooks/login"; import { formatSats } from "number"; import { findTag } from "utils"; @@ -30,6 +31,10 @@ function emojifyReaction(reaction: string) { return reaction; } +const customComponents = { + Event: CollapsibleEvent, +}; + export function ChatMessage({ streamer, ev, @@ -159,7 +164,11 @@ export function ChatMessage({ pubkey={ev.pubkey} profile={profile} /> - + {(hasReactions || hasZaps) && (
{hasZaps && ( diff --git a/src/element/collapsible.css b/src/element/collapsible.css index 3c8fddc..809bb82 100644 --- a/src/element/collapsible.css +++ b/src/element/collapsible.css @@ -18,3 +18,27 @@ color: var(--text-link); cursor: zoom-in; } + +.collapsible { + width: 100%; +} + +.collapsed-event { + display: flex; + align-items: center; + justify-content: space-between; + margin: 8px; +} + +.collapsed-event-header { + display: flex; + align-items: center; + gap: 8px; +} + +.collapsed-event-header svg { + color: var(--text-muted); +} + +.expanded-event { +} diff --git a/src/element/collapsible.tsx b/src/element/collapsible.tsx index c7b90b1..f556d57 100644 --- a/src/element/collapsible.tsx +++ b/src/element/collapsible.tsx @@ -1,7 +1,16 @@ import "./collapsible.css"; -import * as Dialog from "@radix-ui/react-dialog"; import type { ReactNode } from "react"; +import { useState } from "react"; + +import * as Dialog from "@radix-ui/react-dialog"; +import * as Collapsible from "@radix-ui/react-collapsible"; + +import type { NostrLink } from "@snort/system"; + +import { Mention } from "element/mention"; +import { NostrEvent, EventIcon } from "element/Event"; import { ExternalLink } from "element/external-link"; +import { useEvent } from "hooks/event"; interface MediaURLProps { url: URL; @@ -30,3 +39,41 @@ export function MediaURL({ url, children }: MediaURLProps) { ); } + +export function CollapsibleEvent({ link }: { link: NostrLink }) { + const event = useEvent(link); + const [open, setOpen] = useState(false); + const author = event?.pubkey || link.author; + + return ( + +
+
+ {event && } + {author && } +
+ + + +
+ + {open && event && ( +
+ {" "} + +
+ )} +
+
+ ); +} diff --git a/src/element/markdown.tsx b/src/element/markdown.tsx index 313ed45..a31874c 100644 --- a/src/element/markdown.tsx +++ b/src/element/markdown.tsx @@ -4,12 +4,17 @@ import { useMemo } from "react"; import ReactMarkdown from "react-markdown"; import { HyperText } from "element/hypertext"; -import { transformText, type Fragment } from "element/text"; +import { + transformText, + type Fragment, + type NostrComponents, +} from "element/text"; import type { Tags } from "types"; interface MarkdownProps { content: string; tags?: Tags; + customComponents?: NostrComponents; } interface LinkProps { @@ -21,20 +26,36 @@ interface ComponentProps { children?: Array; } -export function Markdown({ content, tags = [] }: MarkdownProps) { +export function Markdown({ + content, + tags = [], + customComponents, +}: MarkdownProps) { const components = useMemo(() => { return { li: ({ children, ...props }: ComponentProps) => { - return children &&
  • {transformText(children, tags)}
  • ; + return ( + children && ( +
  • + {transformText(children, tags, customComponents)} +
  • + ) + ); }, td: ({ children }: ComponentProps) => { - return children && {transformText(children, tags)}; + return ( + children && {transformText(children, tags, customComponents)} + ); }, th: ({ children }: ComponentProps) => { - return children && {transformText(children, tags)}; + return ( + children && {transformText(children, tags, customComponents)} + ); }, p: ({ children }: ComponentProps) => { - return children &&

    {transformText(children, tags)}

    ; + return ( + children &&

    {transformText(children, tags, customComponents)}

    + ); }, a: ({ href, children }: LinkProps) => { return href && {children}; diff --git a/src/element/text.tsx b/src/element/text.tsx index 6d2b92c..55aa64a 100644 --- a/src/element/text.tsx +++ b/src/element/text.tsx @@ -1,9 +1,12 @@ import "./text.css"; -import { useMemo, type ReactNode } from "react"; +import { useMemo, type ReactNode, type FunctionComponent } from "react"; -import { parseNostrLink, validateNostrLink } from "@snort/system"; +import { + type NostrLink, + parseNostrLink, + validateNostrLink, +} from "@snort/system"; -import { Address } from "element/address"; import { Event } from "element/Event"; import { Mention } from "element/mention"; import { Emoji } from "element/emoji"; @@ -110,7 +113,7 @@ function extractNpubs(fragments: Fragment[]) { .flat(); } -function extractNevents(fragments: Fragment[]) { +function extractNevents(fragments: Fragment[], Event: NostrComponent) { return fragments .map((f) => { if (typeof f === "string") { @@ -132,7 +135,7 @@ function extractNevents(fragments: Fragment[]) { .flat(); } -function extractNaddrs(fragments: Fragment[]) { +function extractNaddrs(fragments: Fragment[], Address: NostrComponent) { return fragments .map((f) => { if (typeof f === "string") { @@ -155,7 +158,7 @@ function extractNaddrs(fragments: Fragment[]) { .flat(); } -function extractNoteIds(fragments: Fragment[]) { +function extractNoteIds(fragments: Fragment[], Event: NostrComponent) { return fragments .map((f) => { if (typeof f === "string") { @@ -177,12 +180,26 @@ function extractNoteIds(fragments: Fragment[]) { .flat(); } -export function transformText(ps: Fragment[], tags: Array) { +export type NostrComponent = FunctionComponent<{ link: NostrLink }>; + +export interface NostrComponents { + Event: NostrComponent; +} + +const components: NostrComponents = { + Event, +}; + +export function transformText( + ps: Fragment[], + tags: Array, + customComponents = components +) { let fragments = extractEmoji(ps, tags); fragments = extractNprofiles(fragments); - fragments = extractNevents(fragments); - fragments = extractNaddrs(fragments); - fragments = extractNoteIds(fragments); + fragments = extractNevents(fragments, customComponents.Event); + fragments = extractNaddrs(fragments, customComponents.Event); + fragments = extractNoteIds(fragments, customComponents.Event); fragments = extractNpubs(fragments); fragments = extractLinks(fragments); @@ -192,12 +209,17 @@ export function transformText(ps: Fragment[], tags: Array) { interface TextProps { content: string; tags: Tags; + customComponents?: NostrComponents; } -export function Text({ content, tags }: TextProps) { +export function Text({ content, tags, customComponents }: TextProps) { // todo: RTL langugage support const element = useMemo(() => { - return {transformText([content], tags)}; + return ( + + {transformText([content], tags, customComponents)} + + ); }, [content, tags]); return <>{element}; diff --git a/src/index.css b/src/index.css index b9c6702..a8bb77e 100644 --- a/src/index.css +++ b/src/index.css @@ -110,6 +110,12 @@ a { gap: 8px; } +.btn-small { + font-size: 14px; + line-height: 18px; + padding: 4px 8px; +} + .btn-border { border: 1px solid transparent; color: inherit; diff --git a/yarn.lock b/yarn.lock index cd1af1b..b58f8ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1980,6 +1980,33 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-collapsible@npm:^1.0.3": + version: 1.0.3 + resolution: "@radix-ui/react-collapsible@npm:1.0.3" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/primitive": 1.0.1 + "@radix-ui/react-compose-refs": 1.0.1 + "@radix-ui/react-context": 1.0.1 + "@radix-ui/react-id": 1.0.1 + "@radix-ui/react-presence": 1.0.1 + "@radix-ui/react-primitive": 1.0.3 + "@radix-ui/react-use-controllable-state": 1.0.1 + "@radix-ui/react-use-layout-effect": 1.0.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 26976e4a72a3e0f4b2c62af2898b3e205c3652af46a3b41cda9a43567fe8381d9ef6afb0b29e3214c450b847f4f2099a533cffc5045844ecab290e9fa6114ca9 + languageName: node + linkType: hard + "@radix-ui/react-collection@npm:1.0.3": version: 1.0.3 resolution: "@radix-ui/react-collection@npm:1.0.3" @@ -9984,6 +10011,7 @@ __metadata: "@formatjs/ts-transformer": ^3.13.1 "@noble/curves": ^1.1.0 "@noble/hashes": ^1.3.1 + "@radix-ui/react-collapsible": ^1.0.3 "@radix-ui/react-dialog": ^1.0.4 "@radix-ui/react-progress": ^1.0.3 "@radix-ui/react-tabs": ^1.0.4