Failed Experiment: Svg Tweets: Second attempt: Calculate the size in JavaScript

(This is part 2 in a set of failed experiments trying to generate SVG tweets in Hugo. I eventually abandoned this goal, and generate HTML archives of tweets instead.)

You should be able to calculate the size of things via javascript right?

I tried generating <object> and <iframe> embeds and calculating the resulting size in JavaScript, but it doesn’t work.

Setup

Add this to config.yaml:

mediaTypes:
  ...
  text/html+tweet:
    suffixes: ["tweet.html"]

outputFormats:
  ...
  HtmlTweet:
    mediatype: text/html+tweet
    suffix: tweet.html
    isPlainText: true
    notAlternative: false

Add this to content/archive/tweets/_index.md:

type: tweets
cascade:
    type: tweets
    outputs:
        - HTML
        - SVG
        - HtmlTweet

Here’s layouts/tweets/single.tweet.html:

{{- $tweet := index .Site.Data.tweets .Params.tweetid -}}
<html style="height: 100%; overflow: visible;">
<head>
  <base target="_parent" />
  <title>Tweet from @{{ $tweet.username}} on {{ .Date.Format .Site.Params.dateform }}</title>
  <style>
    {{/* TODO: split out CSS so that I don't have to include all website CSS */}}
    {{- $inlineTweetStandalone := resources.Get "inlineTweetStandalone.scss" | resources.ToCSS | minify -}}
    {{- $inlineTweetStandalone.Content | safeCSS -}}
  </style>
  <script>
    /* Display explanation text for an inline tweet
    */
    function toggleInlineTweetExplanation(button) {
      const expl = button.parentElement.getElementsByClassName('inline-tweet-about-explanation')[0];
      console.log(expl.style.display);
      if (expl.style.display === 'none'){
        expl.style.display = 'block';
      } else {
        expl.style.display = 'none';
      }
    }

    /* Code to expand/collapse all tweets on a page
    */
    function setInlineTweetCollapsibleDisplay(state) {
      const tweets = document.getElementsByClassName("inline-tweet-collapsible");
      for (const tweet of tweets) {
        switch (state) {
          case "open": tweet.open = true; break;
          case "closed": tweet.open = false; break;
          default: console.log(`Unknown state: ${state}`);
        }
      }
    }

    /* Notify the parent of this iframe (if any) that the size has changed
     */
    // function notifyParentFrameSizeChange() {
    //   const msg = {
    //     mrlevent: "resize",
    //     w: window.innerWidth,
    //     h: window.innerHeight,
    //   }
    //   console.log(window.offsetHeight)
    //   window.parent.postMessage(msg, "*");
    // }
    // window.addEventListener('resize', notifyParentFrameSizeChange);


  </script>
</head>
<body>
<section id="content" class="single-tweet">
  {{ partial "inlineTweet.html" (dict "ctx" . "tweetId" .Params.tweetid) }}
</section>


  <script>
    const resizeObserver = new ResizeObserver(e => {
      const w = window.innerWidth;
      const h = window.innerHeight;
      const msg = {mrlevent: "resize", w, h}
      console.log(`In resizeObserver, ${w}x${h}`)
      console.log(window.offsetHeight)
      window.parent.postMessage(msg, "*");
    });
    resizeObserver.observe(document.documentElement);
  </script>


</body>
</html>

Here’s layouts/tweets/single.html that will inline the above:

{{- partial "header.html" . -}}
{{- $formattedDate := .Date.Format .Site.Params.dateform -}}
{{- $tweet := index .Site.Data.tweets .Params.tweetid -}}

<section id="content" class="single-tweet">
  <p><a href='{{ ref . "/archive/tweets" }}'>About archived tweets</a></p>
  <h1 id="single-tweet-page-h1">Tweet from @{{ $tweet.username}} on {{ .Date.Format .Site.Params.dateform }}</h1>
  <!--object id="embedded-tweet-html-object" class="embedded-tweet-html-object" style="width: 100%;" data="index.tweet.html" type="text/html"></object-->
  <iframe id="embedded-tweet-html-iframe" class="embedded-tweet-html-iframe" style="width: 100%;" scrolling="no" src="index.tweet.html"></iframe>
</section>

<script>
  const tweetHtmlObject = document.getElementById("embedded-tweet-html-object");
</script>


{{ partial "taxonomyList.html" . }}
{{ partial "footer.html" . }}

I tried a bunch of different things. Here’s a version of single.html that tries various things at the same time.

