Thumbnails are missing for our external embeds when using topic-thumbnails

We are launching a forum for our product, whose external embeds already function with OneBox after allowing Iframes from us.

A direct link to an embed, is neatly transformed into the result of our oembed endpoint.
Now, our concern is first and foremost that we would like thumbnails to appear when the Topic List Thumbnails is active. I am not sure why the thumbnail is not picked up.

Would we need to add an integration to lib/onebox/engine?

To add, the default behavior mostly works today, short of not supporting our dynamic embeds. We’d like to load JS instead of an Iframe. Admittedly there is a different solution to this, namely to optionally resize the iframe with some JS, but we are not planning to implement that generically for oEmbeds in the short run.

I am aware of Using onebox images for topic thumbnails - #20 by david, which I have not found to be of help in my case.

1 Like

After some discussion with the Discourse team at email, I’m updating here.

The main discovery is that thumbnails are not loaded if a link which becomes a onebox is rendered as an iframe.

This means that the link:

https://app.everviz.com/embed/N0dDTJaOQ

Will and will not render a thumbnail depending on whether iframes are allowed from the domain (allowed iframes). The solution to this is to somehow inject an iframe to the post. I made a plugin which does so, and makes sure display: none is set on the image, to prevent it from appearing in the post.

I imagine it is possible to generalize this to either:

  1. For all generic oneboxes, silently load and hide an image next to it if get_oembed.thumbnail_url exists.
  2. Arguably a better solution, add general support for extracting get_oembed.thumbnail_url and associating it with the post, without it being part of the cooked area itself.

Relavant places:

  1. https://oembed.com/
  2. discourse/lib/onebox/engine at main · discourse/discourse · GitHub
  3. discourse/standard_embed.rb at main · discourse/discourse · GitHub

I am not super happy about this modal behavior, that is, is allowed iframes enabled or not? It would be nice if Discourse extracts and makes use of all properties from a given oembed endpoint when it first sends a GET to it.

Update

I had the idea of using the client side plugin API to load an image into the post.

api.decorateCooked($elem => {
    $elem[0].querySelectorAll('.my-iframe')
    .forEach(function (iframe) {
      
      //work
      
      iframe.insertAdjacentElement('beforebegin', thumbnail);
    });
  
  }, {id: 'unique_string'});

But Discourse won’t pick anything up.

It seems that one of two things are happening:

  1. We are late and Discourse has already done the thumbnail retrieval & generation.
  2. We are missing some attributes on our image that causes the thumbnail generation mechanism to miss it.

Hopefully it’s the latter. One thing to note here is that the image is never uploaded to our Discourse instance, but simply referenced from our own server.

Have you tried this? Writing a specific onebox for your case might allow you to supply a onebox image to the cooked post? Then thumbnails would then work automatically.

I note that Youtube oneboxes I believe are static in-site content, until you click the play button and then it shows an iframe. The content presented prior to the click includes an image which is picked up then thumbnailed. Obviously Discourse can’t read from an iframe, so this technique is a good approach.

I note your example includes an og:image in the header tags which is perfect.


My suggestion is move away from javascript here and get cooking in Rails.

The only downside to this will be your image will be static until you rebuild the Post, assuming the target image is also updated. Thus if you are hoping to show a dynamically changing thumbnail you might have to get even more creative.

3 Likes

Hi Robert, thanks for your reply!

Writing a plugin was the first thing I did, and that worked fantastically—no matter if I put it in lib/onebox/engine, or as a separate plugin. The caveat is that we are on a hosted plan, where plugins are understandably not allowed on multi-tenant plans, leaving cooking in Rails out of the question.

That leaves us with one of three opportunities:

  1. Running our own instance
  2. Hacking something on the client-side, if it might work
  3. Submit a PR to mainline Discourse

To frame a question here. I’m not sure if such a contribution would be accepted, especially if it loads an image just to hide it. How might I find out?

2 Likes

I might have made some sort of progress on this. Looking at:

api.composerBeforeSave

Whose callback is handled in composer.js. From there, we can see that the createPost and editPost methods calls getCookedHtml, which more or less simply returns the innerHTML of:

const editorPreviewNode = document.querySelector(
  "#reply-control .d-editor-preview"
);

Which means that if we were to modify this selector, we might be able to force in some HTML that is equivalent to a regular image upload. However, it seems modifying editorPreviewNode.innerHTML does not result in any change at all.

Why is this, or what can I modify in composerBeforeSave to do something similar?

This mostly makes sense to me, I think we’d be open to accepting a PR to core for a onebox engine for everviz.com (or similar visualization services). However, I would avoid inserting and hiding an image in the cooked post, there is a lighter option. Discourse’s post processor will look for an upload the following elements:

So adding the thumbnail as an anchor below the iframe might work? You could keep it visible, or hide it using a class like “hidden”.

5 Likes

Hi, Penar, thanks for the reply.

Using

  <div class="everviz-box">
   #{get_oembed.html}
   <a class="hidden" href="https://app.everviz.com/thumbnails/#{match[:uuid]}.png"></a>
  </div>

as my to_html method, yields the following output:

  <div class="...">
    <iframe
      ...
    ></iframe>
    <a class="hidden" href="https://app.everviz.com/thumbnails/[...].png" rel="nofollow ugc noopener"></a>
  </div>

Linking an image directly, yields:

<a href="https://app.everviz.com/thumbnails/[...].png" target="_blank" rel="noopener" class="onebox">
   <img src="//localhost:3000/uploads/default/original/1X/[...].png"
        style="aspect-ratio: 690 / 459;" loading="lazy">
</a>

Making it seem like the image_onebox.rb engine is running on the input only if an image is linked outside the context of a different onebox engine.

The consequence of this is that the suggested technique does not work per now and it is necessary to link and hide an image, or modify Discourse to account for this.

Would it be an acceptable PR in this case to insert and hide an image? Or is it necessary to do something more involved?

I’m not sure I am following, this shouldn’t go through the image onebox engine.

Does this:

<a class="hidden" href="https://app.everviz.com/thumbnails/[...].png" rel="nofollow ugc noopener"></a>

not work? I.e. when a first post in a topic has this in its cooked column, does the image get recognized as a thumbnail after processing?

2 Likes

Apologies for the confusion, the output looked similar to the image onebox engine.

The suggested link (properly fitted with a UUID) does not work. Nor does this image I pulled from Google (and am here writing as an anchor tag. If the pasting some HTML is equivalent to having a onebox output it, this would explain our results.

https://static-cse.canva.com/blob/1031184/1600w-wK95f3XNRaM.jpg

1 Like

Ah, my bad, you’re right, that approach doesn’t work. When we generate thumbnails, we use only downloaded images but images linked in an anchor tag aren’t downloaded.

An alternative here might be to add to core something similar to what @merefield recommended, but wrapped as a generic oneboxer for lazy-loaded iframes. Maybe add a new site setting lazy_loaded_iframes, and the onebox can initially output the image from the OG tag (which should get picked up by the thumbnails), and when clicked, a little bit of JS replaces the image with the correct iframe (similar to the Youtube iframe replacement).

One tricky detail here is that the image used and the iframe should have the same height, otherwise this can introduce unwanted jumps when scrolling/navigating posts.

3 Likes