Enfin, j’utilise ceci pour l’instant :
<script type="text/discourse-plugin" version="0.11.3">
api.onPageChange(() => {
if (window.location.pathname === "/") {
const container = document.querySelector(".latest-topic-list");
if (!container) return;
// Évite les initialisations multiples
if (window.latestRepliesInitialized) return;
window.latestRepliesInitialized = true;
// Paramètres
const POLLING_INTERVAL = 2000; // 2 secondes
const COMMENTS_TO_SHOW = 15;
const CACHE_DURATION = 30 * 60 * 1000; // 30 minutes
// Clés de cache
const CACHE_KEY = "discourse_latest_replies_data";
const CACHE_TIMESTAMP_KEY = "discourse_latest_replies_timestamp";
const CACHE_LAST_ID_KEY = "discourse_latest_replies_last_id";
// Stocke le dernier ID de publication vu pour comparaison
let lastSeenPostId = parseInt(localStorage.getItem(CACHE_LAST_ID_KEY) || "0");
let pollingIntervalId = null;
console.log(`Initialisation du plugin des derniers commentaires (dernier ID en cache : ${lastSeenPostId})`);
// Fonction pour charger les commentaires
function loadLatestReplies(silent = false, forceRefresh = false) {
// Vérifie d'abord le cache, sauf si mise à jour forcée
if (!forceRefresh) {
const cachedData = localStorage.getItem(CACHE_KEY);
const cacheTimestamp = localStorage.getItem(CACHE_TIMESTAMP_KEY);
const now = Date.now();
// Si nous avons des données en cache valides
if (cachedData && cacheTimestamp && now - parseInt(cacheTimestamp) < CACHE_DURATION) {
try {
const results = JSON.parse(cachedData);
console.log(`Utilisation des données en cache (${results.length} commentaires, cache de ${Math.round((now - parseInt(cacheTimestamp)) / 1000 / 60)} minutes)`);
renderLatestReplies(results, false);
// Si ce n'est pas silencieux, nous n'avons rien d'autre à faire
if (!silent) {
return;
}
// Si c'est silencieux, nous continuons pour vérifier les mises à jour
} catch (e) {
console.error("Erreur lors du traitement du cache :", e);
// En cas d'erreur dans le cache, nous continuons pour récupérer de nouvelles données
}
} else if (cachedData) {
console.log("Cache expiré, récupération de nouvelles données");
} else {
console.log("Aucun cache trouvé, récupération de nouvelles données");
}
} else {
console.log("Forçage de la mise à jour, ignore le cache");
}
// Si ce n'est pas silencieux, affiche l'indicateur de chargement
if (!silent) {
// Si un conteneur de commentaires existe déjà, n'affiche pas l'indicateur
const existingContainer = document.querySelector(".latest-replies-container");
if (!existingContainer) {
let loadingIndicator = document.getElementById("latest-replies-loading");
if (!loadingIndicator) {
loadingIndicator = document.createElement("div");
loadingIndicator.id = "latest-replies-loading";
loadingIndicator.innerHTML = `
<div style="text-align: center; padding: 20px;">
<span class="spinner small"></span>
<span style="margin-left: 10px;">Chargement des commentaires récents...</span>
</div>
`;
container.parentNode.insertBefore(loadingIndicator, container.nextSibling);
}
}
}
// Récupère les données les plus récentes
fetch("/posts.json?order=created")
.then(res => res.json())
.then(data => {
// Journal pour le diagnostic
if (!silent) {
console.log("Données reçues de l'API :", data.latest_posts.length);
}
const replies = data.latest_posts
.filter(p => p.post_number > 1 && !p.topic_slug.includes("private-message"))
.slice(0, COMMENTS_TO_SHOW);
// Vérifie s'il y a de nouvelles publications
const maxId = replies.length > 0 ? Math.max(...replies.map(post => post.id)) : 0;
const hasNewPosts = maxId > lastSeenPostId;
// Journal pour le diagnostic
if (hasNewPosts && !silent) {
console.log(`Nouvelles publications détectées. Dernier ID : ${lastSeenPostId}, Nouveau maximum ID : ${maxId}`);
}
// Met à jour le dernier ID vu
if (maxId > lastSeenPostId) {
lastSeenPostId = maxId;
localStorage.setItem(CACHE_LAST_ID_KEY, lastSeenPostId.toString());
}
// S'il n'y a pas de nouvelles publications et que c'est une vérification silencieuse, ne fait rien
if (!hasNewPosts && silent && !forceRefresh) {
return;
}
// Récupère les détails des sujets
const topicPromises = replies.map(post => {
// Vérifie si nous avons le sujet en cache
const topicCacheKey = `discourse_topic_${post.topic_id}`;
const cachedTopic = localStorage.getItem(topicCacheKey);
if (cachedTopic && !forceRefresh) {
try {
return Promise.resolve(JSON.parse(cachedTopic));
} catch (e) {
console.error(`Erreur lors du traitement du cache du sujet ${post.topic_id}:`, e);
// En cas d'erreur dans le cache, nous récupérons depuis le serveur
}
}
return fetch(`/t/${post.topic_id}.json`)
.then(res => res.json())
.then(topic => {
// Stocke le sujet en cache
localStorage.setItem(topicCacheKey, JSON.stringify(topic));
return topic;
})
.catch(error => {
console.error(`Erreur lors de la récupération du sujet ${post.topic_id}:`, error);
return { category_id: null, category_name: null, tags: [] };
});
});
Promise.all(topicPromises)
.then(topics => {
const results = replies.map((post, index) => {
const topic = topics[index];
return {
post,
category: topic.category_id ? topic.category_name : null,
tags: topic.tags || []
};
});
// Stocke les résultats en cache
localStorage.setItem(CACHE_KEY, JSON.stringify(results));
localStorage.setItem(CACHE_TIMESTAMP_KEY, Date.now().toString());
// Affiche les résultats
renderLatestReplies(results, hasNewPosts || forceRefresh);
})
.catch(error => {
console.error("Erreur lors du traitement des sujets :", error);
});
})
.catch(error => {
console.error("Erreur lors de la récupération des derniers commentaires :", error);
// Supprime l'indicateur de chargement en cas d'erreur
if (!silent) {
const loadingElement = document.getElementById("latest-replies-loading");
if (loadingElement) loadingElement.remove();
}
});
}
// Fonction pour afficher les résultats
function renderLatestReplies(results, animate = false) {
// Supprime l'indicateur de chargement
const loadingElement = document.getElementById("latest-replies-loading");
if (loadingElement) loadingElement.remove();
// S'il n'y a pas de résultats, ne fait rien
if (!results || results.length === 0) {
console.log("Aucun commentaire trouvé à afficher");
return;
}
const rows = results.map(({ post, category, tags }) => {
const url = `/t/${post.topic_slug}/${post.topic_id}/${post.post_number}`;
const avatarUrl = post.avatar_template.replace("{size}", "45");
const excerpt = post.excerpt?.replace(/<\/?[^>]+(>|$)/g, "")?.slice(0, 120) + (post.excerpt?.length > 120 ? '...' : '') || '';
const categoryHtml = category
? `<span style="font-size: 0.85em; color: #666;">Catégorie : <strong>${category}</strong></span><br>`
: '';
const tagsHtml = tags.length
? `<span style="font-size: 0.85em; color: #666;">Tags : ${tags.map(tag => `<span style="background:#eee; padding:2px 6px; border-radius:3px; margin-right:4px;">${tag}</span>`).join("")}</span>`
: '';
const animationClass = animate ? 'new-comment' : '';
return `
<tr class="topic-list-item ${animationClass}" data-post-id="${post.id}">
<td class="main-link clearfix">
<div style="display: flex; align-items: center; gap: 16px; padding: 8px 0;">
<div style="flex-shrink: 0;">
<a class="avatar-link" href="/u/${post.username}">
<img loading="lazy" width="45" height="45" src="${avatarUrl}" class="avatar" alt="${post.username}">
</a>
</div>
<div style="display: flex; flex-direction: column; justify-content: center; padding-top: 8px; padding-bottom: 8px;">
<span class="link-top-line" style="margin-bottom: 6px;">
<a href="${url}" class="title raw-link">${excerpt}</a>
</span>
<div class="link-bottom-line">
${categoryHtml}
${tagsHtml}
</div>
</div>
</div>
</td>
</tr>
`;
}).join("");
// Ajoute le style d'animation s'il n'existe pas encore
if (!document.getElementById('latest-replies-style')) {
const style = document.createElement('style');
style.id = 'latest-replies-style';
style.textContent = `
@keyframes highlightNew {
0% { background-color: rgba(255, 255, 0, 0.3); }
100% { background-color: transparent; }
}
.new-comment {
animation: highlightNew 2s ease-out;
}
`;
document.head.appendChild(style);
}
// Supprime le conteneur existant, s'il y en a un
const existingContainer = document.querySelector(".latest-replies-container");
if (existingContainer) {
existingContainer.remove();
}
// Crée le conteneur de la section des derniers commentaires
const latestRepliesContainer = document.createElement("div");
latestRepliesContainer.className = "latest-replies-container";
latestRepliesContainer.style.marginTop = "2em";
// Ajoute le bouton de mise à jour manuelle et l'indicateur de statut
const cacheTime = new Date(parseInt(localStorage.getItem(CACHE_TIMESTAMP_KEY) || Date.now()));
const formattedTime = cacheTime.toLocaleTimeString();
latestRepliesContainer.innerHTML = `
<table class="topic-list latest-topic-list">
<thead>
<tr>
<th class="default">
Derniers Commentaires
<span id="comments-status" style="font-size: 0.8em; font-weight: normal; margin-left: 10px;">
</span>
<button id="refresh-comments" class="btn btn-flat no-text btn-icon" style="float: right;" title="Actualiser les commentaires">
<svg class="fa d-icon d-icon-sync svg-icon svg-string" width="16" height="16" aria-hidden="true"><use xlink:href="#sync"></use></svg>
</button>
</th>
</tr>
</thead>
<tbody id="latest-replies-tbody">
${rows}
</tbody>
</table>
`;
container.parentNode.insertBefore(latestRepliesContainer, container.nextSibling);
container.dataset.modified = "true";
// Ajoute l'événement de clic au bouton de mise à jour
document.getElementById("refresh-comments").addEventListener("click", function() {
// Met à jour le texte de statut
const statusElement = document.getElementById("comments-status");
if (statusElement) {
statusElement.textContent = "(actualisation...)";
}
// Force la mise à jour
loadLatestReplies(false, true);
});
console.log(`${results.length} commentaires affichés`);
}
// Démarre le chargement initial (en utilisant le cache)
loadLatestReplies(false, false);
// Configure le sondage haute fréquence
pollingIntervalId = setInterval(() => {
// Ne met à jour que si l'utilisateur est sur la page d'accueil
if (window.location.pathname === "/") {
loadLatestReplies(true, false); // Silencieux, utilise le cache si disponible
}
}, POLLING_INTERVAL);
console.log(`Sondage configuré toutes les ${POLLING_INTERVAL}ms`);
// Nettoie l'intervalle lorsque l'utilisateur quitte la page
api.onPageChange((url) => {
if (url !== "/") {
console.log("Quittant la page d'accueil, nettoyage des ressources");
if (pollingIntervalId) {
clearInterval(pollingIntervalId);
pollingIntervalId = null;
}
window.latestRepliesInitialized = false;
}
});
}
});
</script>
Cela fonctionne comme prévu. Il ajoute automatiquement les nouveaux commentaires, puis les ajoute au cache. Je sais que ce n’est pas l’idéal. Je vais bientôt essayer un plugin.
L’objectif ici est : au lieu d’une catégorie avec le style de page des derniers sujets, j’aimerais une catégorie avec les dernières réponses.