v2: global player, infinite scroll, image picker, threaded comments

This commit is contained in:
khannurien
2026-03-21 13:55:22 +00:00
parent be426eb150
commit 7c098e7c4c
48 changed files with 4346 additions and 711 deletions

View File

@@ -2,18 +2,35 @@ import type { RichContent } from "../../model/interfaces.ts";
import type { RichContentProvider } from "../rich-content-service.ts";
import { fetchWithTimeout } from "../rich-content-service.ts";
const YOUTUBE_REGEX =
/(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
function extractVideoId(url: string): string | null {
try {
const u = new URL(url);
if (u.hostname === "youtu.be") {
return u.pathname.slice(1).split("/")[0] || null;
}
if (u.hostname === "youtube.com" || u.hostname === "www.youtube.com") {
if (u.pathname === "/watch" || u.pathname.startsWith("/watch?")) {
return u.searchParams.get("v");
}
if (u.pathname.startsWith("/embed/") || u.pathname.startsWith("/shorts/")) {
return u.pathname.split("/")[2] || null;
}
}
} catch {
// invalid URL
}
return null;
}
export const youtubeProvider: RichContentProvider = {
name: "youtube",
matches(url: string): boolean {
return YOUTUBE_REGEX.test(url);
return extractVideoId(url) !== null;
},
async fetch(url: string): Promise<RichContent> {
const videoId = url.match(YOUTUBE_REGEX)![1];
const videoId = extractVideoId(url)!;
const thumbnailUrl = `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`;
let title: string | undefined;
@@ -36,6 +53,7 @@ export const youtubeProvider: RichContentProvider = {
videoId,
title,
thumbnailUrl,
embedUrl: `https://www.youtube.com/embed/${videoId}?rel=0`,
};
},
};