Add a featured topic list to your Discourse homepage

In this howto we’ll add a topic list populated by a tag or a category on top of your homepage’s main topic list, like this:

All of the code below can be added to the </head> (head_tag.html) section of your theme.

This first part checks which page we’re on, then sets up our topics. For the purpose of this how-to we’re pulling topics tagged featured. If you want to pull topics from another tag or a category you need to change the /tags/featured.json url in here.

<script type="text/discourse-plugin" version="0.8">
  const ajax = require('discourse/lib/ajax').ajax;
  const Topic = require('discourse/models/topic').default;
  // We're using ajax and the Topic model from Discourse

  api.registerConnectorClass('above-main-container', 'featured-topics', {
    // above-main-container is the plugin outlet,
    // featured-topics is your custom component name

    setupComponent(args, component) {

      api.onPageChange((url, title) => {
        if ((url == "/") || (url == "/latest") ){
        // on page change, check if url matches
        // if your homepage isn't /latest change this to /categories
        
          $('html').addClass('custom-featured-topics');
          // add a class to the HTML tag for easy CSS targetting

          component.set('displayfeaturedTopics', true);
          // we'll use this later to show our template

          component.set("loadingTopics", true);
          // helps us show a loading spinner until topics are ready

          ajax("/tag/featured.json").then (function(result){
          // Get posts from tag "featured" using AJAX
          // if this were a category you'd use /c/featured.json

            let featuredTopics = [];
            // Create an empty array, we'll push topics into it

            var featuredUsers = result.users;
            // Get the relevant users

            result.topic_list.topics.slice(0,4).forEach(function(topic){
            // We're extracting the topics starting at 0 and ending at 4
            // This means we'll show 3 total. Increase 4 to see more.

              topic.posters.forEach(function(poster){
                poster.user = $.grep(featuredUsers, function(e){ return e.id == poster.user_id; })[0];
              });
              // Associate users with our topic

              featuredTopics.push(Topic.create(topic));
              // Push our topics into the featuredTopics array
            });

            component.set("loadingTopics", false);
            // Topics are loaded, stop showing the loading spinner

            component.set('featuredTopics', featuredTopics);
            // Setup our component with the topics from the array
          });

        } else {
        // If the page doesn't match the urls above, do this:

          $('html').removeClass('custom-featured-topics');
          // Remove our custom class

          component.set('displayfeaturedTopics', false);
          // Don't display our customization
        }
      });
    }
  });
</script>

This second part is your handlebars template. This is your HTML. Note how the script tag references the relevant plugin outlet and the custom component name set above.

<script
  type="text/x-handlebars"
  data-template-name="/connectors/above-main-container/featured-topics"
>

  {{#if displayfeaturedTopics}}
  <!-- If our component is true, show this content: -->

      <div class="custom-featured-topics-wrapper">
        {{conditional-loading-spinner condition=loadingTopics}}
        <!-- Show a loading spinner if topics are loading -->

        {{#unless loadingTopics}}
        <!-- Unless topics are still loading... -->
          {{topic-list topics=featuredTopics showPosters=true}}
          <!-- Show the topic list -->
        {{/unless}}

      </div>

  {{/if}}
</script>

Now you’ll have a featured topic list on your homepage.

If you want to add custom CSS (common.scss) for some additional styling, you’d do it like this:

.custom-featured-topics {
  .custom-featured-topics-wrapper .topic-list {
    // your css here, at minimum you'll probably want some margin:
    margin-bottom: 2em;
  }
}

If you want to learn more about the topics covered in this example or see what else you can do with the Plugin API, check out our Developer’s guide to Discourse Themes

51 Likes

Ah I think it’s because we made some changes to our themes, outlined here: Upcoming core changes that may break some themes/components (April 12)

In the code above I wasn’t declaring the featuredTopics variable, adding let should do the trick… so you’ll just need to update:

featuredTopics = [];

to

let featuredTopics = [];

8 Likes

Hello! Do we need to update something after making changes into <head>?

I tried the code but nothing shows up, maybe I’m missing something. Browser shows /c/featured.json not found (I’m in spanish, maybe could be related to that?)

Thanks for your time.

1 Like

Perhaps someone can share their design for this feature topic block? Thank you. :grinning:

1 Like

Thanks for this topic, it’s very useful.

If there are no topics with the relevant tag, the spinner shows forever which is not great. I wroked around this by just removing the spinner, I don’t see the value of it in this context.

Personally I prefer to place this in the discovery-above plugin outlet instead of above-main-container, but I can see that’s debatable.

Unfortunately this will include closed topics, which may well be not what a site wants. I fixed this crudely with

              if (!topic.closed){
                topics.push(Topic.create(topic));
                // Push our topics into the topics array
              }

but if I was a real js developer I’d have made sure I still got the number of topics I wanted (defaults to 3 in the example) regardless of whether they were any closed.

2 Likes

This component works great for me. I just have one question. My default hompage is /categories but some users have changed it to /latest in their preferences.

I would like for this to show up on /categories only but the “/” page is ambiguous depending on your setting.

I see there is a homepage_id value that will allow me to determine which hompage is default. How do I access this value from the script? I tried getCurrentUser but it doesn’t seem to include this.

1 Like

Will this theme component help for now?

Just want to put it out there. :grinning:

1 Like

Hello, thanks for providing this solution - there is an issue though.

I tried it and found out that the api.registerConnectorClass is executed every time the route changes (aka pressing Categories, Top, Latest) - which will register the event handler passed to api.onPageChange a subsequent time for every page route change. This in turn will cause numerous API calls to fire which ultimately kills the API access limit.

I followed precisely your example and I do not understand why this is happening as the code should only be run once - to register the api.onPageChange event once, right?

Any feedback appreciated.

2 Likes

How are topics with the same tag sorted? Is there a way to manually sort the featured topics? Thank you!

1 Like