ランダムなトピックを読み込むカテゴリ?

Googleで検索してみましたが、関連する情報は見つかりませんでした。

いくつかのトピックは非常に価値があると思いますが、ユーザーは最新のものに焦点を当てる傾向があります(例えば、Google検索からフォーラムに来た場合を除いて)。

コンポーネント/プラグインなどを利用して、ランダムなトピックが自動的に読み込まれるカテゴリのようなものを設けることは可能でしょうか?これにより、3年前にそれほどエンゲージメントがなかったものの、依然として価値のある古い投稿が、より多くのユーザーがいる現在では当然受けるべき注目を集めることができるかもしれません。

明確にしておくと、ランダムなトピックを単一のカテゴリに限定したくはありません。それも良いかもしれませんが、私は、https://meta.discourse.org/のホームページにアクセスしたときに、カテゴリに関係なく最新のトピックのリストが表示されるのと同じように、*どの*カテゴリの*どの*トピックでもそのセクションに読み込むことができるグローバルな機能について考えていました。私の場合は、トピックの日付や最新の返信に焦点を当てるのではなく、ランダムなトピックもすべてのカテゴリから読み込むことになります。

意図が伝われば幸いです…

「いいね!」 2

ランダムなトピックのアイデアは好きですが、調整が必要だと思います。
私のフォーラムは20年以上続いており、10年以上前のランダムなトピックがたくさんあるページがあるのは奇妙でしょう。

エンゲージメントの低いトピックを対象としたい場合は、表示される最大返信数のようなパラメータが必要です。

それでも、エンゲージメントの低いトピックすべてが興味深いとは限りません。

特定の基準でトピックを対象としたいのですか、それとも単にランダムなものですか?

既存の最も近いものは、カテゴリ設定にあります。

これは、Support で(おそらくまだ)使用されていました。Support のトピックの理想的な状態は解決済み/クローズ済みです。ここでのオープンなトピックは、通常、解決策が提供されていないことを意味するため、エンゲージメントの低いトピックでも、数ヶ月または数年後であっても、検討される機会を与えます。それは常に価値がある可能性があります。

「いいね!」 1

ランダムなものです。

たとえば、特定のカテゴリや、ランダム化するボタンなどです。
あるいは、最新の投稿の下部にセクションがあり、古いランダムな投稿を3〜4件読み込むようなものではどうでしょうか?

20年以上前のフォーラムの投稿でも今日関連性があるかもしれませんが、作成当時は誰も注目しておらず、今「注目」されるべきかもしれません :wink:

「いいね!」 2

今日、Claudeにこれを手伝ってもらうことにしました。
たくさんのテストと修正を経て、私たちが思いついたのはこれです(私にとっては意味のある、OPENトピックのみを読み込みます):

「さらにランダムなトピックを読み込む」をクリックすると、現在のトピックの下にさらに5つのトピックが読み込まれます。ボタンを再度クリックすると、さらに5つ追加され、トピックがなくなるまで続きます:

ランダムなトピックを読み込むには、以下にアクセスしてください:
yourwebsite.com/?random


実装方法は次のとおりです:

1 - コンポーネントを作成します
2 - JSタブにこれを追加します:

import { apiInitializer } from "discourse/lib/api";
// URLに?randomが含まれている場合は、コンテンツをすぐに非表示にする
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) {
  // ボタンを非表示にし、読み込み中を表示
  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;
      }
      
      // すでに読み込まれたトピックを除外
      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;
      }
      
      // シャッフルして最大5つのランダムなトピックを選択
      const shuffled = unloadedTopics.sort(() => 0.5 - Math.random());
      const selected = shuffled.slice(0, Math.min(5, unloadedTopics.length));
      
      // 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.insertBefore(itemDiv, button);
      });
      
      // ロードされたすべての利用可能なトピックを確認
      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) {
  // 読み込まれたトピックをリセット
  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;
      }
      
      // シャッフルして5つのランダムなトピックを選択
      const shuffled = data.topics.sort(() => 0.5 - Math.random());
      const selected = shuffled.slice(0, 5);
      
      // HTMLを構築
      const listDiv = document.createElement('div');
      listDiv.className = 'random-topics-list';
      
      const heading = document.createElement('h2');
      heading.textContent = 'Random Open Topics';
      listDiv.appendChild(heading);
      
      // 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');
    
    // ランダムページにいたのに、そうではなくなった場合は強制的にリロードする
    if (wasOnRandomPage && !isOnRandomPage) {
      window.location.reload();
      return;
    }
    
    wasOnRandomPage = isOnRandomPage;
    
    if (!isOnRandomPage) {
      return;
    }
    
    // コンテンツを表示して置き換える
    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 - 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); }
}

誰か、HTMLカスタムページを使用する代わりに、通常のDiscourseビューを使用する方法を知っている人がいれば、それは素晴らしいことです!機能自体には満足していますが、誰かが方法を共有してくれれば、デフォルトのレイアウトになっている方が良いでしょう。