{{- partial "header.html" . -}}
{{- $formattedDate := .Date.Format .Site.Params.dateform -}}
{{- $tweet := index .Site.Data.tweets .Params.tweetid -}}

<section id="content" class="single-tweet">
  <p><a href='{{ ref . "/archive/tweets" }}'>About archived tweets</a></p>

  <h1>Experiments</h1>

  <h2>An HTML page in an iframe</h2>

  <iframe id="embedded-tweet-iframe" scrolling="no" class="" src="index.tweet.html"></iframe>

  <h2>An HTML page as an object</h2>

  <object id="embedded-tweet-html-object" class="" data="index.tweet.html" type="text/html"></object>

  <h2>The SVG as an object</h2>

  <object id="embedded-tweet-svg-object" data="index.svg" type="image/svg+xml"></object>

  <h2>The SVG as an img</h2>

  <img id="embedded-tweet-svg-img" style="" src="index.svg" />

  <h1 id="single-tweet-page-h1">Tweet from @{{ $tweet.username}} on {{ .Date.Format .Site.Params.dateform }}</h1>

  {{ partial "inlineTweet.html" (dict "ctx" . "tweetId" .Params.tweetid) }}

</section>


<script>
  const tweetHtmlIframe = document.getElementById("embedded-tweet-iframe");
  const tweetHtmlObject = document.getElementById("embedded-tweet-html-object");
  const tweetSvgObject = document.getElementById("embedded-tweet-svg-object");
  const tweetSvgImage = document.getElementById("embedded-tweet-svg-img");
</script>


{{ partial "taxonomyList.html" . }}
{{ partial "footer.html" . }}

Here’s assets/inlineTweetStandalone.scss:

blockquote.inline-tweet {

  --body-fg-color-deemphasize-nontext: #ddd;
  --body-fg-color-deemphasize-text: gray;
  --inline-tweet-dt-fg-color: rgb(101, 119, 134);
  --inline-tweet-username-fg-color: rgb(101, 119, 134);
  --inline-tweet-about-bg-color: rgb(75, 75, 172);
  --inline-tweet-about-fg-color: white;

  *, *:hover, *:hover *{
    all: revert;
  }

  .inline-tweet-qt {
    border-radius: 2%;
    border-style: solid;
    border-width: .1em;
    border-color: var(--body-fg-color-deemphasize-nontext);

    padding: 0;
    margin: 0 0 1em 0;

    .blockquote {
      padding: 0;
      margin: 0;
    }
  }

  margin: 0;
  background-color: var(--body-bg-color);
  border-radius: 2%;
  border-style: solid;
  border-width: .1em;
  border-color: var(--body-fg-color-deemphasize-nontext);
  padding: 1em;
  font-family: sans-serif;

  line-height: 1.4em;
  max-width: 30rem;

  a.inline-tweet-user {
    text-decoration: none;
    .inline-tweet-pfp {
      float: left;
      width: 4rem;
      height: 4rem;
      border-radius: 50%;
      margin: 0 .5rem 0 0;
      border-style: solid;
      border-width: .1em;
      border-color: var(--body-fg-color-deemphasize-nontext);
    }
    .inline-tweet-display-name {
      margin: 0;
      font-size: 1rem;
      text-decoration: none;
      color: var(--body-fg-color);
    }
    .inline-tweet-username {
      margin: 0;
      font-size: 1rem;
      font-weight: normal;
      color: var(--inline-tweet-username-fg-color);
    }
  }

  .inline-tweet-id {
    font-size: 70%;
    color: var(--body-fg-color-deemphasize-text);
    margin: 0;
    padding: 0;
  }

  .inline-tweet-text {
    clear: both;
    font-size: 1rem;
  }

  ol.media-inline-tweet-list {
    list-style-type: none;
    padding: 0;

    li:before {
      content: "";
      padding: 0;
      margin: 0;
    }
    li {
      display: inline-block;
      padding: 0.25em;
      margin: 0;

      .media-inline-tweet {
        max-width: 100%;
        border-radius: 2%;
        border-radius: 2%;
        border-style: solid;
        border-width: .1em;
        border-color: var(--body-fg-color-deemphasize-nontext);
      }
    }
  }
  ol.media-inline-tweet-list-1 li {
    max-width: 100%;
  }
  ol.media-inline-tweet-list-2 li {
    max-width: 45%;
  }
  ol.media-inline-tweet-list-3 li {
    max-width: 30%;
  }
  ol.media-inline-tweet-list-4 li {
    max-width: 20%;
  }

  .inline-tweet-footer {
    .inline-tweet-original-link {
      .inline-tweet-dt {
        font-size: .9rem;
        font-family: sans-serif;
        margin: 0;
        padding-bottom: 1rem;
        color: var(--inline-tweet-dt-fg-color);
        text-decoration: none;
      }
    }
    .inline-tweet-about-button {
      float: right;
      background-color: var(--inline-tweet-about-bg-color);
      color: var(--inline-tweet-about-fg-color);
      border-radius: 1em;
      width: 2em;
      height: 2em;
      text-align: center;
      padding: 0;
    }
    .inline-tweet-about-explanation {
      color: var(--body-fg-color-deemphasize-text);
      font-size: .8rem;
      line-height: 1.2rem;
      ul.inline-tweet-about-explanation-footer li {
      }
    }
  }

}

