Categoria que carrega tópicos aleatórios?

Eu tentei pesquisar no Google, mas não encontrei nada relacionado a isso.
Acho que alguns tópicos podem ser muito valiosos, mas os usuários tendem a focar mais nos mais recentes (a menos que venham para um fórum de uma pesquisa do Google, por exemplo).

É possível, talvez através de um componente/plugin, ter uma categoria ou algo assim, onde ele carregue tópicos aleatórios automaticamente? Isso reviveria alguns posts antigos que talvez 3 anos atrás não tiveram tanto engajamento, mas ainda assim eram valiosos, e talvez agora com mais usuários pudessem ter a atenção que merecem.

Para ser claro, eu não quero que os tópicos aleatórios sejam limitados a uma única categoria, embora isso também possa ser bom. Eu estava pensando mais em um recurso global onde qualquer tópico de qualquer categoria pudesse ser carregado nessa seção. Basicamente, da mesma forma que quando vou para a página inicial de https://meta.discourse.org/ posso ver uma lista de todos os tópicos mais recentes, independentemente da categoria, mas no meu caso, ele carregaria tópicos aleatórios também de qualquer categoria, mas não focado na data dos tópicos ou mesmo nas últimas respostas.

Espero que faça sentido…

2 curtidas

Eu gosto da ideia de tópicos aleatórios, mas suponho que haja ajustes a serem feitos.
Meu fórum tem mais de 20 anos e seria estranho ter uma página com um monte de tópicos aleatórios com mais de uma década.

Se você quiser segmentar tópicos com baixo engajamento, deve haver um parâmetro como o número máximo de respostas para serem exibidas.

Mesmo assim, isso não significa que todos esses tópicos de baixo engajamento sejam interessantes.

Você quer segmentar tópicos por algum critério ou apenas… aleatórios?

A coisa mais próxima que existe está nas configurações da categoria:

Isso foi (talvez ainda seja?) usado aqui em Support, pois o estado ideal dos tópicos de Support é resolvido/fechado. Tópicos abertos aqui geralmente significam que nenhuma solução foi fornecida, então dá uma chance aos tópicos de baixo engajamento de serem vistos, mesmo que seja meses ou anos depois - sempre pode ser valioso.

1 curtida

Aleatoriamente.

Poderia ser uma categoria específica, por exemplo, ou até mesmo um botão para Aleatorizar.
Ou talvez apenas uma seção na parte inferior das postagens mais recentes que carregaria umas 3 ou 4 postagens antigas aleatórias?

Mesmo postagens do seu fórum de mais de 20 anos podem ser relevantes hoje, mas talvez quando foram criadas, ninguém realmente prestou atenção e elas poderiam receber um pouco de “amor” agora :wink:

2 curtidas

Hoje decidi pedir ajuda ao Claude para isto.
Foi o que conseguimos, após muitos testes e correções (ele carrega apenas tópicos ABERTOS, o que faz mais sentido para mim):

image

Ao clicar em “Load more random topics” (Carregar mais tópicos aleatórios), ele carrega mais 5 tópicos abaixo dos atuais. Clicar no botão novamente adiciona mais 5, até que não haja mais tópicos:

image

Para carregar os tópicos aleatórios, basta visitar:
seuwebsite.com/?random


Veja como implementar:

1 - Crie um componente
2 - Adicione isto na aba JS:

import { apiInitializer } from "discourse/lib/api";
// Oculta o conteúdo imediatamente se ?random estiver na URL
if (new URLSearchParams(window.location.search).has('random')) {
  const style = document.createElement('style');
  style.textContent = '#main-outlet { display: none; }';
  document.head.appendChild(style);
}

let loadedTopicIds = new Set();

