`
* into:
*
*
*
*
* ...
*
*
* Reuses the same outer markup as `html-embed.ts` so the existing publisher
* CSS (`figure.html-embed`, `.html-embed-container`, `.html-embed__desc`)
* styles the figure, numbers it, and lays it out responsively.
*
* Differences with htmlEmbed:
* - `src=` is a remote URL, not a Y.Map key, so we never inline `srcdoc`.
* - No sandbox attribute: arbitrary third-party pages often need cookies,
* popups, or storage. Authors who need to lock things down can use
* RawHtml with a hand-rolled iframe.
* - `wide` toggles the `html-embed--wide` modifier (full-bleed layout).
*/
const DEFAULT_IFRAME_HEIGHT = 600;
const SAFETY_MIN_HEIGHT = 80;
export const iframeEmbedTransformer: Transformer = {
name: "iframe",
apply(document) {
for (const div of [...document.querySelectorAll('div[data-component="iframe"]')]) {
const src = (div.getAttribute("src") || div.getAttribute("data-src") || "").trim();
const title = div.getAttribute("title") || div.getAttribute("data-title") || "";
const desc = div.getAttribute("desc") || div.getAttribute("data-desc") || "";
const rawHeight = div.getAttribute("height") || div.getAttribute("data-height");
const wide =
div.getAttribute("wide") === "true" ||
div.getAttribute("data-wide") === "true";
const initialHeight = Math.max(
parseInt(rawHeight || "", 10) || DEFAULT_IFRAME_HEIGHT,
SAFETY_MIN_HEIGHT,
);
// Drop iframes without a src - publishing a broken empty frame is worse
// than silently omitting the placeholder.
if (!src) {
div.remove();
continue;
}
const figure = document.createElement("figure");
figure.className = wide ? "html-embed html-embed--wide" : "html-embed";
figure.setAttribute("data-iframe-src", src);
const container = document.createElement("div");
container.className = "html-embed-container";
const iframe = document.createElement("iframe");
iframe.setAttribute("src", src);
iframe.setAttribute(
"style",
`width:100%;border:none;display:block;height:${initialHeight}px;min-height:${SAFETY_MIN_HEIGHT}px;background:transparent;`,
);
iframe.setAttribute("loading", "lazy");
iframe.setAttribute("referrerpolicy", "no-referrer-when-downgrade");
iframe.setAttribute(
"allow",
"accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; gyroscope; picture-in-picture",
);
if (title) iframe.setAttribute("title", title);
container.appendChild(iframe);
figure.appendChild(container);
if (title || desc) {
const caption = document.createElement("figcaption");
caption.className = "html-embed__desc";
if (title && desc) {
const strong = document.createElement("strong");
strong.textContent = title;
caption.appendChild(strong);
caption.appendChild(document.createTextNode(" " + desc));
} else {
caption.textContent = title || desc;
}
figure.appendChild(caption);
}
div.replaceWith(figure);
}
},
};