Here’s stuff I added to the whole-site JS header layouts/partials/header.js.html (just the JS I added):

  function getDocHeight(doc) {
    doc = doc || document;
    // stackoverflow.com/questions/1145850/
    const body = doc.body;
    const html = doc.documentElement;
    const height = Math.max(
      body.scrollHeight,
      body.offsetHeight,
      html.clientHeight,
      html.scrollHeight,
      html.offsetHeight
    );
    return height;
  }

  function resizeEmbeddedTweets() {
    const tweetHtmlObjs = document.getElementsByClassName("embedded-tweet-html-object");
    for (const tweetHtmlObj of tweetHtmlObjs) {
      window.TESTTWEET = tweetHtmlObj;
      const tweetHeight = getDocHeight(tweetHtmlObj.contentDocument);
      console.log(`tweetHeight: ${tweetHeight}`);
      tweetHtmlObj.height = tweetHeight + 10;
    }
  }

  function resizeEmbeddedTweets2() {
    const tweetHtmlFrames = document.querySelectorAll("iframe.embedded-tweet-html-iframe")

    for (const tweetHtmlFrame of tweetHtmlFrames) {
      const objWindow = tweetHtmlFrame.contentWindow
      const objDoc = objWindow.document
      const objHtml = objDoc.documentElement
      const objBody = objDoc.body

      if(objBody) {
          // objBody.style.overflowX = "scroll" // scrollbar-jitter fix
          // objBody.style.overflowY = "hidden"
      }
      if(objHtml) {
          // objHtml.style.overflowX = "scroll" // scrollbar-jitter fix
          // objHtml.style.overflowY = "hidden"

          // var style = window.getComputedStyle(html)
          // tweetHtmlObj.width = parseInt(style.getPropertyValue("width")) // round value
          // tweetHtmlObj.height = parseInt(style.getPropertyValue("height"))
          // console.log(`Just set <object> height to ${tweetHtmlObj.width}x${tweetHtmlObj.height}`);

          const objHtmlStyle = objWindow.getComputedStyle(objHtml);
          const compW = objHtmlStyle.getPropertyValue("width");
          const compH = objHtmlStyle.getPropertyValue("height");
          const docHeight = getDocHeight(objDoc);
          console.log(`Computed style: ${compW}x${compH}; JS detected height ${docHeight}.`);
          console.log(objHtml.getBoundingClientRect())

          tweetHtmlFrame.width = parseInt(compW);
          tweetHtmlFrame.height = parseInt(docHeight);
      }
    }

  }
    // requestAnimationFrame(resizeEmbeddedTweets2);
    // resizeEmbeddedTweets2();

  window.onloads.push(resizeEmbeddedTweets2);
  // window.addEventListener('resize', resizeEmbeddedTweets2);

  window.addEventListener('message', function (e) {
    if (e.data.mrlevent) {
      console.log(`Receiving mrlevent from iframe...`)
      window.TESTEVENT = e;
      if (e.data.mrlevent === "resize") {
        console.log(`Getting resize message from iframe with dimensions: ${e.data.w} x ${e.data.h}`);
        return;
        resizeEmbeddedTweets2();
      }
    // } else {
    //   console.log(`Receiving non-mrl event?`)
    //   console.log(e)
    }
  })

Result

Unsatisfying :(

  • That resizeObserver thing seems like it ought to be handy, but it doesn’t trigger when the user clicks button that shows the about message for the tweet embed.
  • Measuring the height gets it wrong in every case. Not sure if differently wrong or the same wrong, but wrong.