function addRandomTopics(listDiv, button) {
  // Oculta o botão e mostra o carregamento
  button.style.display = 'none';
  const loadingDiv = document.createElement('div');
  loadingDiv.className = 'loading-more';
  loadingDiv.textContent = 'Carregando...';
  listDiv.appendChild(loadingDiv);
  
  const query = `status:open`;
  
  fetch(`/search.json?q=${encodeURIComponent(query)}`)
    .then((response) => response.json())
    .then((data) => {
      loadingDiv.remove();
      
      if (!data || !data.topics || data.topics.length === 0) {
        button.remove();
        const noMoreDiv = document.createElement('div');
        noMoreDiv.className = 'no-more-topics';
        noMoreDiv.textContent = 'Não há mais tópicos para carregar';
        listDiv.appendChild(noMoreDiv);
        return;
      }
      
      // Filtra tópicos já carregados
      const unloadedTopics = data.topics.filter(topic => !loadedTopicIds.has(topic.id));
      
      if (unloadedTopics.length === 0) {
        button.remove();
        const noMoreDiv = document.createElement('div');
        noMoreDiv.className = 'no-more-topics';
        noMoreDiv.textContent = 'Não há mais tópicos para carregar';
        listDiv.appendChild(noMoreDiv);
        return;
      }
      
      // Embaralha e seleciona até 5 tópicos aleatórios
      const shuffled = unloadedTopics.sort(() => 0.5 - Math.random());
      const selected = shuffled.slice(0, Math.min(5, unloadedTopics.length));
      
      // Obtém a categoria dos dados do site Discourse
      const site = Discourse.__container__.lookup('site:main');
      
      selected.forEach(topic => {
        loadedTopicIds.add(topic.id);
        
        const date = new Date(topic.created_at);
        const formattedDate = date.toLocaleDateString('en-US', { 
          year: 'numeric', 
          month: 'short', 
          day: 'numeric' 
        });
        
        const itemDiv = document.createElement('div');
        itemDiv.className = 'random-topic-item';
        
        const linkDiv = document.createElement('div');
        linkDiv.className = 'random-topic-link';
        
        const link = document.createElement('a');
        link.href = `/t/${topic.slug}/${topic.id}`;
        link.textContent = topic.unicode_title || topic.title;
        
        linkDiv.appendChild(link);
        
        const metaDiv = document.createElement('div');
        metaDiv.className = 'random-topic-meta';
        
        const category = site.categories.find(cat => cat.id === topic.category_id);
        const categoryName = category?.name || 'Uncategorized';
        
        metaDiv.textContent = `${categoryName} • ${formattedDate}`;
        
        itemDiv.appendChild(linkDiv);
        itemDiv.appendChild(metaDiv);
        
        // Insere antes do botão
        listDiv.insertBefore(itemDiv, button);
      });
      
      // Verifica se carregamos todos os tópicos disponíveis
      if (selected.length < 5 || loadedTopicIds.size >= data.topics.length) {
        button.remove();
        const noMoreDiv = document.createElement('div');
        noMoreDiv.className = 'no-more-topics';
        noMoreDiv.textContent = 'Não há mais tópicos para carregar';
        listDiv.appendChild(noMoreDiv);
      } else {
        button.style.display = 'block';
      }
    })
    .catch((err) => {
      console.error("Erro de busca:", err);
      loadingDiv.remove();
      button.style.display = 'block';
    });
}

