File size: 3,355 Bytes
7843436
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import type { Transformer } from "./types.js";
import { buildEmbedSrcdoc, DEFAULT_EMBED_HEIGHT } from "../../shared/embed-doc.js";

export { buildEmbedSrcdoc };

/**
 * Transforms `<div data-component="htmlEmbed" src="..." title="..." desc="..." height="...">`
 * into:
 *   <figure class="html-embed">
 *     <div class="html-embed-container">
 *       <iframe srcdoc="..." ... />
 *     </div>
 *     <figcaption class="html-embed__desc">...</figcaption>
 *   </figure>
 *
 * - The stored `height` is used as the initial `height:` on the iframe so
 *   the page reserves the right space before the iframe even loads
 *   (important with `loading=lazy`), but is NOT used as `min-height`: that
 *   would prevent the iframe from shrinking if the chart, on a narrower
 *   viewport, reports a smaller scrollHeight.
 * - A small safety `min-height` keeps the iframe from collapsing to 0 while
 *   the embed is still loading.
 * - `srcdoc` is built from the shared `buildEmbedSrcdoc` which also embeds
 *   the height reporter and the theme listener.
 * - If `title` or `desc` is present, a <figcaption> is generated so the
 *   publisher CSS (`figure.html-embed`) can number the figure.
 */
const SAFETY_MIN_HEIGHT = 80;

export const htmlEmbedTransformer: Transformer = {
  name: "htmlEmbed",
  apply(document, ctx) {
    for (const div of [...document.querySelectorAll('div[data-component="htmlEmbed"]')]) {
      const src = div.getAttribute("src") || div.getAttribute("data-src") || "";
      const rawHeight = div.getAttribute("height") || div.getAttribute("data-height");
      const initialHeight = Math.max(
        parseInt(rawHeight || "", 10) || DEFAULT_EMBED_HEIGHT,
        SAFETY_MIN_HEIGHT,
      );
      const title = div.getAttribute("title") || div.getAttribute("data-title") || "";
      const desc = div.getAttribute("desc") || div.getAttribute("data-desc") || "";
      const embedHtml = ctx.embeds?.[src] || "";

      const figure = document.createElement("figure");
      figure.className = "html-embed";
      if (src) figure.setAttribute("data-embed-src", src);

      const container = document.createElement("div");
      container.className = "html-embed-container";

      const iframe = document.createElement("iframe");
      iframe.setAttribute("data-embed-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("sandbox", "allow-scripts allow-same-origin");
      if (title) iframe.setAttribute("title", title);

      iframe.setAttribute("srcdoc", embedHtml ? buildEmbedSrcdoc(embedHtml) : "");

      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);
    }
  },
};