diff --git a/src/lib/utils/markdown/basicMarkdownParser.ts b/src/lib/utils/markdown/basicMarkdownParser.ts index c06e840..68548ca 100644 --- a/src/lib/utils/markdown/basicMarkdownParser.ts +++ b/src/lib/utils/markdown/basicMarkdownParser.ts @@ -26,8 +26,41 @@ const VIDEO_URL_REGEX = /https?:\/\/[^\s<]+\.(?:mp4|webm|mov|avi)(?:[^\s<]*)?/i; const AUDIO_URL_REGEX = /https?:\/\/[^\s<]+\.(?:mp3|wav|ogg|m4a)(?:[^\s<]*)?/i; const YOUTUBE_URL_REGEX = /https?:\/\/(?:www\.)?(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/|youtube-nocookie\.com\/embed\/)([a-zA-Z0-9_-]{11})(?:[^\s<]*)?/i; -// Emoji shortcut pattern -const EMOJI_SHORTCUT_REGEX = /:([a-zA-Z0-9_+-]+):/g; +// Utility to strip tracking parameters from URLs +function stripTrackingParams(url: string): string { + // List of tracking params to remove + const trackingParams = [/^utm_/i, /^fbclid$/i, /^gclid$/i, /^tracking$/i, /^ref$/i]; + try { + // Absolute URL + if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url)) { + const parsed = new URL(url); + trackingParams.forEach(pattern => { + for (const key of Array.from(parsed.searchParams.keys())) { + if (pattern.test(key)) { + parsed.searchParams.delete(key); + } + } + }); + parsed.search = parsed.searchParams.toString(); + return parsed.origin + parsed.pathname + (parsed.search ? '?' + parsed.search : '') + (parsed.hash || ''); + } else { + // Relative URL: parse query string manually + const [path, queryAndHash = ''] = url.split('?'); + const [query = '', hash = ''] = queryAndHash.split('#'); + if (!query) return url; + const params = query.split('&').filter(Boolean); + const filtered = params.filter(param => { + const [key] = param.split('='); + return !trackingParams.some(pattern => pattern.test(key)); + }); + const queryString = filtered.length ? '?' + filtered.join('&') : ''; + const hashString = hash ? '#' + hash : ''; + return path + queryString + hashString; + } + } catch { + return url; + } +} function processBasicFormatting(content: string): string { if (!content) return ''; @@ -37,27 +70,30 @@ function processBasicFormatting(content: string): string { try { // Process Markdown images first processedText = processedText.replace(MARKDOWN_IMAGE, (match, alt, url) => { + url = stripTrackingParams(url); if (YOUTUBE_URL_REGEX.test(url)) { const videoId = extractYouTubeVideoId(url); if (videoId) { return ``; } } - if (VIDEO_URL_REGEX.test(url)) { return ``; } - if (AUDIO_URL_REGEX.test(url)) { return ``; } - - return `${alt}`; + // Only render if the url ends with a direct image extension + if (/\.(jpg|jpeg|gif|png|webp|svg)$/i.test(url.split('?')[0])) { + return `${alt}`; + } + // Otherwise, render as a clickable link + return `${alt || url}`; }); // Process Markdown links processedText = processedText.replace(MARKDOWN_LINK, (match, text, url) => - `${text}` + `${text}` ); // Process WebSocket URLs @@ -67,28 +103,27 @@ function processBasicFormatting(content: string): string { return `${match}`; }); - // Process direct media URLs + // Process direct media URLs and auto-link all URLs processedText = processedText.replace(DIRECT_LINK, match => { - if (YOUTUBE_URL_REGEX.test(match)) { - const videoId = extractYouTubeVideoId(match); + const clean = stripTrackingParams(match); + if (YOUTUBE_URL_REGEX.test(clean)) { + const videoId = extractYouTubeVideoId(clean); if (videoId) { return ``; } } - - if (VIDEO_URL_REGEX.test(match)) { - return ``; + if (VIDEO_URL_REGEX.test(clean)) { + return ``; } - - if (AUDIO_URL_REGEX.test(match)) { - return ``; + if (AUDIO_URL_REGEX.test(clean)) { + return ``; } - - if (IMAGE_URL_REGEX.test(match)) { - return `Embedded media`; + // Only render if the url ends with a direct image extension + if (/\.(jpg|jpeg|gif|png|webp|svg)$/i.test(clean.split('?')[0])) { + return `Embedded media`; } - - return `${match}`; + // Otherwise, render as a clickable link + return `${clean}`; }); // Process text formatting diff --git a/src/lib/utils/markdown/markdownTestfile.md b/src/lib/utils/markdown/markdownTestfile.md index 7ceb3b8..08f1d71 100644 --- a/src/lib/utils/markdown/markdownTestfile.md +++ b/src/lib/utils/markdown/markdownTestfile.md @@ -7,7 +7,7 @@ It is _only_ a test, for __sure__. I just wanted to see if the markdown renders This file is full of ~errors~ opportunities to ~~mess up the formatting~~ check your markdown parser. -npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z wrote this. That's the same person as nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z and nprofile1qydhwumn8ghj7argv4nx7un9wd6zumn0wd68yvfwvdhk6tcpr3mhxue69uhkx6rjd9ehgurfd3kzumn0wd68yvfwvdhk6tcqyr7jprhgeregx7q2j4fgjmjgy0xfm34l63pqvwyf2acsd9q0mynuzp4qva3. That is a different person from npub1s3ht77dq4zqnya8vjun5jp3p44pr794ru36d0ltxu65chljw8xjqd975wz. +npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z wrote this. That's the same person as this one with a nostr prefix nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z and nprofile1qydhwumn8ghj7argv4nx7un9wd6zumn0wd68yvfwvdhk6tcpr3mhxue69uhkx6rjd9ehgurfd3kzumn0wd68yvfwvdhk6tcqyr7jprhgeregx7q2j4fgjmjgy0xfm34l63pqvwyf2acsd9q0mynuzp4qva3. That is a different person from npub1s3ht77dq4zqnya8vjun5jp3p44pr794ru36d0ltxu65chljw8xjqd975wz. > This is important information @@ -62,7 +62,7 @@ Try embedded a nostr note with nevent: nostr:nevent1qvzqqqqqqypzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqydhwumn8ghj7argv4nx7un9wd6zumn0wd68yvfwvdhk6tcpr3mhxue69uhkx6rjd9ehgurfd3kzumn0wd68yvfwvdhk6tcqyrzdyycehfwyekef75z5wnnygqeps6a4qvc8dunvumzr08g06svgcptkske -Here with note: +Here a note with no prefix note1cnfpxxd6t3xdk204q4r5uezqxgvxhdgrxpm0ym8xcsme6r75rzxqcj9lmz @@ -74,12 +74,22 @@ Here's a nonsense one: nevent123 +And a nonsense one with a prefix: + +nostr:naddrwhatever + And some Nostr addresses that should be ignored: https://lumina.rocks/note/note1sd0hkhxr49jsetkcrjkvf2uls5m8frkue6f5huj8uv4964p2d8fs8dn68z https://primal.net/e/nevent1qqsqum7j25p9z8vcyn93dsd7edx34w07eqav50qnde3vrfs466q558gdd02yr +URL with a tracking parameter, no Markdown: +https://example.com?utm_source=newsletter1&utm_medium=email&utm_campaign=sale + +Image without Markdown: +https://upload.wikimedia.org/wikipedia/commons/f/f1/Heart_coraz%C3%B3n.svg + This is an implementation of [Nostr-flavored Markdown](https://github.com/nostrability/nostrability/issues/146) for #gitstuff issue notes. You can even include `code inline`, like `
` or