function loadRandomTopics(container) {
  // Reseta os tópicos carregados
  loadedTopicIds = new Set();
  
  container.innerHTML = '<div class="spinner"></div>';
  
  const query = `status:open`;
  
  fetch(`/search.json?q=${encodeURIComponent(query)}`)
    .then((response) => response.json())
    .then((data) => {
      if (!data || !data.topics || data.topics.length === 0) {
        container.innerHTML = '<p>Nenhum tópico encontrado</p>';
        return;
      }
      
      // Embaralha e seleciona 5 tópicos aleatórios
      const shuffled = data.topics.sort(() => 0.5 - Math.random());
      const selected = shuffled.slice(0, 5);
      
      // Constrói o HTML
      const listDiv = document.createElement('div');
      listDiv.className = 'random-topics-list';
      
      const heading = document.createElement('h2');
      heading.textContent = 'Tópicos Abertos Aleatórios';
      listDiv.appendChild(heading);
      
      // Obtém a categoria dos dados do site Discourse
      const site = Discourse.__container__.lookup('site:main');
      
      selected.forEach(topic => {
        loadedTopicIds.add(topic.id);
        
        const date = new Date(topic.created_at);
        const formattedDate = date.toLocaleDateString('en-US', { 
          year: 'numeric', 
          month: 'short', 
          day: 'numeric' 
        });
        
        const itemDiv = document.createElement('div');
        itemDiv.className = 'random-topic-item';
        
        const linkDiv = document.createElement('div');
        linkDiv.className = 'random-topic-link';
        
        const link = document.createElement('a');
        link.href = `/t/${topic.slug}/${topic.id}`;
        link.textContent = topic.unicode_title || topic.title;
        
        linkDiv.appendChild(link);
        
        const metaDiv = document.createElement('div');
        metaDiv.className = 'random-topic-meta';
        
        const category = site.categories.find(cat => cat.id === topic.category_id);
        const categoryName = category?.name || 'Uncategorized';
        
        metaDiv.textContent = `${categoryName} • ${formattedDate}`;
        
        itemDiv.appendChild(linkDiv);
        itemDiv.appendChild(metaDiv);
        listDiv.appendChild(itemDiv);
      });
      
      const button = document.createElement('button');
      button.className = 'btn btn-primary load-more-random';
      button.textContent = 'Carregar mais tópicos aleatórios';
      button.addEventListener('click', () => {
        addRandomTopics(listDiv, button);
      });
      
      listDiv.appendChild(button);
      container.innerHTML = '';
      container.appendChild(listDiv);
    })
    .catch((err) => {
      console.error("Erro de busca:", err);
      container.innerHTML = '<p>Erro ao carregar tópicos</p>';
    });
}

export default apiInitializer("1.8.0", (api) => {
  let wasOnRandomPage = new URLSearchParams(window.location.search).has('random');
  
  api.onPageChange(() => {
    const urlParams = new URLSearchParams(window.location.search);
    const isOnRandomPage = urlParams.has('random');
    
    // Se estávamos na página aleatória, mas agora não estamos mais, força recarregamento
    if (wasOnRandomPage && !isOnRandomPage) {
      window.location.reload();
      return;
    }
    
    wasOnRandomPage = isOnRandomPage;
    
    if (!isOnRandomPage) {
      return;
    }
    
    // Mostra e substitui o conteúdo
    const mainContent = document.querySelector('#main-outlet');
    if (mainContent) {
      mainContent.style.display = 'block';
      mainContent.innerHTML = '<div id="random-topics-container"></div>';
      
      const container = document.querySelector('#random-topics-container');
      loadRandomTopics(container);
    }
  });
});

3 - Adicione isto na aba CSS:

.random-topics-list {
  max-width: 800px;
  margin: 40px auto;
  padding: 20px;
}

.random-topics-list h2 {
  margin-bottom: 30px;
  font-size: 2em;
}

.random-topic-item {
  margin-bottom: 25px;
}

.random-topic-link a {
  color: #E9E9E9;
  text-decoration: underline;
  font-size: 1.1em;
}

.random-topic-link a:hover {
  color: #229ED7;
}

.random-topic-meta {
  color: #808080;
  font-size: 0.85em;
  margin-top: 4px;
}

.load-more-random {
  margin-top: 30px;
}

.loading-more {
  text-align: center;
  margin-top: 20px;
  color: #808080;
}

.no-more-topics {
  text-align: center;
  margin-top: 30px;
  color: #808080;
  font-style: italic;
}

.spinner {
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  border-radius: 50%;
  width: 40px;
  height: 40px;
  animation: spin 1s linear infinite;
  margin: 100px auto;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

Se alguém souber como usar a visualização normal do Discourse em vez de usar uma página HTML personalizada, isso seria ótimo! Estou satisfeito com o recurso em si, mas ter o layout padrão seria melhor, se alguém puder compartilhar como fazer?