Incorporando uma lista de tópicos do Discourse em outro site

Se você baixar as últimas versões do Discourse, terá a capacidade de incorporar listas de tópicos em outros sites por meio de algum JavaScript e HTML simples.

O caso de uso típico para isso é um blog ou outro site orientado a conteúdo, onde você deseja um widget na lateral da tela que liste tópicos. Você pode filtrar por categoria, tag ou qualquer outra opção de filtro pública disponível.

Como incorporar uma lista de tópicos

Primeiro, você deve ativar a configuração do site embed topics list (incorporar lista de tópicos).

Em seguida, em seu HTML, adicione uma tag <script> que inclua o JavaScript necessário para incorporar os tópicos do Discourse. Você pode adicioná-la onde normalmente adiciona scripts. Por exemplo:

<script src="http://URL/javascripts/embed-topics.js"></script>

Substitua URL pelo endereço do fórum, incluindo a subpasta, se existir.

Depois disso, no <body> do seu documento HTML, adicione uma tag d-topics-list para indicar a lista de tópicos que você deseja incorporar. Você também precisará substituir URL pela sua URL base aqui:

<d-topics-list discourse-url="URL" category="1234" per-page="5"></d-topics-list>

Qualquer atributo que você fornecer (exceto discourse-url, que é obrigatório) será convertido em parâmetros de consulta para a pesquisa de tópicos. Então, se você quiser pesquisar tópicos por tag, pode fazer o seguinte:

<d-topics-list discourse-url="URL" tags="cool"></d-topics-list>

Se um parâmetro de consulta tiver underlines, converta-os para hífens. No exemplo acima, você provavelmente notou que per_page se tornou per-page.

Em contextos SameSite (ou seja, o site de incorporação e seu fórum compartilham um domínio principal), o Discourse saberá se você está logado no fórum e exibirá os resultados de acordo. Não se surpreenda se vir categorias seguras e afins quando estiver logado — usuários anônimos não poderão ver!

Lista de parâmetros

template: Pode ser complete ou basic (padrão). Enquanto o basic é apenas uma lista de títulos de tópicos, o complete traz título, nome do usuário, avatar do usuário, data de criação e miniatura do tópico.

per-page: Número. Controla quantos tópicos retornar.

category: Número. Restringe os tópicos a uma única categoria. Passe o id da categoria de destino.

allow-create: Booleano. Se habilitado, a incorporação terá um botão “Novo Tópico”.

tags: String. Restringe os tópicos aos associados a esta tag.

top_period: Um de all, yearly, quarterly, monthly, weekly, daily. Se habilitado, retornará os tópicos “Top” do período.

Exemplos

Criei um site de exemplo aqui:

https://embed.eviltrout.com

Você deve ser capaz de visualizar o código-fonte no seu navegador para ver o código, mas também todo o código-fonte está no GitHub:

Esta é uma funcionalidade totalmente nova, então qualquer feedback ou solicitação será muito apreciada.

Estilizando a lista

Você pode usar nosso recurso de tema existente para adicionar estilos personalizados para a lista incorporada.

Por exemplo, por padrão, nossa lista incorporada usando o modelo complete aparece assim:

Se você quiser que ela se pareça, por exemplo, com uma grade, pode adicionar SCSS personalizado em Tema > Comum > CSS Incorporado:

.topics-list {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  
  .topic-list-item { 
    .main-link {
      border: 1px dotted gray;
      padding: 0;
    }
  
    .topic-column-wrapper {
      flex-direction: column-reverse;
      
      .topic-column.details-column {
        width: 100%;
      }
        
      .topic-column.featured-image-column .topic-featured-image img {
        max-width: initial;
        max-height: initial;
        width: 100%;
      }
    }
  }
}

O que fará com que ela se pareça com isto:

95 curtidas

Hey folks! We’re looking to have the links open in a new tab (so target="_blank" instead of target="_parent"). Is there an easy way to do this given the iframe?

2 curtidas

I don’t know if it’s what you’re looking for, but something like this should work to open links in new tabs. CORS has to be enabled for the external domain. I tried it this way because I wanted to filter out a pinned topic.

// `fetch` might require a polyfill
const targetEl = document.getElementById("forumTopics");

function renderTemplate(topicsArr) {
    const items = topicsArr.map(
        (topic) => `<li><a href="${topic[1]}" target="_blank">${topic[0]}</li>`
    );

    targetEl.innerHTML = `<ul>${items.join("\n")}</ul>`;
}

// tack `.json` on to any topic list
fetch("https://forum.example.com/latest.json")
    .then((res) => res.json())
    .then((json) => {
        const topics = json.topic_list.topics
            .slice(1, 6)
            .map((t) => [
                t.title,
                `https://forum.example.com/t/${t.slug}/${t.id}`,
            ]);

        renderTemplate(topics);
    });
3 curtidas

Thanks! I like this idea, but based on this post What are the risks of enabling Cross-origin resource sharing (DISCOURSE_ENABLE_CORS) I’m thinking that enabling CORS isn’t the best idea.

2 curtidas

You are only supposed to enable CORS for sites you trust. Enabling CORS for every source is defeating the purpose of it.

6 curtidas

I only enabled CORS for other sites I control. The goal was to load a list of forum topics in our other sites with frontend code. That method probably wouldn’t work for letting random sites embed the posts.

Edit: if you copy/paste that code watch out for .slice(1, 6) because it removes one of the topics. (I think that was the pinned topic that I wanted to remove.)

3 curtidas

