現時点では、最新の返信を瞬時に表示する方法はありますか? 現在、以下のコードを使用しています:
api.onPageChange(() = {
if (window.location.pathname === "/") {
const container = document.querySelector(".latest-topic-list");
if (!container || container.dataset.modified === "true") return;
fetch("/posts.json?order=created")
.then(res = res.json())
.then(data = {
const replies = data.latest_posts
.filter(p = p.post_number 1 B p.topic_slug.includes("private-message"))
.slice(0, 15);
const topicFetches = replies.map(post =
fetch(`/t/${post.topic_id}.json`)
.then(res = res.json())
.then(topic = {
return {
post,
category: topic.category_id ? topic.category_name : null,
tags: topic.tags || []
};
})
);
Promise.all(topicFetches).then(results = {
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;"Categoria: strong${category}/strong/span
: '';
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`
: '';
return `
tr class="topic-list-item"
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("");
// セクションの最新コメントのコンテナを作成
const latestRepliesContainer = document.createElement("div");
latestRepliesContainer.className = "latest-replies-container";
latestRepliesContainer.style.marginTop = "2em";
latestRepliesContainer.innerHTML = `
table class="topic-list latest-topic-list"
thead
tr
th class="default"Últimos Comentários/th
/tr
/thead
tbody
${rows}
/tbody
/table
`;
container.parentNode.insertBefore(latestRepliesContainer, container.nextSibling);
container.dataset.modified = "true";
});
})
.catch(error = {
console.error("Erro ao buscar últimos comentários:", error);
});
}
});
/script
コンテンツが表示されるまでに平均2秒かかります。同じことが「最近の返信」が表示される右側のサイドバーのブロックにも起こります。これは正常ですか?
なぜ何かコードを追加しているのですか?
これは特別なものなしで最初から動作します。あなたにとって壊れていますか?
それが彼の問題です。
コードが複数のAPIリクエストを行っているため、これは予想されることだと思います。
最新の投稿を取得し、次にカテゴリ名を取得するためにトピックID(15個)ごとに1つのリクエストを行います。
現時点では、プラグインを使用してカスタムSQLクエリを実行する以外に方法があるかどうかはわかりません。
その通りです。
しかし、右サイドバーブロックもリクエストを行いますか?最近の返信で同じ結果が得られます。表示されるのに2秒かかります。
プラグインの作成方法を検討します。ありがとうございます。
はい、最新の投稿を取得することは同じですが、それだけです。カテゴリ名を取得しようとはしません。それが違いです。
ついに、今はこれで運用しています:
<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;
// 多重初期化を防止
if (window.latestRepliesInitialized) return;
window.latestRepliesInitialized = true;
// 設定
const POLLING_INTERVAL = 2000; // 2 秒
const COMMENTS_TO_SHOW = 15;
const CACHE_DURATION = 30 * 60 * 1000; // 30 分
// キャッシュキー
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";
// 比較用の最後の表示済み投稿 ID を保存
let lastSeenPostId = parseInt(localStorage.getItem(CACHE_LAST_ID_KEY) || "0");
let pollingIntervalId = null;
console.log(`最新コメントプラグインを初期化中(キャッシュ内の最終 ID: ${lastSeenPostId})`);
// コメントを読み込む関数
function loadLatestReplies(silent = false, forceRefresh = false) {
// 強制更新でない場合は、まずキャッシュを確認
if (!forceRefresh) {
const cachedData = localStorage.getItem(CACHE_KEY);
const cacheTimestamp = localStorage.getItem(CACHE_TIMESTAMP_KEY);
const now = Date.now();
// 有効なキャッシュデータがある場合
if (cachedData && cacheTimestamp && now - parseInt(cacheTimestamp) < CACHE_DURATION) {
try {
const results = JSON.parse(cachedData);
console.log(`キャッシュデータを使用中(${results.length}件のコメント、${Math.round((now - parseInt(cacheTimestamp)) / 1000 / 60)}分前のキャッシュ)`);
renderLatestReplies(results, false);
// 非表示モードの場合は、これ以上何もしない
if (!silent) {
return;
}
// 非表示モードの場合は、更新を確認するために続行
} catch (e) {
console.error("キャッシュ処理エラー:", e);
// キャッシュエラーの場合は、最新データを取得するために続行
}
} else if (cachedData) {
console.log("キャッシュが期限切れ、最新データを取得中");
} else {
console.log("キャッシュが見つからない、最新データを取得中");
}
} else {
console.log("更新を強制、キャッシュを無視");
}
// 非表示モードでない場合は、読み込みインジケーターを表示
if (!silent) {
// 既にコメントコンテナがある場合は、インジケーターを表示しない
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;">最近のコメントを読み込み中...</span>
</div>
`;
container.parentNode.insertBefore(loadingIndicator, container.nextSibling);
}
}
}
// 最新データを取得
fetch("/posts.json?order=created")
.then(res => res.json())
.then(data => {
// 診断用ログ
if (!silent) {
console.log("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);
// 新しい投稿があるか確認
const maxId = replies.length > 0 ? Math.max(...replies.map(post => post.id)) : 0;
const hasNewPosts = maxId > lastSeenPostId;
// 診断用ログ
if (hasNewPosts && !silent) {
console.log(`新しい投稿を検出。最終 ID: ${lastSeenPostId}, 新しい最大 ID: ${maxId}`);
}
// 最後の表示済み ID を更新
if (maxId > lastSeenPostId) {
lastSeenPostId = maxId;
localStorage.setItem(CACHE_LAST_ID_KEY, lastSeenPostId.toString());
}
// 新しい投稿がなく、非表示チェックかつ強制更新でない場合は何もしない
if (!hasNewPosts && silent && !forceRefresh) {
return;
}
// トピックの詳細を取得
const topicPromises = replies.map(post => {
// トピックがキャッシュされているか確認
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(`トピック ${post.topic_id} のキャッシュ処理エラー:`, e);
// キャッシュエラーの場合は、サーバーから取得
}
}
return fetch(`/t/${post.topic_id}.json`)
.then(res => res.json())
.then(topic => {
// トピックをキャッシュに保存
localStorage.setItem(topicCacheKey, JSON.stringify(topic));
return topic;
})
.catch(error => {
console.error(`トピック ${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 || []
};
});
// 結果をキャッシュに保存
localStorage.setItem(CACHE_KEY, JSON.stringify(results));
localStorage.setItem(CACHE_TIMESTAMP_KEY, Date.now().toString());
// 結果をレンダリング
renderLatestReplies(results, hasNewPosts || forceRefresh);
})
.catch(error => {
console.error("トピック処理エラー:", error);
});
})
.catch(error => {
console.error("最新コメントの取得エラー:", error);
// エラー発生時は読み込みインジケーターを削除
if (!silent) {
const loadingElement = document.getElementById("latest-replies-loading");
if (loadingElement) loadingElement.remove();
}
});
}
// 結果をレンダリングする関数
function renderLatestReplies(results, animate = false) {
// 読み込みインジケーターを削除
const loadingElement = document.getElementById("latest-replies-loading");
if (loadingElement) loadingElement.remove();
// 結果がない場合は何もしない
if (!results || results.length === 0) {
console.log("表示するコメントが見つかりません");
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;">カテゴリ: <strong>${category}</strong></span><br>`
: '';
const tagsHtml = tags.length
? `<span style="font-size: 0.85em; color: #666;">タグ: ${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("");
// 存在しない場合はアニメーションスタイルを追加
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);
}
// 既存のコンテナがあれば削除
const existingContainer = document.querySelector(".latest-replies-container");
if (existingContainer) {
existingContainer.remove();
}
// 最新コメントセクションのコンテナを作成
const latestRepliesContainer = document.createElement("div");
latestRepliesContainer.className = "latest-replies-container";
latestRepliesContainer.style.marginTop = "2em";
// 手動更新ボタンとステータスインジケーターを追加
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">
最新コメント
<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="コメントを更新">
<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";
// 更新ボタンにクリックイベントを追加
document.getElementById("refresh-comments").addEventListener("click", function() {
// ステータステキストを更新
const statusElement = document.getElementById("comments-status");
if (statusElement) {
statusElement.textContent = "(更新中...)";
}
// 更新を強制
loadLatestReplies(false, true);
});
console.log(`${results.length}件のコメントをレンダリングしました`);
}
// 初期読み込みを開始(キャッシュを使用)
loadLatestReplies(false, false);
// 高頻度ポーリングを設定
pollingIntervalId = setInterval(() => {
// ユーザーがホームページにいる場合のみ更新
if (window.location.pathname === "/") {
loadLatestReplies(true, false); // 非表示、キャッシュがあれば使用
}
}, POLLING_INTERVAL);
console.log(`ポーリングを ${POLLING_INTERVAL}ms ごとに設定`);
// ユーザーがページから離れるときにインターバルをクリア
api.onPageChange((url) => {
if (url !== "/") {
console.log("ホームページから離脱、リソースを解放");
if (pollingIntervalId) {
clearInterval(pollingIntervalId);
pollingIntervalId = null;
}
window.latestRepliesInitialized = false;
}
});
}
});
</script>
期待通りに動作しています。新しいコメントを自動的に追加し、その後キャッシュに追加します。理想的な方法ではありませんが、すぐにプラグイン化を試みる予定です。
ここでの目的は、最新のトピックページスタイルのカテゴリーではなく、最新の返信があるカテゴリーを実現することです。