diff --git a/src/components/embed-types/common.tsx b/src/components/embed-types/common.tsx index 5adec7930..a7e16c764 100644 --- a/src/components/embed-types/common.tsx +++ b/src/components/embed-types/common.tsx @@ -5,7 +5,7 @@ import appSettings from "../../services/settings/app-settings"; import { useTrusted } from "../../providers/trust"; import OpenGraphCard from "../open-graph-card"; import { EmbedableContent, defaultGetLocation } from "../../helpers/embeds"; -import { matchLink } from "../../helpers/regexp"; +import { getMatchLink } from "../../helpers/regexp"; import { useRegisterSlide } from "../lightbox-provider"; import { isImageURL, isVideoURL } from "../../helpers/url"; @@ -89,7 +89,7 @@ export function embedImageGallery(content: EmbedableContent): EmbedableContent { return content .map((subContent, i) => { if (typeof subContent === "string") { - const matches = Array.from(subContent.matchAll(matchLink)); + const matches = Array.from(subContent.matchAll(getMatchLink())); const newContent: EmbedableContent = []; let lastBatchEnd = 0; diff --git a/src/components/embed-types/nostr.tsx b/src/components/embed-types/nostr.tsx index b890e98e5..8415449ce 100644 --- a/src/components/embed-types/nostr.tsx +++ b/src/components/embed-types/nostr.tsx @@ -3,17 +3,16 @@ import { EmbedableContent, embedJSX } from "../../helpers/embeds"; import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event"; import QuoteNote from "../note/quote-note"; import { UserLink } from "../user-link"; -import { EventPointer, ProfilePointer } from "nostr-tools/lib/nip19"; import { Link } from "@chakra-ui/react"; import { Link as RouterLink } from "react-router-dom"; -import { matchHashtag, matchNostrLink } from "../../helpers/regexp"; +import { getMatchHashtag, getMatchNostrLink } from "../../helpers/regexp"; // nostr:nevent1qqsthg2qlxp9l7egtwa92t8lusm7pjknmjwa75ctrrpcjyulr9754fqpz3mhxue69uhhyetvv9ujuerpd46hxtnfduq36amnwvaz7tmwdaehgu3dwp6kytnhv4kxcmmjv3jhytnwv46q2qg5q9 // nostr:nevent1qqsq3wc73lqxd70lg43m5rul57d4mhcanttjat56e30yx5zla48qzlspz9mhxue69uhkummnw3e82efwvdhk6qgdwaehxw309ahx7uewd3hkcq5hsum export function embedNostrLinks(content: EmbedableContent) { return embedJSX(content, { name: "nostr-link", - regexp: matchNostrLink, + regexp: getMatchNostrLink(), render: (match) => { try { const decoded = nip19.decode(match[2]); @@ -64,7 +63,7 @@ export function embedNostrHashtags(content: EmbedableContent, event: NostrEvent return embedJSX(content, { name: "nostr-hashtag", - regexp: matchHashtag, + regexp: getMatchHashtag(), getLocation: (match) => { if (match.index === undefined) throw new Error("match dose not have index"); diff --git a/src/components/timeline-page/index.tsx b/src/components/timeline-page/index.tsx index b52fd84ad..bd79a199c 100644 --- a/src/components/timeline-page/index.tsx +++ b/src/components/timeline-page/index.tsx @@ -9,7 +9,7 @@ import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline- import TimelineActionAndStatus from "./timeline-action-and-status"; import { useSearchParams } from "react-router-dom"; import { NostrEvent } from "../../types/nostr-event"; -import { matchLink } from "../../helpers/regexp"; +import { getMatchLink } from "../../helpers/regexp"; import TimelineHealth from "./timeline-health"; export function useTimelinePageEventFilter() { @@ -18,7 +18,7 @@ export function useTimelinePageEventFilter() { return useCallback( (event: NostrEvent) => { - if (view === "images" && !event.content.match(matchLink)) return false; + if (view === "images" && !event.content.match(getMatchLink())) return false; return true; }, [view], diff --git a/src/components/timeline-page/media-timeline/index.tsx b/src/components/timeline-page/media-timeline/index.tsx index 217de26f2..e87e992cb 100644 --- a/src/components/timeline-page/media-timeline/index.tsx +++ b/src/components/timeline-page/media-timeline/index.tsx @@ -4,7 +4,7 @@ import { useNavigate } from "react-router-dom"; import { TimelineLoader } from "../../../classes/timeline-loader"; import useSubject from "../../../hooks/use-subject"; -import { matchLink } from "../../../helpers/regexp"; +import { getMatchLink } from "../../../helpers/regexp"; import { LightboxProvider, useRegisterSlide } from "../../lightbox-provider"; import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer"; import { getSharableNoteId } from "../../../helpers/nip19"; @@ -64,7 +64,7 @@ export default function MediaTimeline({ timeline }: { timeline: TimelineLoader } var images: { eventId: string; src: string; index: number }[] = []; for (const event of events) { - const urls = event.content.matchAll(matchLink); + const urls = event.content.matchAll(getMatchLink()); let i = 0; for (const match of urls) { diff --git a/src/helpers/embeds.ts b/src/helpers/embeds.ts index fa10b4fca..268045c05 100644 --- a/src/helpers/embeds.ts +++ b/src/helpers/embeds.ts @@ -1,5 +1,5 @@ import { cloneElement } from "react"; -import { matchLink } from "./regexp"; +import { getMatchLink } from "./regexp"; export type EmbedableContent = (string | JSX.Element)[]; export type EmbedType = { @@ -30,6 +30,7 @@ export function embedJSX(content: EmbedableContent, embed: EmbedType): Embedable for (const match of matches) { if (match.index !== undefined) { const { start, end } = (embed.getLocation || defaultGetLocation)(match); + if (start === 0 && match[0].includes("#")) debugger; if (start < cursor) continue; @@ -73,7 +74,7 @@ export type LinkEmbedHandler = (link: URL) => JSX.Element | string | null; export function embedUrls(content: EmbedableContent, handlers: LinkEmbedHandler[]) { return embedJSX(content, { name: "embedUrls", - regexp: matchLink, + regexp: getMatchLink(), render: (match) => { try { const url = new URL(match[0]); diff --git a/src/helpers/nostr/event.ts b/src/helpers/nostr/event.ts index a8e7eeae9..45f510868 100644 --- a/src/helpers/nostr/event.ts +++ b/src/helpers/nostr/event.ts @@ -4,7 +4,7 @@ import { DraftNostrEvent, isETag, isPTag, NostrEvent, RTag, Tag } from "../../ty import { RelayConfig, RelayMode } from "../../classes/relay"; import accountService from "../../services/account"; import { Kind, nip19 } from "nostr-tools"; -import { matchNostrLink } from "../regexp"; +import { getMatchNostrLink } from "../regexp"; import { getSharableNoteId } from "../nip19"; import relayScoreboardService from "../../services/relay-scoreboard"; import { getAddr } from "../../services/replaceable-event-requester"; @@ -14,7 +14,7 @@ export function isReply(event: NostrEvent | DraftNostrEvent) { } export function isRepost(event: NostrEvent | DraftNostrEvent) { - const match = event.content.match(matchNostrLink); + const match = event.content.match(getMatchNostrLink()); return event.kind === 6 || (match && match[0].length === event.content.length); } @@ -39,7 +39,7 @@ export function getContentTagRefs(content: string, tags: Tag[]) { const indexes = new Set(); Array.from(content.matchAll(/#\[(\d+)\]/gi)).forEach((m) => indexes.add(parseInt(m[1]))); - const linkMatches = Array.from(content.matchAll(new RegExp(matchNostrLink, "gi"))); + const linkMatches = Array.from(content.matchAll(getMatchNostrLink())); for (const [_, _prefix, link] of linkMatches) { try { const decoded = nip19.decode(link); diff --git a/src/helpers/nostr/post.ts b/src/helpers/nostr/post.ts index b7c3d9415..92b8bf256 100644 --- a/src/helpers/nostr/post.ts +++ b/src/helpers/nostr/post.ts @@ -1,5 +1,5 @@ import { DraftNostrEvent, NostrEvent, PTag, Tag } from "../../types/nostr-event"; -import { matchHashtag, mentionNpubOrNote } from "../regexp"; +import { getMatchHashtag, getMentionNpubOrNote } from "../regexp"; import { normalizeToHex } from "../nip19"; import { getReferences } from "./event"; import { getEventRelays } from "../../services/event-relays"; @@ -65,7 +65,7 @@ export function replaceAtMentions(draft: DraftNostrEvent) { // replace all occurrences of @npub and @note while (true) { - const match = mentionNpubOrNote.exec(updatedDraft.content); + const match = getMentionNpubOrNote().exec(updatedDraft.content); if (!match || match.index === undefined) break; const hex = normalizeToHex(match[1]); @@ -90,7 +90,7 @@ export function createHashtagTags(draft: DraftNostrEvent) { const updatedDraft: DraftNostrEvent = { ...draft, tags: Array.from(draft.tags) }; // create tags for all occurrences of #hashtag - const matches = updatedDraft.content.matchAll(new RegExp(matchHashtag, "giu")); + const matches = updatedDraft.content.matchAll(getMatchHashtag()); for (const [_, space, hashtag] of matches) { const lower = hashtag.toLocaleLowerCase(); if (!updatedDraft.tags.find((t) => t[0] === "t" && t[1] === lower)) { diff --git a/src/helpers/regexp.ts b/src/helpers/regexp.ts index 2fc84220c..37e853c61 100644 --- a/src/helpers/regexp.ts +++ b/src/helpers/regexp.ts @@ -1,4 +1,7 @@ -export const mentionNpubOrNote = /(?:\s|^)(@|nostr:)?((npub1|note1)[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{58})(?:\s|$)/gi; -export const matchNostrLink = /(nostr:|@)?((npub|note|nprofile|nevent)1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{58,})/gi; -export const matchHashtag = /(^|[^\p{L}])#([\p{L}\p{N}]+)/gu; -export const matchLink = /https?:\/\/([a-zA-Z0-9\.\-]+\.[a-zA-Z]+)([\p{Letter}\p{Number}&\.-\/\?=#\-@%\+_,:]*)/gu; +export const getMentionNpubOrNote = () => + /(?:\s|^)(@|nostr:)?((npub1|note1)[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{58})(?:\s|$)/gi; +export const getMatchNostrLink = () => + /(nostr:|@)?((npub|note|nprofile|nevent)1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{58,})/gi; +export const getMatchHashtag = () => /(^|[^\p{L}])#([\p{L}\p{N}]+)/gu; +export const getMatchLink = () => + /https?:\/\/([a-zA-Z0-9\.\-]+\.[a-zA-Z]+)([\p{Letter}\p{Number}&\.-\/\?=#\-@%\+_,:]*)/gu; diff --git a/src/views/search/index.tsx b/src/views/search/index.tsx index 9cfc578e3..b1c008774 100644 --- a/src/views/search/index.tsx +++ b/src/views/search/index.tsx @@ -17,7 +17,7 @@ import { useSearchParams, Link as RouterLink, useNavigate } from "react-router-d import { ClipboardIcon, QrCodeIcon } from "../../components/icons"; import QrScannerModal from "../../components/qr-scanner-modal"; import { safeDecode } from "../../helpers/nip19"; -import { matchHashtag } from "../../helpers/regexp"; +import { getMatchHashtag } from "../../helpers/regexp"; import RelaySelectionButton from "../../components/relay-selection/relay-selection-button"; import RelaySelectionProvider, { useRelaySelectionRelays } from "../../providers/relay-selection-provider"; import useTimelineLoader from "../../hooks/use-timeline-loader"; @@ -128,7 +128,7 @@ export function SearchPage() { return; } - const hashTagMatch = matchHashtag.exec(cleanText); + const hashTagMatch = getMatchHashtag().exec(cleanText); if (hashTagMatch) { navigate({ pathname: "/t/" + hashTagMatch[2].toLocaleLowerCase() }); return;