// Embed expansion for Journal HTML. The Blog Editor (Journal plugin, // assets/src/blogeditor/embed.ts) never stores an iframe: it writes the // placeholder
, and the // plugin's bluemonday policy (classes/format_html.go) keeps exactly that, with // data-embed matching ^youtube$ and data-id matching ^[A-Za-z0-9_-]{11}$. // htmlGate.ts mirrors those two rules, so the placeholder survives the layer's // sanitizer too. // // expandEmbeds is the very last step before v-html, after sanitizeJournalHtml: // its input is always sanitizer output, and its output is never sanitized // again (the sanitizer strips every iframe, including the one built here). The // regex matches the serialization sanitize-html gives the placeholder: // attribute order kept, double quotes, empty figure. Only a known provider // with a valid id becomes an iframe, whose src is a fixed host plus that id; // anything else is returned unchanged and stays an inert empty figure. /** One allowlisted embed provider. */ export type EmbedProvider = { /** The id a placeholder must match to expand. */ id: RegExp /** The iframe src for a validated id. */ src: (id: string) => string /** The iframe's accessible title. */ title: string } /** Providers a placeholder may name. YouTube only, on its privacy-enhanced host. */ export const EMBED_PROVIDERS: Readonly> = Object.freeze({ youtube: { id: /^[A-Za-z0-9_-]{11}$/, src: (id: string) => `https://www.youtube-nocookie.com/embed/${id}`, title: 'YouTube video', }, }) /** * The placeholder exactly as the editor writes it and sanitize-html * serializes it. Group 1 is the provider, group 2 the id; neither may hold a * quote or an angle bracket. */ export const EMBED_PLACEHOLDER = /
]*)" data-id="([^"<>]*)"><\/figure>/g /** * Replaces every placeholder whose provider is in EMBED_PROVIDERS and whose * id passes the provider's pattern with a figure holding the provider's * iframe. Every other match is returned unchanged. Empty input gives "". * Call it on sanitizeJournalHtml output only, and never sanitize its result. */ export function expandEmbeds(html: string | null | undefined): string { if (!html) return '' return html.replace(EMBED_PLACEHOLDER, (match: string, provider: string, id: string) => { if (!Object.hasOwn(EMBED_PROVIDERS, provider)) return match const p = EMBED_PROVIDERS[provider]! if (!p.id.test(id)) return match return ( `
` + `' + '
' ) }) }