Embed latest topics from Discourse on your website


#1

N.B. The post has been updated so the embed JavaScript works with Discourse v2 Beta’s API.

This post shares something we did to dynamically load content from this Discourse forum on http://www.daemon.com.au/labs using JavaScript. The code retrieves the latest content in selected categories from Discourse and then wraps it around the markup that works with our website before injects it into the page. We have then made a few more changes to make the code generic so that it can be easily reused on different websites for loading content from different Discourse forums.

A Discourse forum possesses numerous data endpoints. For example, when you go to Latest, it loads the latest.json endpoint that returns the data needed for that particular page. This allows us to display content in Discourse on our own website.

Before we start

In order to load content from Discourse remotely, we have to make Discourse’s endpoints available to our website. This can be done in Discourse’s “Admin” settings.

Log in Discourse using an account with admin access, then go to the “Settings” tab in the “Admin” panel:

Find “Security” in the left hand navigation, then locate the “cors origins” field on the right hand side. Add the URL of the website that will display content from Discourse in the field (in our case: http://www.daemon.com.au/), then save the changes:

Endpoints

Since Discourse generates countless data endpoints, it is important to find the right one depending on what content is required to be displayed remotely. Adding /l/latest.json at the end of the URL of a category page will show the endpoint containing the latest posts for that particular category. For example, https://labs.daemon.com.au/c/design/l/latest.json is the endpoint for https://labs.daemon.com.au/c/design.

HTML & JavaScript

Since we now have the endpoint we needs, we now move to enable our site to read the endpoint so that it can retrieve useful information and display it correctly. In this example, we aim to display the latest 3 posts published by either User #1, #2 or #3 from the “Design” category in the #div on our site. In addition, we do not want to show the “About the design category” post.

N.B. In our example, we use default Bootstrap v4.0.0-beta.2 to provide some necessary styles for demo purposes only and its use is totally optional.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta content="ie=edge" http-equiv="x-ua-compatible">
    <meta content="initial-scale=1.0, shrink-to-fit=no, width=device-width" name="viewport">
    <title>Discourse embed</title>

    <!-- Bootstrap CSS for basic styles in the demo -->
    <link crossorigin="anonymous" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" rel="stylesheet">
  </head>
  <body>
    <div class="container">
      <div class="card-deck" id="div"></div>
    </div>

    <!-- jQuery -->
    <script crossorigin="anonymous" integrity="sha384-p7RDedFtQzvcp0/3247fDud39nqze/MUmahi6MOWjyr3WKWaMOyqhXuCT1sM9Q+l" src="https://code.jquery.com/jquery-3.2.1.js"></script>

    <!-- JavaScript -->
    <script>
      (function ($) {
        'use strict'

        $(function () {
          $.ajax('https://labs.daemon.com.au/c/design/l/latest.json').then(function (result) {
            // Parse data to generate content from Discourse:
            // * Discourse endpoint, i.e. `result`,
            // * Number of posts to be shown on your site, e.g. `3`,
            // * Optional array of user IDs (whitelist)
            //   if only posts published by particular users are intended to be shown on your site, e.g. `[1, 2, 3]`.
            $('#div').discourse(result, 3, [1, 2, 3]);
          });

          $.fn.discourse = function (feed, numToShow, whitelist) {
            var feedLength = feed.topic_list.topics.length;

            // Make sure there are enough posts to be shown.
            if (numToShow > feedLength) {
              numToShow = feedLength;
            }

            for (var i = 0; i < numToShow; i++) {
              var content = '';

              // URLs in Discourse endpoints are all relative URLs (e.g. topic.image_url),
              // we need this so that the links displayed on your site point to the right places.
              // Please modify this to the URL of your Discourse forum.
              var discourseURL = 'https://labs.daemon.com.au/';

              // Variables for Discourse post data.
              var post          = feed.topic_list.topics[i],
                  postAuthor    = post.posters[0].user_id,
                  postDate      = new Date(post.created_at),
                  postLink      = discourseURL + 't/' + post.slug + '/' + post.id;

              // If whitelist is present, check whether the post author is a verified user.
              if (typeof whitelist !== 'undefined') {
                var verifiedUser = false;

                for (var n = 0; n < whitelist.length; n++) {
                  if (postAuthor === whitelist[n]) {
                    verifiedUser = true;
                    break;
                  }
                }

                // If the post author is not on the whitelist,
                // breaks this iteration and continues with the next iteration in the loop.
                if (!verifiedUser) {
                  // Increase number of posts to be shown if possible
                  // to compensate for the eliminated post.
                  if (numToShow < feedLength) {
                    numToShow++;
                  }

                  continue;
                }
              }

              // The following block of code is optional.
              // The purpose is to ignore the "About the X category" post
              // since it may not be desirable to be displayed on your site.
              if (post.title.substring(0, 10) === "About the " && post.title.substring(post.title.length - 9) === ' category') {
                // Increase number of posts to be shown if possible
                // to compensate for the eliminated post.
                if (numToShow < feedLength) {
                  numToShow++;
                }

                continue;
              }

              // If a post does not have a thumbnail,
              // then use a default placeholder image as its thumbnail for your site.
              // Please modify this to use your site's placeholder image.
              if (post.image_url === null) {
                post.image_url = 'http://placehold.it/320x180';
              }

              // Generate HTML for your site.
              // This part of the code may need to be modified accordingly
              // to fit the markup of your site.
              content += '<div class="card" style="max-width: 20rem;">';
                content += '<img alt="' + post.fancy_title + '" class="card-img-top" src="' + post.image_url + '">';
                content += '<div class="card-body">';
                  content += '<h4 class="card-title">' + post.fancy_title + '</h4>';
                  content += '<p class="card-text"><small>' + postDate.getDate() + '/' + postDate.getMonth() + '/' + postDate.getFullYear() + '</small></p>';
                  // The following line does a little bit more than displaying excerpt as it is,
                  // it replaces `<a>` tags in the excerpt with `<em>` tags
                  // so that they do not show up as links on your site.
                  // This is optional, however it may be necessary under certain circumstances. 
                  content += '<p class="card-text">' + post.excerpt.replace(/<a/g, '<em').replace(/<\/a/g, '</em') + '</p>';
                  content += '<a href="' + postLink + '">Read more</a>';
                content += '</div>';
              content += '</div>';

              $(this).append(content);
            }
          };
        });
      }(jQuery));
    </script>
  </body>
</html>

The final look:

The JavaScript is generic enough to be reused on different website, however you may want to go through step by step to tailor it to your needs. Especially the part to generate the HTML markup very likely requires to be customised to suit the markup of your site.


#2

Awesome post!

We also use this technique for publishing content onto the FarCry Platform website direct from the blog category of the FarCry developer forum.


#5

Great post. Thank you very much for sharing.
I’ve got it to work on a client’s website, except for one thing: I cannot get the contents of the posts. The expression ‘post.excerpt’ gives an undefined result. I can’t find any reference to it in the api documentation either.

Where does it come from? How can I make it work as well?

Do I need to install a special plugin?

Kind regards

Ludo