¿Categoría que carga temas aleatorios?

Intenté buscar en Google, pero no encontré nada relacionado con esto.
Creo que algunos temas pueden ser muy valiosos, pero los usuarios tienden a centrarse más en los más recientes (a menos que lleguen a un foro desde una búsqueda de Google, por ejemplo).

¿Es posible, tal vez a través de un componente/plugin, tener una categoría o algo así, donde cargue temas aleatorios automáticamente? Esto reviviría algunas publicaciones antiguas que tal vez hace 3 años no tuvieron mucha participación, pero que seguían siendo valiosas, y tal vez ahora con más usuarios podrían tener la atención que merecen.

Para ser claro, no quiero que los temas aleatorios se limiten a una sola categoría, aunque eso también podría ser bueno. Estaba pensando más en una función global donde cualquier tema de cualquier categoría pudiera cargarse en esa sección. Básicamente, de la misma manera que cuando voy a la página principal de https://meta.discourse.org/ puedo ver una lista de todos los temas más recientes, independientemente de la categoría, pero en mi caso, cargaría temas aleatorios también de cualquier categoría, pero no centrados en la fecha de los temas o incluso en las últimas respuestas.

Espero que tenga sentido…

2 Me gusta

Me gusta la idea de los temas aleatorios, pero supongo que hay que hacer algunos ajustes.
Mi foro tiene más de 20 años y sería raro tener una página con un montón de temas aleatorios de hace más de una década.

Si quieres dirigirte a temas con poca participación, debería haber un parámetro como el número máximo de respuestas a mostrar.

Aun así, eso no significa que todos esos temas de baja participación sean interesantes.

¿Quieres dirigirte a temas según algún criterio o simplemente… aleatorios?

Lo más parecido que existe está en la configuración de la categoría:

Se usó (¿quizás todavía se usa?) aquí en Support, ya que el estado ideal de los temas de Support es resuelto/cerrado. Los temas abiertos aquí normalmente significan que no se proporcionó una solución, por lo que da una oportunidad a los temas de baja participación de ser examinados, incluso si es meses o años después, siempre puede ser valioso.

1 me gusta

Al azar.

Podría ser una categoría específica, por ejemplo, o incluso un botón para aleatorizar.
¿O tal vez solo una sección en la parte inferior de las últimas publicaciones que cargue unas 3 o 4 publicaciones antiguas aleatorias?

Incluso las publicaciones de tu foro de más de 20 años pueden ser relevantes hoy en día, pero tal vez cuando se crearon, nadie les prestó mucha atención y podrían recibir algo de “amor” ahora :wink:

2 Me gusta

Hoje decidi pedir ajuda ao Claude com isto.
Isto é o que criámos, após muitos testes e correções (só carrega tópicos ABERTOS, o que faz mais sentido para mim):

image

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

image

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


Veja como implementá-lo:

1 - Crie um componente
2 - Adicione isto ao separador JS:

import { apiInitializer } from "discourse/lib/api";
// Hide content immediately if ?random is in 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) {
  // Hide button and show loading
  button.style.display = 'none';
  const loadingDiv = document.createElement('div');
  loadingDiv.className = 'loading-more';
  loadingDiv.textContent = 'Loading...';
  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 = 'No more topics to load';
        listDiv.appendChild(noMoreDiv);
        return;
      }
      
      // Filter out already loaded topics
      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 = 'No more topics to load';
        listDiv.appendChild(noMoreDiv);
        return;
      }
      
      // Shuffle and pick up to 5 random topics
      const shuffled = unloadedTopics.sort(() => 0.5 - Math.random());
      const selected = shuffled.slice(0, Math.min(5, unloadedTopics.length));
      
      // Get category from Discourse site data
      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);
        
        // Insert before the button
        listDiv.insertBefore(itemDiv, button);
      });
      
      // Check if we've loaded all available topics
      if (selected.length < 5 || loadedTopicIds.size >= data.topics.length) {
        button.remove();
        const noMoreDiv = document.createElement('div');
        noMoreDiv.className = 'no-more-topics';
        noMoreDiv.textContent = 'No more topics to load';
        listDiv.appendChild(noMoreDiv);
      } else {
        button.style.display = 'block';
      }
    })
    .catch((err) => {
      console.error("Fetch error:", err);
      loadingDiv.remove();
      button.style.display = 'block';
    });
}

function loadRandomTopics(container) {
  // Reset loaded topics
  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>No topics found</p>';
        return;
      }
      
      // Shuffle and pick 5 random topics
      const shuffled = data.topics.sort(() => 0.5 - Math.random());
      const selected = shuffled.slice(0, 5);
      
      // Build the HTML
      const listDiv = document.createElement('div');
      listDiv.className = 'random-topics-list';
      
      const heading = document.createElement('h2');
      heading.textContent = 'Random Open Topics';
      listDiv.appendChild(heading);
      
      // Get category from Discourse site data
      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 = 'Load more random topics';
      button.addEventListener('click', () => {
        addRandomTopics(listDiv, button);
      });
      
      listDiv.appendChild(button);
      container.innerHTML = '';
      container.appendChild(listDiv);
    })
    .catch((err) => {
      console.error("Fetch error:", err);
      container.innerHTML = '<p>Error loading topics</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');
    
    // If we were on random page but now we're not, force reload
    if (wasOnRandomPage && !isOnRandomPage) {
      window.location.reload();
      return;
    }
    
    wasOnRandomPage = isOnRandomPage;
    
    if (!isOnRandomPage) {
      return;
    }
    
    // Show and replace the content
    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 ao separador 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 personalizada em HTML, isso seria ótimo! Estou satisfeito com a funcionalidade em si, mas ter o layout padrão seria melhor, se alguém puder partilhar como fazer.