forked from Kieran/zap.stream
refactor: use regular text for chat
This commit is contained in:
parent
65fd21f334
commit
db56a801d0
@ -14,7 +14,7 @@ import { EmojiPicker } from "./emoji-picker";
|
|||||||
import { Icon } from "./icon";
|
import { Icon } from "./icon";
|
||||||
import { Emoji } from "./emoji";
|
import { Emoji } from "./emoji";
|
||||||
import { Profile } from "./profile";
|
import { Profile } from "./profile";
|
||||||
import { Markdown } from "./markdown";
|
import { Text } from "element/text";
|
||||||
import { SendZapsDialog } from "./send-zap";
|
import { SendZapsDialog } from "./send-zap";
|
||||||
import { findTag } from "../utils";
|
import { findTag } from "../utils";
|
||||||
import type { EmojiPack } from "../hooks/emoji";
|
import type { EmojiPack } from "../hooks/emoji";
|
||||||
@ -152,12 +152,7 @@ export function ChatMessage({
|
|||||||
pubkey={ev.pubkey}
|
pubkey={ev.pubkey}
|
||||||
profile={profile}
|
profile={profile}
|
||||||
/>
|
/>
|
||||||
<Markdown
|
<Text tags={ev.tags} content={ev.content} />
|
||||||
element="span"
|
|
||||||
enableParagraphs={false}
|
|
||||||
tags={ev.tags}
|
|
||||||
content={ev.content}
|
|
||||||
/>
|
|
||||||
{(hasReactions || hasZaps) && (
|
{(hasReactions || hasZaps) && (
|
||||||
<div className="message-reactions">
|
<div className="message-reactions">
|
||||||
{hasZaps && (
|
{hasZaps && (
|
||||||
|
@ -357,12 +357,3 @@
|
|||||||
height: 18px;
|
height: 18px;
|
||||||
border-radius: unset;
|
border-radius: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message .markdown {
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message .markdown .emoji {
|
|
||||||
width: unset;
|
|
||||||
}
|
|
||||||
|
@ -1,204 +1,20 @@
|
|||||||
import "./markdown.css";
|
import "./markdown.css";
|
||||||
|
|
||||||
import { createElement } from "react";
|
import { createElement } from "react";
|
||||||
import { parseNostrLink } from "@snort/system";
|
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
|
|
||||||
import { Address } from "element/Address";
|
|
||||||
import { Event } from "element/Event";
|
|
||||||
import { Mention } from "element/mention";
|
|
||||||
import { Emoji } from "element/emoji";
|
|
||||||
import { HyperText } from "element/hypertext";
|
import { HyperText } from "element/hypertext";
|
||||||
|
import { transformText } from "element/text";
|
||||||
const MentionRegex = /(#\[\d+\])/gi;
|
|
||||||
const NostrPrefixRegex = /^nostr:/;
|
|
||||||
const EmojiRegex = /:([\w-]+):/g;
|
|
||||||
|
|
||||||
function extractEmoji(fragments: Fragment[], tags: string[][]) {
|
|
||||||
return fragments
|
|
||||||
.map((f) => {
|
|
||||||
if (typeof f === "string") {
|
|
||||||
return f.split(EmojiRegex).map((i) => {
|
|
||||||
const t = tags.find((a) => a[0] === "emoji" && a[1] === i);
|
|
||||||
if (t) {
|
|
||||||
return <Emoji name={t[1]} url={t[2]} />;
|
|
||||||
} else {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return f;
|
|
||||||
})
|
|
||||||
.flat();
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractMentions(fragments, tags) {
|
|
||||||
return fragments
|
|
||||||
.map((f) => {
|
|
||||||
if (typeof f === "string") {
|
|
||||||
return f.split(MentionRegex).map((match) => {
|
|
||||||
const matchTag = match.match(/#\[(\d+)\]/);
|
|
||||||
if (matchTag && matchTag.length === 2) {
|
|
||||||
const idx = parseInt(matchTag[1]);
|
|
||||||
const ref = tags?.find((a, i) => i === idx);
|
|
||||||
if (ref) {
|
|
||||||
switch (ref[0]) {
|
|
||||||
case "p": {
|
|
||||||
return <Mention key={ref[1]} pubkey={ref[1]} />;
|
|
||||||
}
|
|
||||||
case "a": {
|
|
||||||
return <Address link={parseNostrLink(ref[1])} />;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// todo: e and t mentions
|
|
||||||
return ref[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return f;
|
|
||||||
})
|
|
||||||
.flat();
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractNprofiles(fragments) {
|
|
||||||
return fragments
|
|
||||||
.map((f) => {
|
|
||||||
if (typeof f === "string") {
|
|
||||||
return f.split(/(nostr:nprofile1[a-z0-9]+)/g).map((i) => {
|
|
||||||
if (i.startsWith("nostr:nprofile1")) {
|
|
||||||
try {
|
|
||||||
const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
|
|
||||||
return <Mention key={link.id} pubkey={link.id} />;
|
|
||||||
} catch (error) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return f;
|
|
||||||
})
|
|
||||||
.flat();
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractNpubs(fragments) {
|
|
||||||
return fragments
|
|
||||||
.map((f) => {
|
|
||||||
if (typeof f === "string") {
|
|
||||||
return f.split(/(nostr:npub1[a-z0-9]+)/g).map((i) => {
|
|
||||||
if (i.startsWith("nostr:npub1")) {
|
|
||||||
try {
|
|
||||||
const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
|
|
||||||
return <Mention key={link.id} pubkey={link.id} />;
|
|
||||||
} catch (error) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return f;
|
|
||||||
})
|
|
||||||
.flat();
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractNevents(fragments) {
|
|
||||||
return fragments
|
|
||||||
.map((f) => {
|
|
||||||
if (typeof f === "string") {
|
|
||||||
return f.split(/(nostr:nevent1[a-z0-9]+)/g).map((i) => {
|
|
||||||
if (i.startsWith("nostr:nevent1")) {
|
|
||||||
try {
|
|
||||||
const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
|
|
||||||
return <Event link={link} />;
|
|
||||||
} catch (error) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return f;
|
|
||||||
})
|
|
||||||
.flat();
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractNaddrs(fragments) {
|
|
||||||
return fragments
|
|
||||||
.map((f) => {
|
|
||||||
if (typeof f === "string") {
|
|
||||||
return f.split(/(nostr:naddr1[a-z0-9]+)/g).map((i) => {
|
|
||||||
if (i.startsWith("nostr:naddr1")) {
|
|
||||||
try {
|
|
||||||
const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
|
|
||||||
return <Address key={i} link={link} />;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return f;
|
|
||||||
})
|
|
||||||
.flat();
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractNoteIds(fragments) {
|
|
||||||
return fragments
|
|
||||||
.map((f) => {
|
|
||||||
if (typeof f === "string") {
|
|
||||||
return f.split(/(nostr:note1[a-z0-9]+)/g).map((i) => {
|
|
||||||
if (i.startsWith("nostr:note1")) {
|
|
||||||
try {
|
|
||||||
const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
|
|
||||||
return <Event link={link} />;
|
|
||||||
} catch (error) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return f;
|
|
||||||
})
|
|
||||||
.flat();
|
|
||||||
}
|
|
||||||
|
|
||||||
function transformText(ps, tags) {
|
|
||||||
let fragments = extractMentions(ps, tags);
|
|
||||||
fragments = extractNprofiles(fragments);
|
|
||||||
fragments = extractNevents(fragments);
|
|
||||||
fragments = extractNaddrs(fragments);
|
|
||||||
fragments = extractNoteIds(fragments);
|
|
||||||
fragments = extractNpubs(fragments);
|
|
||||||
fragments = extractEmoji(fragments, tags);
|
|
||||||
|
|
||||||
return fragments;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MarkdownProps {
|
interface MarkdownProps {
|
||||||
content: string;
|
content: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
enableParagraphs?: booleam;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Markdown({
|
export function Markdown({
|
||||||
content,
|
content,
|
||||||
tags = [],
|
tags = [],
|
||||||
enableParagraphs = true,
|
|
||||||
element = "div",
|
element = "div",
|
||||||
}: MarkdownProps) {
|
}: MarkdownProps) {
|
||||||
const components = useMemo(() => {
|
const components = useMemo(() => {
|
||||||
@ -208,17 +24,12 @@ export function Markdown({
|
|||||||
},
|
},
|
||||||
td: ({ children }) =>
|
td: ({ children }) =>
|
||||||
children && <td>{transformText(children, tags)}</td>,
|
children && <td>{transformText(children, tags)}</td>,
|
||||||
p: ({ children }) =>
|
p: ({ children }) => <p>{transformText(children, tags)}</p>,
|
||||||
enableParagraphs ? (
|
|
||||||
<p>{transformText(children, tags)}</p>
|
|
||||||
) : (
|
|
||||||
transformText(children, tags)
|
|
||||||
),
|
|
||||||
a: (props) => {
|
a: (props) => {
|
||||||
return <HyperText link={props.href}>{props.children}</HyperText>;
|
return <HyperText link={props.href}>{props.children}</HyperText>;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, [tags, enableParagraphs]);
|
}, [tags]);
|
||||||
return createElement(
|
return createElement(
|
||||||
element,
|
element,
|
||||||
{ className: "markdown" },
|
{ className: "markdown" },
|
||||||
|
@ -1,32 +1,18 @@
|
|||||||
import { useMemo, type ReactNode } from "react";
|
import { useMemo, type ReactNode } from "react";
|
||||||
import { validateNostrLink } from "@snort/system";
|
|
||||||
|
import { 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";
|
||||||
|
import { HyperText } from "element/hypertext";
|
||||||
import { splitByUrl } from "utils";
|
import { splitByUrl } from "utils";
|
||||||
import { Emoji } from "./emoji";
|
|
||||||
import { HyperText } from "./hypertext";
|
|
||||||
|
|
||||||
type Fragment = string | ReactNode;
|
type Fragment = string | ReactNode;
|
||||||
|
|
||||||
function transformText(fragments: Fragment[], tags: string[][]) {
|
const NostrPrefixRegex = /^nostr:/;
|
||||||
return extractLinks(extractEmoji(fragments, tags));
|
const EmojiRegex = /:([\w-]+):/g;
|
||||||
}
|
|
||||||
|
|
||||||
function extractEmoji(fragments: Fragment[], tags: string[][]) {
|
|
||||||
return fragments
|
|
||||||
.map((f) => {
|
|
||||||
if (typeof f === "string") {
|
|
||||||
return f.split(/:([\w-]+):/g).map((i) => {
|
|
||||||
const t = tags.find((a) => a[0] === "emoji" && a[1] === i);
|
|
||||||
if (t) {
|
|
||||||
return <Emoji name={t[1]} url={t[2]} />;
|
|
||||||
} else {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return f;
|
|
||||||
})
|
|
||||||
.flat();
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractLinks(fragments: Fragment[]) {
|
function extractLinks(fragments: Fragment[]) {
|
||||||
return fragments
|
return fragments
|
||||||
@ -74,6 +60,147 @@ function extractLinks(fragments: Fragment[]) {
|
|||||||
.flat();
|
.flat();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractEmoji(fragments: Fragment[], tags: string[][]) {
|
||||||
|
return fragments
|
||||||
|
.map((f) => {
|
||||||
|
if (typeof f === "string") {
|
||||||
|
return f.split(EmojiRegex).map((i) => {
|
||||||
|
const t = tags.find((a) => a[0] === "emoji" && a[1] === i);
|
||||||
|
if (t) {
|
||||||
|
return <Emoji name={t[1]} url={t[2]} />;
|
||||||
|
} else {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractNprofiles(fragments: Fragment[]) {
|
||||||
|
return fragments
|
||||||
|
.map((f) => {
|
||||||
|
if (typeof f === "string") {
|
||||||
|
return f.split(/(nostr:nprofile1[a-z0-9]+)/g).map((i) => {
|
||||||
|
if (i.startsWith("nostr:nprofile1")) {
|
||||||
|
try {
|
||||||
|
const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
|
||||||
|
return <Mention key={link.id} pubkey={link.id} />;
|
||||||
|
} catch (error) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractNpubs(fragments: Fragment[]) {
|
||||||
|
return fragments
|
||||||
|
.map((f) => {
|
||||||
|
if (typeof f === "string") {
|
||||||
|
return f.split(/(nostr:npub1[a-z0-9]+)/g).map((i) => {
|
||||||
|
if (i.startsWith("nostr:npub1")) {
|
||||||
|
try {
|
||||||
|
const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
|
||||||
|
return <Mention key={link.id} pubkey={link.id} />;
|
||||||
|
} catch (error) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractNevents(fragments: Fragment[]) {
|
||||||
|
return fragments
|
||||||
|
.map((f) => {
|
||||||
|
if (typeof f === "string") {
|
||||||
|
return f.split(/(nostr:nevent1[a-z0-9]+)/g).map((i) => {
|
||||||
|
if (i.startsWith("nostr:nevent1")) {
|
||||||
|
try {
|
||||||
|
const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
|
||||||
|
return <Event link={link} />;
|
||||||
|
} catch (error) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractNaddrs(fragments: Fragment[]) {
|
||||||
|
return fragments
|
||||||
|
.map((f) => {
|
||||||
|
if (typeof f === "string") {
|
||||||
|
return f.split(/(nostr:naddr1[a-z0-9]+)/g).map((i) => {
|
||||||
|
if (i.startsWith("nostr:naddr1")) {
|
||||||
|
try {
|
||||||
|
const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
|
||||||
|
return <Address key={i} link={link} />;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractNoteIds(fragments: Fragment[]) {
|
||||||
|
return fragments
|
||||||
|
.map((f) => {
|
||||||
|
if (typeof f === "string") {
|
||||||
|
return f.split(/(nostr:note1[a-z0-9]+)/g).map((i) => {
|
||||||
|
if (i.startsWith("nostr:note1")) {
|
||||||
|
try {
|
||||||
|
const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
|
||||||
|
return <Event link={link} />;
|
||||||
|
} catch (error) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformText(ps: Fragment[], tags: Array<string[]>) {
|
||||||
|
let fragments = extractEmoji(ps, tags);
|
||||||
|
fragments = extractNprofiles(fragments);
|
||||||
|
fragments = extractNevents(fragments);
|
||||||
|
fragments = extractNaddrs(fragments);
|
||||||
|
fragments = extractNoteIds(fragments);
|
||||||
|
fragments = extractNpubs(fragments);
|
||||||
|
fragments = extractLinks(fragments);
|
||||||
|
|
||||||
|
return fragments;
|
||||||
|
}
|
||||||
|
|
||||||
export function Text({ content, tags }: { content: string; tags: string[][] }) {
|
export function Text({ content, tags }: { content: string; tags: string[][] }) {
|
||||||
// todo: RTL langugage support
|
// todo: RTL langugage support
|
||||||
const element = useMemo(() => {
|
const element = useMemo(() => {
|
||||||
|
Loading…
Reference in New Issue
Block a user