tagliatelle

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit e20af5c68b21048cf0a2cbbc082c3b02fbccbd33
parent 42cbc5736aa6f9293d6013d8a98988aca1a58d45
Author: breadcat <breadcat@users.noreply.github.com>
Date:   Fri, 24 Jul 2026 16:37:06 +0100

Support markdown and bare links in description

Diffstat:
Mreadme.md | 1+
Mstatic/timestamps.js | 31++++++++++++++++++++++++++++---
2 files changed, 29 insertions(+), 3 deletions(-)

diff --git a/readme.md b/readme.md @@ -31,6 +31,7 @@ Then access the server via a web browser, the default port is 8080. * Regenerate video thumbnails via web interface * Add files via local upload, remote upload or `yt-dlp` directly * Clickable [rotate90](## "Rotates video/image contents by angle on click"), [l45](## "Jumps to line number in text viewer on click"), [01:23](## "Jumps video playback to specified timestamp on click") and [file/1234](## "Clickable link to that file ID") shortcodes in file descriptions +* Clickable markdown-style links and bare URLs in file descriptions * Artbitrary searchable descriptions on files * Raw file URI copying for external application access * In browser file management (delete, rename) diff --git a/static/timestamps.js b/static/timestamps.js @@ -18,16 +18,41 @@ function makeTimestampsClickable(containerId, videoId, imageId) { const timestampRegex = /\[(\d{1,2}(?::\d{2}){0,2})\]/g; // Regex for rotations: [rotate90], [rotate180], [rotate270], [rotate0] const rotateRegex = /\[rotate(0|90|180|270)\]/g; + // Regex for URLs: http(s):// or www. links + const urlRegex = /\b((?:https?:\/\/|www\.)[^\s<]+)/gi; + // Regex for markdown-style links: [link text](https://example.com) + const markdownLinkRegex = /\[([^\[\]]+)\]\(((?:https?:\/\/|www\.)[^)\s]+)\)/gi; - // Replace timestamps - container.innerHTML = container.innerHTML + // Pull out markdown links first and swap in placeholders, so the raw URL + // inside them doesn't get double-processed by the bare-URL regex below. + const mdLinks = []; + let html = container.innerHTML.replace(markdownLinkRegex, (match, text, url) => { + const href = url.startsWith("http") ? url : `https://${url}`; + const placeholder = `\u0000MDLINK${mdLinks.length}\u0000`; + mdLinks.push(`<a href="${href}" class="external-link" target="_blank" rel="noopener noreferrer">${text}</a>`); + return placeholder; + }); + + // Replace bare URLs, then timestamps, then rotations + html = html + .replace(urlRegex, (match) => { + // Strip common trailing punctuation that isn't part of the URL + const trailingPunct = /[.,;:!?)\]"'>]+$/; + const trimmed = match.match(trailingPunct) ? match.replace(trailingPunct, "") : match; + const suffix = match.slice(trimmed.length); + const href = trimmed.startsWith("http") ? trimmed : `https://${trimmed}`; + return `<a href="${href}" class="external-link" target="_blank" rel="noopener noreferrer">${trimmed}</a>${suffix}`; + }) .replace(timestampRegex, (match, ts) => { const seconds = parseTimestamp(ts); return `<a href="#" class="timestamp" data-time="${seconds}">${match}</a>`; }) .replace(rotateRegex, (match, angle) => { return `<a href="#" class="rotate" data-angle="${angle}">${match}</a>`; - }); + }); + + // Restore the markdown links now that no other regex can touch them + container.innerHTML = html.replace(/\u0000MDLINK(\d+)\u0000/g, (_, i) => mdLinks[Number(i)]); // Handle clicks container.addEventListener("click", e => { if (e.target.classList.contains("timestamp")) {