Browse Source

fixed url and image rendering

master
Silberengel 10 months ago
parent
commit
14d4b54234
  1. 77
      src/lib/utils/markdown/basicMarkdownParser.ts
  2. 14
      src/lib/utils/markdown/markdownTestfile.md

77
src/lib/utils/markdown/basicMarkdownParser.ts

@ -26,8 +26,41 @@ const VIDEO_URL_REGEX = /https?:\/\/[^\s<]+\.(?:mp4|webm|mov|avi)(?:[^\s<]*)?/i; @@ -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 { @@ -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 `<iframe class="w-full aspect-video rounded-lg shadow-lg my-4" src="https://www.youtube-nocookie.com/embed/${videoId}" title="${alt || 'YouTube video'}" frameborder="0" allow="fullscreen" sandbox="allow-scripts allow-same-origin allow-presentation"></iframe>`;
}
}
if (VIDEO_URL_REGEX.test(url)) {
return `<video controls class="max-w-full rounded-lg shadow-lg my-4" preload="none" playsinline><source src="${url}">${alt || 'Video'}</video>`;
}
if (AUDIO_URL_REGEX.test(url)) {
return `<audio controls class="w-full my-4" preload="none"><source src="${url}">${alt || 'Audio'}</audio>`;
}
return `<img src="${url}" alt="${alt}" class="max-w-full h-auto rounded-lg shadow-lg my-4" loading="lazy" decoding="async">`;
// Only render <img> if the url ends with a direct image extension
if (/\.(jpg|jpeg|gif|png|webp|svg)$/i.test(url.split('?')[0])) {
return `<img src="${url}" alt="${alt}" class="max-w-full h-auto rounded-lg shadow-lg my-4" loading="lazy" decoding="async">`;
}
// Otherwise, render as a clickable link
return `<a href="${url}" class="text-primary-600 dark:text-primary-500 hover:underline" target="_blank" rel="noopener noreferrer">${alt || url}</a>`;
});
// Process Markdown links
processedText = processedText.replace(MARKDOWN_LINK, (match, text, url) =>
`<a href="${url}" class="text-primary-600 dark:text-primary-500 hover:underline" target="_blank" rel="noopener noreferrer">${text}</a>`
`<a href="${stripTrackingParams(url)}" class="text-primary-600 dark:text-primary-500 hover:underline" target="_blank" rel="noopener noreferrer">${text}</a>`
);
// Process WebSocket URLs
@ -67,28 +103,27 @@ function processBasicFormatting(content: string): string { @@ -67,28 +103,27 @@ function processBasicFormatting(content: string): string {
return `<a href="https://nostrudel.ninja/#/r/wss%3A%2F%2F${cleanUrl}%2F" target="_blank" rel="noopener noreferrer" class="text-primary-600 dark:text-primary-500 hover:underline">${match}</a>`;
});
// 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 `<iframe class="w-full aspect-video rounded-lg shadow-lg my-4" src="https://www.youtube-nocookie.com/embed/${videoId}" title="YouTube video" frameborder="0" allow="fullscreen" sandbox="allow-scripts allow-same-origin allow-presentation" class="text-primary-600 dark:text-primary-500 hover:underline"></iframe>`;
}
}
if (VIDEO_URL_REGEX.test(match)) {
return `<video controls class="max-w-full rounded-lg shadow-lg my-4" preload="none" playsinline><source src="${match}">Your browser does not support the video tag.</video>`;
if (VIDEO_URL_REGEX.test(clean)) {
return `<video controls class="max-w-full rounded-lg shadow-lg my-4" preload="none" playsinline><source src="${clean}">Your browser does not support the video tag.</video>`;
}
if (AUDIO_URL_REGEX.test(match)) {
return `<audio controls class="w-full my-4" preload="none"><source src="${match}">Your browser does not support the audio tag.</audio>`;
if (AUDIO_URL_REGEX.test(clean)) {
return `<audio controls class="w-full my-4" preload="none"><source src="${clean}">Your browser does not support the audio tag.</audio>`;
}
if (IMAGE_URL_REGEX.test(match)) {
return `<img src="${match}" alt="Embedded media" class="max-w-full h-auto rounded-lg shadow-lg my-4" loading="lazy" decoding="async">`;
// Only render <img> if the url ends with a direct image extension
if (/\.(jpg|jpeg|gif|png|webp|svg)$/i.test(clean.split('?')[0])) {
return `<img src="${clean}" alt="Embedded media" class="max-w-full h-auto rounded-lg shadow-lg my-4" loading="lazy" decoding="async">`;
}
return `<a href="${match}" target="_blank" rel="noopener noreferrer" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300">${match}</a>`;
// Otherwise, render as a clickable link
return `<a href="${clean}" target="_blank" rel="noopener noreferrer" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300">${clean}</a>`;
});
// Process text formatting

14
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 @@ -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: @@ -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: @@ -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 `<div class="leather min-h-full w-full flex flex-col items-center">` or

Loading…
Cancel
Save