refactor: use regular text for chat

This commit is contained in:
verbiricha 2023-07-31 18:24:39 +02:00
parent 65fd21f334
commit db56a801d0
4 changed files with 156 additions and 232 deletions

View File

@ -14,7 +14,7 @@ import { EmojiPicker } from "./emoji-picker";
import { Icon } from "./icon";
import { Emoji } from "./emoji";
import { Profile } from "./profile";
import { Markdown } from "./markdown";
import { Text } from "element/text";
import { SendZapsDialog } from "./send-zap";
import { findTag } from "../utils";
import type { EmojiPack } from "../hooks/emoji";
@ -152,12 +152,7 @@ export function ChatMessage({
pubkey={ev.pubkey}
profile={profile}
/>
<Markdown
element="span"
enableParagraphs={false}
tags={ev.tags}
content={ev.content}
/>
<Text tags={ev.tags} content={ev.content} />
{(hasReactions || hasZaps) && (
<div className="message-reactions">
{hasZaps && (

View File

@ -357,12 +357,3 @@
height: 18px;
border-radius: unset;
}
.message .markdown {
font-size: 14px;
line-height: normal;
}
.message .markdown .emoji {
width: unset;
}

View File

@ -1,204 +1,20 @@
import "./markdown.css";
import { createElement } from "react";
import { parseNostrLink } from "@snort/system";
import { useMemo } from "react";
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";
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;
}
import { transformText } from "element/text";
interface MarkdownProps {
content: string;
tags?: string[];
enableParagraphs?: booleam;
}
export function Markdown({
content,
tags = [],
enableParagraphs = true,
element = "div",
}: MarkdownProps) {
const components = useMemo(() => {
@ -208,17 +24,12 @@ export function Markdown({
},
td: ({ children }) =>
children && <td>{transformText(children, tags)}</td>,
p: ({ children }) =>
enableParagraphs ? (
<p>{transformText(children, tags)}</p>
) : (
transformText(children, tags)
),
p: ({ children }) => <p>{transformText(children, tags)}</p>,
a: (props) => {
return <HyperText link={props.href}>{props.children}</HyperText>;
},
};
}, [tags, enableParagraphs]);
}, [tags]);
return createElement(
element,
{ className: "markdown" },

View File

@ -1,32 +1,18 @@
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 { Emoji } from "./emoji";
import { HyperText } from "./hypertext";
type Fragment = string | ReactNode;
function transformText(fragments: Fragment[], tags: string[][]) {
return extractLinks(extractEmoji(fragments, tags));
}
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();
}
const NostrPrefixRegex = /^nostr:/;
const EmojiRegex = /:([\w-]+):/g;
function extractLinks(fragments: Fragment[]) {
return fragments
@ -74,6 +60,147 @@ function extractLinks(fragments: Fragment[]) {
.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[][] }) {
// todo: RTL langugage support
const element = useMemo(() => {