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