Cool, this is something we can try then - I don’t have a site other than localhost for now (during testing) which is why I was hesitant to add it. But if I can find someone that has a place, we can give it a go. Thanks for your help!

4 curtidas

Hey @j127

we are currently facing the same issue and want to open links in new tab.
Just quick question.

Where exactly do you put the code that you provided

@eviltrout Any idea why in my case the CSS styles are not appearing?

2 curtidas

That code is just a quick example of the general idea. I don’t think fetch works in all browsers. I can rewrite it in ES5 if you want.

Is your site WordPress or some kind of headless WordPress?

4 curtidas

It appears to work in all modern browsers, and Discourse doesn’t support IE11 any more…

4 curtidas

My site is just regular wordpress, not headless.

1 curtida

Can I use this to embed a list of Discourse Topics in another Discourse instance? Or is there a smarter way to do that?

My use case is having a ‘bridge’ between two instances as a temporary measure until they can be merged in future. It would be nice to have this automated and dynamic.

2 curtidas

If you don’t care about IE, then that snippet should work. (CORS has to be enabled for the external domain in the Discourse settings.)

To test it, open any page on this forum and paste the snippet below in the browser console. I changed the target element to document.body, so it will replace the entire page with the list for forum topics. For a real site, just change the target element to document.getElementById("#someId") and then put a div with that ID on the page somewhere. The script will fill it with the list of topics.

// `fetch` might require a polyfill unless you don't care about IE
// const targetEl = document.getElementById("forumTopics");

// this replaces the body of the page, just as an example
const targetEl = document.body;

// put your forum's URL here to test it on your own forum
const baseForumURL = `https://meta.discourse.org`;

// how many topics you want to show
const numTopics = 6;

function renderTemplate(topicsArr) {
    const items = topicsArr.map(
        (topic) => `<li><a href="${topic[1]}" target="_blank">${topic[0]}</li>`
    );

    targetEl.innerHTML = `<ul>${items.join("\n")}</ul>`;
}

// tack `.json` on to any topic list
fetch(`${baseForumURL}/latest.json`)
    .then((res) => res.json())
    .then((json) => {
        const topics = json.topic_list.topics
            .slice(0, numTopics)
            .map((t) => [
                t.title,
                `${baseForumURL}/t/${t.slug}/${t.id}`,
            ]);

        renderTemplate(topics);
    });

Example: if the target div somewhere on your WP site looks like this

<div id="forumTopics">
    <!-- the forum posts will appear here -->
</div>

then the JavaScript could target that ID like this:

// look for this line in the original example I posted
const targetEl = document.getElementById("forumTopics");
4 curtidas

By that you mean the code in the first post or the snippet in post 50? And no, I don’t care about IE. No need to cater to that dinosaur :).
In my case the CSS is not loaded for my snippet in any browser.

So this is JS and I have to put <script> tag around it when implementing it to wordpress, correct?

2 curtidas

It has been a while since I used WordPress. Is it something that you’re adding to a theme’s HTML? If yes, then wrap my code in a script tag and put the <div> wherever you want the posts to appear.

I think you could drop the code below right into the HTML of a WordPress template wherever you want the posts to appear. Be sure to update the line that has the URL of the forum. If it doesn’t work, let me know. (It’s untested.)

Click here for the code
<div id="forumTopics"></div>
<script>
// put your forum's URL here to test it on your own forum
const baseForumURL = `https://forum.example.com`;

// how many topics you want to show
const numTopics = 6;

const targetEl = document.getElementById("forumTopics");
// If the posts don't load for some reason, you could show a link to the forum
targetEl.innerHTML = `<a class="link-to-discourse" href="${baseForumURL}/">View the latest forum posts</a>`;

function renderTemplate(topicsArr) {
    const items = topicsArr.map(
        (topic) => `<li><a href="${topic[1]}" target="_blank">${topic[0]}</li>`
    );

    targetEl.innerHTML = `<ul>${items.join("\n")}</ul>`;
}

// tack `.json` on to any topic list
fetch(`${baseForumURL}/latest.json`)
    .then((res) => res.json())
    .then((json) => {
        const topics = json.topic_list.topics
            .slice(0, numTopics)
            .map((t) => [
                t.title,
                `${baseForumURL}/t/${t.slug}/${t.id}`,
            ]);

        renderTemplate(topics);
    });
</script>
3 curtidas

Thx, appreciate the full snippet

After initial test, only a link is displayed. But I think CORS is disabled, so gonna have my host enable that.
Meanwhile, how can I make the script only display post with a specific tag and from a specific category?

2 curtidas

I’m not sure, but I think the format is
https://forum.example.com/tags/c/<catgory_name>/<tag_name>

Example: the category is “support” and the tag is “css”:
https://meta.discourse.org/tags/c/support/css

To turn it into JSON, add .json on the end:
https://meta.discourse.org/tags/c/support/css.json

So in the fetch function, instead of /latest.json, use something like /tags/c/support/css.json.

(Untested.)

3 curtidas

And if I want to embed 5 topics in the Discourse-instance itself?

I make a topic. Make it a wiki. I publish it as a page. How do I embed the five latest topics from a specific category/tag or use the template this feature supports?

Thanks.

1 curtida

What is the query parameter for top topics? I tried order="top" but it is not returning the top topics the same way the forum does based on a particular period. How can I replicate the forum filter result? Where can I look up the attribute values Discourse’s filter options take? Thank you!

2 curtidas

Is it possible to change the font style of the embedded content? I tried CSS selectors like d-topics-list iframe html .topics-list .topic-list-item without success. Appreciate the pointer in advance!

2 curtidas