/** * Embed providers (D-05). Stored posts hold only the placeholder *
; the layer turns it * into the real iframe. Adding a provider is one entry here and one in the * layer's table. */ export interface EmbedProvider { /** The name shown on the editor card. */ name: string /** The id the placeholder may hold. */ idPattern: RegExp /** Returns the id from a provider URL, or null. */ idFromUrl: (url: URL) => string | null } const YOUTUBE_HOSTS = new Set(['youtube.com', 'www.youtube.com', 'm.youtube.com', 'music.youtube.com', 'youtube-nocookie.com', 'www.youtube-nocookie.com']) export const EMBED_PROVIDERS = { youtube: { name: 'YouTube', idPattern: /^[A-Za-z0-9_-]{11}$/, idFromUrl(url: URL): string | null { const host = url.hostname.toLowerCase() if (host === 'youtu.be') { return url.pathname.split('/')[1] ?? null } if (!YOUTUBE_HOSTS.has(host)) return null if (url.pathname === '/watch') return url.searchParams.get('v') const match = /^\/(?:shorts|embed)\/([^/]+)\/?$/.exec(url.pathname) return match ? match[1]! : null }, }, } as const satisfies Record export type EmbedProviderName = keyof typeof EMBED_PROVIDERS export function isEmbedProvider(name: string): name is EmbedProviderName { return Object.prototype.hasOwnProperty.call(EMBED_PROVIDERS, name) } /** * parseEmbedUrl returns the provider and id for a supported video URL * (youtube.com/watch?v=, youtu.be/, youtube.com/shorts/ and * youtube.com/embed/ for YouTube), or null for anything else. */ export function parseEmbedUrl(input: string): { provider: EmbedProviderName; id: string } | null { let url: URL try { url = new URL(input.trim()) } catch { return null } if (url.protocol !== 'https:' && url.protocol !== 'http:') return null for (const provider of Object.keys(EMBED_PROVIDERS) as EmbedProviderName[]) { const id = EMBED_PROVIDERS[provider].idFromUrl(url) if (id && EMBED_PROVIDERS[provider].idPattern.test(id)) return { provider, id } } return null }