From 6752b80c0d2df3019753f9e2556a7ae0123e825c Mon Sep 17 00:00:00 2001 From: hzrd149 Date: Mon, 14 Aug 2023 10:53:02 -0500 Subject: [PATCH] use global regexp for embeds --- src/components/embed-types/emoji.tsx | 2 +- src/components/embed-types/lightning.tsx | 2 +- src/components/embed-types/nostr.tsx | 2 +- src/helpers/embeds.ts | 47 +++++++++++++++------- src/helpers/regexp.ts | 4 +- src/views/note/components/thread-post.tsx | 2 +- src/views/relays/components/relay-card.tsx | 12 ++++-- src/views/relays/relay/index.tsx | 2 +- 8 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/components/embed-types/emoji.tsx b/src/components/embed-types/emoji.tsx index 505cc400d..3310bbceb 100644 --- a/src/components/embed-types/emoji.tsx +++ b/src/components/embed-types/emoji.tsx @@ -4,7 +4,7 @@ import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event"; export function embedEmoji(content: EmbedableContent, note: NostrEvent | DraftNostrEvent) { return embedJSX(content, { - regexp: /:([a-zA-Z0-9_]+):/i, + regexp: /:([a-zA-Z0-9_]+):/gi, render: (match) => { const emojiTag = note.tags.find( (tag) => tag[0] === "emoji" && tag[1].toLowerCase() === match[1].toLowerCase() && tag[2] diff --git a/src/components/embed-types/lightning.tsx b/src/components/embed-types/lightning.tsx index 9fbbc9ee2..3fb4d7570 100644 --- a/src/components/embed-types/lightning.tsx +++ b/src/components/embed-types/lightning.tsx @@ -4,7 +4,7 @@ import { InlineInvoiceCard } from "../inline-invoice-card"; export function embedLightningInvoice(content: EmbedableContent) { return embedJSX(content, { name: "Lightning Invoice", - regexp: /(lightning:)?(LNBC[A-Za-z0-9]+)/im, + regexp: /(lightning:)?(LNBC[A-Za-z0-9]+)/gim, render: (match) => , }); } diff --git a/src/components/embed-types/nostr.tsx b/src/components/embed-types/nostr.tsx index 3dfc838e0..b890e98e5 100644 --- a/src/components/embed-types/nostr.tsx +++ b/src/components/embed-types/nostr.tsx @@ -40,7 +40,7 @@ export function embedNostrLinks(content: EmbedableContent) { export function embedNostrMentions(content: EmbedableContent, event: NostrEvent | DraftNostrEvent) { return embedJSX(content, { name: "nostr-mention", - regexp: /#\[(\d+)\]/, + regexp: /#\[(\d+)\]/g, render: (match) => { const index = parseInt(match[1]); const tag = event?.tags[index]; diff --git a/src/helpers/embeds.ts b/src/helpers/embeds.ts index d27a510a8..bb1178bc2 100644 --- a/src/helpers/embeds.ts +++ b/src/helpers/embeds.ts @@ -20,24 +20,43 @@ export function embedJSX(content: EmbedableContent, embed: EmbedType): Embedable return content .map((subContent, i) => { if (typeof subContent === "string") { - const match = subContent.match(embed.regexp); + const matches = subContent.matchAll(embed.regexp); - if (match && match.index !== undefined) { - const { start, end } = (embed.getLocation || defaultGetLocation)(match); - const before = subContent.slice(0, start); - const after = subContent.slice(end, subContent.length); - let render = embed.render(match); + if (matches) { + const newContent: EmbedableContent = []; + let cursor = 0; + let str = subContent; + for (const match of matches) { + if (match.index !== undefined) { + const { start, end } = (embed.getLocation || defaultGetLocation)(match); - if (render === null) return subContent; + if (start < cursor) continue; - if (typeof render !== "string" && !render.props.key) { - render = cloneElement(render, { key: match[0] }); + const before = str.slice(0, start - cursor); + const after = str.slice(end - cursor, str.length); + let render = embed.render(match); + if (render === null) continue; + + if (typeof render !== "string" && !render.props.key) { + render = cloneElement(render, { key: embed.name + match[0] }); + } + + newContent.push(before, render); + + cursor = end; + str = after; + } } - const newContent: EmbedableContent = []; - if (before.length > 0) newContent.push(...embedJSX([before], embed)); - newContent.push(render); - if (after.length > 0) newContent.push(...embedJSX([after], embed)); + // if all matches failed just return the existing content + if (newContent.length === 0) { + return subContent; + } + + // add the remaining string to the content + if (str.length > 0) { + newContent.push(str); + } return newContent; } @@ -53,7 +72,7 @@ export type LinkEmbedHandler = (link: URL) => JSX.Element | string | null; export function embedUrls(content: EmbedableContent, handlers: LinkEmbedHandler[]) { return embedJSX(content, { name: "embedUrls", - regexp: /https?:\/\/([a-zA-Z0-9\.\-]+\.[a-zA-Z]+)([\p{Letter}\p{Number}&\.-\/\?=#\-@%\+_,:]*)/iu, + regexp: /https?:\/\/([a-zA-Z0-9\.\-]+\.[a-zA-Z]+)([\p{Letter}\p{Number}&\.-\/\?=#\-@%\+_,:]*)/giu, render: (match) => { try { const url = new URL(match[0]); diff --git a/src/helpers/regexp.ts b/src/helpers/regexp.ts index d6df77802..a0c70eeb3 100644 --- a/src/helpers/regexp.ts +++ b/src/helpers/regexp.ts @@ -2,5 +2,5 @@ export const mentionNpubOrNote = /(?:\s|^)(@|nostr:)?((npub1|note1)[qpzry9x8gf2t export const matchImageUrls = /https?:\/\/([\dA-z\.-]+\.[A-z\.]{2,12})((?:\/[\+~%\/\.\w\-_]*)?\.(?:svg|gif|png|jpg|jpeg|webp|avif))(\??(?:[\?#\-\+=&;%@\.\w_]*)#?(?:[\-\.\!\/\\\w]*))?/i; -export const matchNostrLink = /(nostr:|@)?((npub|note|nprofile|nevent)1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{58,})/i; -export const matchHashtag = /(^|[^\p{L}])#([\p{L}\p{N}]+)/iu; +export const matchNostrLink = /(nostr:|@)?((npub|note|nprofile|nevent)1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{58,})/gi; +export const matchHashtag = /(^|[^\p{L}])#([\p{L}\p{N}]+)/giu; diff --git a/src/views/note/components/thread-post.tsx b/src/views/note/components/thread-post.tsx index ef1d30a76..997339283 100644 --- a/src/views/note/components/thread-post.tsx +++ b/src/views/note/components/thread-post.tsx @@ -27,7 +27,7 @@ export const ThreadPost = ({ post, initShowReplies, focusId }: ThreadItemProps) {showReplyForm.isOpen && ( )} - + {!showReplyForm.isOpen && (