render comment attachments in danmaku

This commit is contained in:
2026-05-17 10:37:42 +02:00
parent 838883bd6d
commit 2c67ccb803
2 changed files with 51 additions and 4 deletions

View File

@@ -489,6 +489,25 @@
margin: 0 2px; margin: 0 2px;
} }
/* Inline video (converted GIF) in pill */
.danmaku-pill .dpill-video {
max-height: 80px;
max-width: 120px;
width: auto;
height: auto;
vertical-align: middle;
object-fit: contain;
display: inline-block;
border-radius: 4px;
margin: 0 2px;
}
/* Audio indicator in pill */
.danmaku-pill .dpill-audio {
vertical-align: middle;
font-size: 0.9em;
}
@keyframes danmaku-fly { @keyframes danmaku-fly {
from { transform: translateX(calc(100vw + 100%)); } from { transform: translateX(calc(100vw + 100%)); }
to { transform: translateX(calc(-100% - 200px)); } to { transform: translateX(calc(-100% - 200px)); }

View File

@@ -609,10 +609,33 @@ class Danmaku {
parent.appendChild(document.createTextNode(match[0])); parent.appendChild(document.createTextNode(match[0]));
} }
} else if (match[4]) { } else if (match[4]) {
const img = document.createElement('img'); const mediaUrl = match[4];
img.src = match[4]; const isConvertedGif = mediaUrl.endsWith('#gif');
img.className = 'dpill-img'; const cleanUrl = mediaUrl.replace(/#gif$/, '');
parent.appendChild(img); const videoExts = /\.(?:mp4|webm|ogv|mov)$/i;
const audioExts = /\.(?:mp3|ogg|wav|flac|aac|opus|m4a)$/i;
if (videoExts.test(cleanUrl)) {
const vid = document.createElement('video');
vid.src = cleanUrl;
vid.className = 'dpill-video';
vid.muted = true;
vid.loop = true;
vid.autoplay = true;
vid.playsInline = true;
vid.play().catch(() => { vid.addEventListener('canplay', () => vid.play().catch(() => {}), { once: true }); });
parent.appendChild(vid);
} else if (audioExts.test(cleanUrl)) {
const span = document.createElement('span');
span.className = 'dpill-audio';
span.textContent = '🔊 ';
parent.appendChild(span);
} else {
const img = document.createElement('img');
img.src = cleanUrl;
img.className = 'dpill-img';
parent.appendChild(img);
}
} }
lastIndex = match.index + match[0].length; lastIndex = match.index + match[0].length;
@@ -648,6 +671,11 @@ class Danmaku {
}; };
const imgTokenUrls = []; const imgTokenUrls = [];
protected_ = protected_ protected_ = protected_
// Tokenize relative /c/ media URLs (comment attachments)
.replace(/\/c\/[a-f0-9]+\.(?:png|jpg|jpeg|gif|webp|svg|avif|mp4|webm|ogv|mov|mp3|ogg|wav|flac|aac|opus|m4a)(?:#gif)?/gi, (url) => {
imgTokenUrls.push(url);
return `\x04${imgTokenUrls.length - 1}\x05`;
})
// Stop at protocol boundaries so concatenated URLs aren't merged into one broken src. // Stop at protocol boundaries so concatenated URLs aren't merged into one broken src.
.replace(/https?:\/\/(?:(?!https?:\/\/)\S)+\.(?:png|jpg|jpeg|gif|webp|svg|avif)(\?(?:(?!https?:\/\/)\S)*)?/gi, (url) => { .replace(/https?:\/\/(?:(?!https?:\/\/)\S)+\.(?:png|jpg|jpeg|gif|webp|svg|avif)(\?(?:(?!https?:\/\/)\S)*)?/gi, (url) => {
if (!isAllowedImg(url)) return url; // disallowed image URLs stay as plain text if (!isAllowedImg(url)) return url; // disallowed image URLs stay as plain text