Embed widget within text in a topic

Hello!! :slight_smile:

I hope this will be of the interest of someone else: I’m trying to make a “social media feed” topic, in which I want to embed the widgets of different platforms so the users (and the admins!) can have a quick look at all of them in just one page, so they can remain within the forum, preventing the overload that means having to switch between so many social platforms just for quick updates (considering also that they usually share the same content), thus, increasing motivation to use the forum as a hub for the development of the comunity.

A nice widget generator I found is Woxo, which is clean and simple enough for the purpose. The problem now is that I’m unable to figure out how to embed the widget in the topic. I’m trying to see if there is some workaround with iframes or something like that, but I now wanted to ask to see if this is possible at all.

This is the code I get from Woxo for the instagram feed:

<div data-mc-src="f4b43a8f-c188-4f80-8206-36d9f7529f13#instagram"></div>
        
<script 
  src="https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619" 
  async data-usrc>
</script>

What I tried so far:

  • Placing the <script> in the <body> section, <footer> and <header> (I left it in header)
  • Ensuring the URL the script is coming from is whitelisted (https://cdn2.woxo.tech/ in this case)
  • adding defer doesnt help (I’m keeping it just in case)

If I inspect the page, the script does appears at the bottom of the body section (inside), and since the source is whitelisted it should take effect. I checked if it could be my browser, but if I execute the HTML here Tryit Editor v3.7 it works perfectly fine.

I narrowed down the error to a specific function inside of the js script. The following call delivers a null value. It’s the only runtime error:

e=document.querySelector("div[data-mc-src]")
e is null

That div is written in the topic (the <div data-mc-src="f4b43a8f-c188-4f80-8206-36d9f7529f13#instagram"></div> part). It remains as pure html code, so it should be readable. For some reason, the script fails to locate it.

With the defer attribute, and in located in the footer, the scripts trows no error (that fact that it was trowing an error before proves that the URL of the js file is indeed whitelisted), so now I’m blindfolded on why it’s doing nothing.

Any input will be more that appreciated, thank you for your time in advance! :slight_smile:
Lisandro

EDIT: finally I had to desist. Since only iframes are supported, I’m currently looking for a good web service that can provide one. Most of free services are too limited, paid versions are more than double the cost of the forum hosting service. Sorry for the whining, had to cry in loud voice here at not being able to just insert the free html code :’(

2 Likes

Discourse is a single-page application. The issue you’re encountering happens because the script you’re using is not aware of that. When you visit the homepage - or any other page - in Discourse, you get something like this.

<html>
  <head>
    head content including your script
  </head>
  <body>
    <section id="main">
      page content
    </section>
  </body>
</html>

When you navigate to another page, the only thing that gets reloaded is the content inside

<section id="main">

So, the DOM has changed, and your custom script doesn’t fire again. If you try to visit the topic page directly, you’ll see that it loads fine.

.

So, now the question is how to get it to work with Discourse.

The plugin-api has a method that you can use to “decorate” posts.

discourse/plugin-api.js at main · discourse/discourse · GitHub

You can use that to fire third-party scripts when a post is rendered.

Here’s the code you would need. Add this to the common > header tab of your theme.

<script type="text/discourse-plugin" version="0.8">
const WOXO_SCRIPT_SRC = "https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619";
const PREVIEW_ICON = "heart";

const loadScript = require("discourse/lib/load-script").default;
const { iconHTML } = require("discourse-common/lib/icon-library");

const composerPreviewIcon = iconHTML(PREVIEW_ICON, {
  class: "woxo-preview-icon"
});

const previewMarkup = () => {
  const markup = `<div class="woxo-preview">${composerPreviewIcon}</div>`;
  return markup;
};

// create a post decorator
api.decorateCookedElement(
  post => {
    const woxoWidgets = post.querySelectorAll("div[data-mc-src]");

    if (woxoWidgets.length) {
      woxoWidgets.forEach(woxoWidget => {
        if (post.classList.contains("d-editor-preview")) {
          woxoWidget.innerHTML = previewMarkup();
          return;
        }

        loadScript(WOXO_SCRIPT_SRC).then(() => {
          const script = document.head.querySelector(
            `script[src*="cdn2.woxo.tech"]`
          );
          script.dataset.usrc = "";
          window.MC.Loader.init();
        });
      });
    }
  },
  { id: "render-woxo-widgets" }
);
</script>

You would then need to add a couple of domains for CSP. Add these to your

content_security_policy_script_src

site setting

https://*.woxo.tech/
https://us-central1-core-period-259421.cloudfunctions.net/availableComponentTracks

finally, you’d need to add a little bit of CSS for the static composer preview

This goes in the common > CSS tab of your theme.

.woxo-preview {
  height: 400px;
  width: 100%;
  background: var(--primary-low);
  display: flex;
  align-items: center;
  justify-content: center;
  .woxo-preview-icon {
    font-size: var(--font-up-4);
    color: var(--primary-high);
  }
}

Then you can just add

<div data-mc-src="f4b43a8f-c188-4f80-8206-36d9f7529f13#instagram"></div>

to any post, and the widgets will render and be fully functional.

If you look at the JavaScript, you’ll notice that it has two options at the very top.

const WOXO_SCRIPT_SRC = "https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619";
const PREVIEW_ICON = "heart";

Change WOXO_SCRIPT_SRC to the src that woxo gives you. It should be the same for all the embeds you create.

Change PREVIEW_ICON to the name of the icon you want to use in the composer preview. Running this code in the composer is a bit expensive, so the composer has a static preview that looks like this.

The icon you pick will show in the middle.

Here’s a commented copy of the code if you want to follow along with what’s happening

commented code
<script type="text/discourse-plugin" version="0.8">
// options
const WOXO_SCRIPT_SRC = "https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619";
const PREVIEW_ICON = "heart";

// we use the Discourse Load script lib to ensure scripts are loaded
// properly. Don't worry, this is smart enough to not duplicate the script
// if it's already loaded
const loadScript = require("discourse/lib/load-script").default;

// we load the Discourse Icon HTML function to get the svg for the icon
// we want to use in the static composer preview
const { iconHTML } = require("discourse-common/lib/icon-library");

// setup the composer preview icon
const composerPreviewIcon = iconHTML(PREVIEW_ICON, {
  class: "woxo-preview-icon"
});

// create a helper function for the composer preview markup
const previewMarkup = () => {
  const markup = `<div class="woxo-preview">${composerPreviewIcon}</div>`;

  return markup;
};

// create a post decorator
api.decorateCookedElement(
  post => {
    // does this post have woxo widgets?
    const woxoWidgets = post.querySelectorAll("div[data-mc-src]");

    // Yes, so let's do some work.
    if (woxoWidgets.length) {
      // for each woxo widget
      woxoWidgets.forEach(woxoWidget => {
        // if it's a composer widget, swap it out for a static preview and
        // quit early
        if (post.classList.contains("d-editor-preview")) {
          woxoWidget.innerHTML = previewMarkup();
          return;
        }

        // if it's not in the composer, load the woxo script.
        loadScript(WOXO_SCRIPT_SRC).then(() => {
          // The woxo script is very strange. It won't work unless the script
          // tag has an empty data-usrc attribute. So, let's add it
          const script = document.head.querySelector(
            `script[src*="cdn2.woxo.tech"]`
          );
          script.dataset.usrc = "";

          // everything is ready, let's call the init method in the woxo script
          window.MC.Loader.init();
        });
      });
    }
  },
  // add an id to the decorator to avoid memory leaks
  { id: "render-woxo-widgets" }
);

</script>
3 Likes

First of all: O-M-G THANK YOU SO MUCH :exploding_head:
I’m astonished at the level of the response, my only consolation at seeing such amount work is that is a given that this is going to be of great help for many. This opens a lot of new possibilities, all in benefit of the development of a forum as a hub for a community.

Second: I’m so sorry for answering so late since you answered so quickly. I was first unable to get to my computer sooner, and second I only wanted to answer once indeed I had gone trough the code you so kindly commented (line by line :flushed:, my goodness, thank you so much). Of course, everything worked perfectly, the entire feed loads so quickly… I’m in debt :pray:

Thank you again Johan, I really hope to be able to contribute from my part with my own experience :pray:

PD: I’m totally keeping the heart for the static preview.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.