Schließlich verwende ich das vorerst:
<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;
// Vermeidet mehrfache Initialisierungen
if (window.latestRepliesInitialized) return;
window.latestRepliesInitialized = true;
// Konfigurationen
const POLLING_INTERVAL = 2000; // 2 Sekunden
const COMMENTS_TO_SHOW = 15;
const CACHE_DURATION = 30 * 60 * 1000; // 30 Minuten
// Cache-Schlüssel
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";
// Speichert die letzte gesehene Post-ID zum Vergleich
let lastSeenPostId = parseInt(localStorage.getItem(CACHE_LAST_ID_KEY) || "0");
let pollingIntervalId = null;
console.log(`Initialisiere Plugin für letzte Kommentare (letzte ID im Cache: ${lastSeenPostId})`);
// Funktion zum Laden der Kommentare
function loadLatestReplies(silent = false, forceRefresh = false) {
// Überprüft zuerst den Cache, wenn keine erzwungene Aktualisierung vorliegt
if (!forceRefresh) {
const cachedData = localStorage.getItem(CACHE_KEY);
const cacheTimestamp = localStorage.getItem(CACHE_TIMESTAMP_KEY);
const now = Date.now();
// Wenn gültige Daten im Cache vorhanden sind
if (cachedData && cacheTimestamp && now - parseInt(cacheTimestamp) < CACHE_DURATION) {
try {
const results = JSON.parse(cachedData);
console.log(`Verwende Daten aus dem Cache (${results.length} Kommentare, Cache von ${Math.round((now - parseInt(cacheTimestamp)) / 1000 / 60)} Minuten alt)`);
renderLatestReplies(results, false);
// Wenn nicht still, müssen wir nichts weiter tun
if (!silent) {
return;
}
// Wenn still, fahren wir fort, um Aktualisierungen zu prüfen
} catch (e) {
console.error("Fehler bei der Verarbeitung des Caches:", e);
// Bei einem Fehler im Cache fahren wir fort, um frische Daten abzurufen
}
} else if (cachedData) {
console.log("Cache abgelaufen, hole frische Daten");
} else {
console.log("Kein Cache gefunden, hole frische Daten");
}
} else {
console.log("Erzwinge Aktualisierung, ignoriere Cache");
}
// Wenn nicht still, zeige den Lade-Indikator
if (!silent) {
// Wenn bereits ein Container für Kommentare existiert, zeige keinen Indikator
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;">Lade letzte Kommentare...</span>
</div>
`;
container.parentNode.insertBefore(loadingIndicator, container.nextSibling);
}
}
}
// Hole die neuesten Daten
fetch("/posts.json?order=created")
.then(res => res.json())
.then(data => {
// Log für Diagnose
if (!silent) {
console.log("Daten von der API erhalten:", 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);
// Prüfe, ob es neue Posts gibt
const maxId = replies.length > 0 ? Math.max(...replies.map(post => post.id)) : 0;
const hasNewPosts = maxId > lastSeenPostId;
// Log für Diagnose
if (hasNewPosts && !silent) {
console.log(`Neue Posts erkannt. Letzte ID: ${lastSeenPostId}, Neue maximale ID: ${maxId}`);
}
// Aktualisiere die letzte gesehene ID
if (maxId > lastSeenPostId) {
lastSeenPostId = maxId;
localStorage.setItem(CACHE_LAST_ID_KEY, lastSeenPostId.toString());
}
// Wenn keine neuen Posts und es eine stille Prüfung ist, nichts tun
if (!hasNewPosts && silent && !forceRefresh) {
return;
}
// Hole die Details der Themen
const topicPromises = replies.map(post => {
// Prüfe, ob wir das Thema im Cache haben
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(`Fehler bei der Verarbeitung des Themas im Cache ${post.topic_id}:`, e);
// Bei einem Fehler im Cache holen wir es vom Server
}
}
return fetch(`/t/${post.topic_id}.json`)
.then(res => res.json())
.then(topic => {
// Speichere das Thema im Cache
localStorage.setItem(topicCacheKey, JSON.stringify(topic));
return topic;
})
.catch(error => {
console.error(`Fehler beim Abrufen des Themas ${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 || []
};
});
// Speichere die Ergebnisse im Cache
localStorage.setItem(CACHE_KEY, JSON.stringify(results));
localStorage.setItem(CACHE_TIMESTAMP_KEY, Date.now().toString());
// Rendere die Ergebnisse
renderLatestReplies(results, hasNewPosts || forceRefresh);
})
.catch(error => {
console.error("Fehler bei der Verarbeitung der Themen:", error);
});
})
.catch(error => {
console.error("Fehler beim Abrufen der letzten Kommentare:", error);
// Entferne den Lade-Indikator im Fehlerfall
if (!silent) {
const loadingElement = document.getElementById("latest-replies-loading");
if (loadingElement) loadingElement.remove();
}
});
}
// Funktion zum Rendern der Ergebnisse
function renderLatestReplies(results, animate = false) {
// Entferne den Lade-Indikator
const loadingElement = document.getElementById("latest-replies-loading");
if (loadingElement) loadingElement.remove();
// Wenn keine Ergebnisse, nichts tun
if (!results || results.length === 0) {
console.log("Keine Kommentare gefunden zum Anzeigen");
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;">Kategorie: <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("");
// Füge den Animationsstil hinzu, falls noch nicht vorhanden
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);
}
// Entferne den bestehenden Container, falls vorhanden
const existingContainer = document.querySelector(".latest-replies-container");
if (existingContainer) {
existingContainer.remove();
}
// Erstelle den Container für den Abschnitt der letzten Kommentare
const latestRepliesContainer = document.createElement("div");
latestRepliesContainer.className = "latest-replies-container";
latestRepliesContainer.style.marginTop = "2em";
// Füge Button für manuelle Aktualisierung und Status-Indikator hinzu
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">
Letzte Kommentare
<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="Kommentare aktualisieren">
<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";
// Füge Click-Event zum Aktualisierungs-Button hinzu
document.getElementById("refresh-comments").addEventListener("click", function() {
// Aktualisiere den Status-Text
const statusElement = document.getElementById("comments-status");
if (statusElement) {
statusElement.textContent = "(aktualisiere...)";
}
// Erzwingt die Aktualisierung
loadLatestReplies(false, true);
});
console.log(`Gerendert ${results.length} Kommentare`);
}
// Starte das initiale Laden (unter Verwendung des Caches)
loadLatestReplies(false, false);
// Konfiguriere das Polling mit hoher Frequenz
pollingIntervalId = setInterval(() => {
// Aktualisiere nur, wenn der Benutzer auf der Startseite ist
if (window.location.pathname === "/") {
loadLatestReplies(true, false); // Still, verwende Cache falls verfügbar
}
}, POLLING_INTERVAL);
console.log(`Polling alle ${POLLING_INTERVAL}ms konfiguriert`);
// Bereinige den Intervall, wenn der Benutzer die Seite verlässt
api.onPageChange((url) => {
if (url !== "/") {
console.log("Verlasse Startseite, bereinige Ressourcen");
if (pollingIntervalId) {
clearInterval(pollingIntervalId);
pollingIntervalId = null;
}
window.latestRepliesInitialized = false;
}
});
}
});
</script>
es funktioniert wie erwartet. Es fügt automatisch neue Kommentare hinzu und speichert sie im Cache. Ich weiß, dass das nicht ideal ist. Ich werde bald einen Plugin versuchen.
Das Ziel hier ist: Anstatt einer Kategorie mit dem Stil der Seite „Neueste Themen“, würde ich gerne eine Kategorie mit den „Neuesten Antworten“ haben.