Oggi ho deciso di chiedere aiuto a Claude per questo.
Ecco cosa abbiamo ideato, dopo molti test e correzioni (carica solo gli argomenti APERTI, il che ha più senso per me):
Quando si fa clic su “Load more random topics” (Carica altri argomenti casuali), vengono caricati altri 5 argomenti sotto quelli attuali. Cliccando di nuovo sul pulsante, ne vengono aggiunti altri 5, finché non ci sono più argomenti:
Per caricare gli argomenti casuali, basta visitare:
yourwebsite.com/?random
Ecco come implementarlo:
1 - Crea un componente
2 - Aggiungi questo alla scheda JS:
import { apiInitializer } from "discourse/lib/api";
// Nasconde il contenuto immediatamente se ?random è nell'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) {
// Nasconde il pulsante e mostra il caricamento
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;
}
// Filtra gli argomenti già caricati
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;
}
// Mescola e seleziona fino a 5 argomenti casuali
const shuffled = unloadedTopics.sort(() => 0.5 - Math.random());
const selected = shuffled.slice(0, Math.min(5, unloadedTopics.length));
// Ottieni la categoria dai dati del sito 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);
// Inserisci prima del pulsante
listDiv.insertBefore(itemDiv, button);
});
// Controlla se abbiamo caricato tutti gli argomenti disponibili
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) {
// Resetta gli argomenti caricati
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;
}
// Mescola e seleziona 5 argomenti casuali
const shuffled = data.topics.sort(() => 0.5 - Math.random());
const selected = shuffled.slice(0, 5);
// Costruisci l'HTML
const listDiv = document.createElement('div');
listDiv.className = 'random-topics-list';
const heading = document.createElement('h2');
heading.textContent = 'Random Open Topics';
listDiv.appendChild(heading);
// Ottieni la categoria dai dati del sito 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 = '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');
// Se eravamo sulla pagina random ma ora non lo siamo più, forza il ricaricamento
if (wasOnRandomPage && !isOnRandomPage) {
window.location.reload();
return;
}
wasOnRandomPage = isOnRandomPage;
if (!isOnRandomPage) {
return;
}
// Mostra e sostituisci il contenuto
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 - Aggiungi questo alla scheda 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 qualcuno sa come usare la normale visualizzazione di Discourse invece di usare una pagina personalizzata HTML, sarebbe fantastico! Sono soddisfatto della funzionalità in sé, ma avere il layout predefinito sarebbe meglio, se qualcuno può condividere come fare?