Las respuestas recientes tardan 2 segundos en mostrarse

¿Existe alguna forma de mostrar las últimas respuestas al instante? Actualmente, uso este código:

  api.onPageChange(() =e {
    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 =e res.json())
        .then(data =e {
          const replies = data.latest_posts
            .filter(p =e p.post_number e 1 66 !p.topic_slug.includes("private-message"))
            .slice(0, 15);

          const topicFetches = replies.map(post =e
            fetch(`/t/${post.topic_id}.json`)
              .then(res =e res.json())
              .then(topic =e {
                return {
                  post,
                  category: topic.category_id ? topic.category_name : null,
                  tags: topic.tags || []
                };
              })
          );

          Promise.all(topicFetches).then(results =e {
            const rows = results.map(({ post, category, tags }) =e {
              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(/c\/?[^e]+(e|$)/g, "")?.slice(0, 120) + (post.excerpt?.length e 120 ? '...' : '') || '';

              const categoryHtml = category
                ? `cspan style="font-size: 0.85em; color: #666;"eCategoria: cstronge${category}c/strongec/spanecbre`
                : '';

              const tagsHtml = tags.length
                ? `cspan style="font-size: 0.85em; color: #666;"eTags: ${tags.map(tag =e `cspan style="background:#eee; padding:2px 6px; border-radius:3px; margin-right:4px;"e${tag}c/spane`).join("")}c/spane`
                : '';

              return `
                ctr class="topic-list-item"e
                  ctd class="main-link clearfix"e
                    cdiv style="display: flex; align-items: center; gap: 16px; padding: 8px 0;"e
                      cdiv style="flex-shrink: 0;"e
                        ca class="avatar-link" href="/u/${post.username}"e
                          cimg loading="lazy" width="45" height="45" src="${avatarUrl}" class="avatar" alt="${post.username}"e
                        c/ae
                      c/dive
                      cdiv style="display: flex; flex-direction: column; justify-content: center; padding-top: 8px; padding-bottom: 8px;"e
                        cspan class="link-top-line" style="margin-bottom: 6px;"e
                          ca href="${url}" class="title raw-link"e${excerpt}c/ae
                        c/spane
                        cdiv class="link-bottom-line"e
                          ${categoryHtml}
                          ${tagsHtml}
                        c/dive
                      c/dive
                    c/dive
                  c/tde
                ctre
              `;
            }).join("");

            // Crea el contenedor de la sección de últimos comentarios
            const latestRepliesContainer = document.createElement("div");
            latestRepliesContainer.className = "latest-replies-container";
            latestRepliesContainer.style.marginTop = "2em";

            latestRepliesContainer.innerHTML = `
              ctable class="topic-list latest-topic-list"e
                ctheade
                  ctre
                    cth class="default"eÚltimos Comentariose
                  c/tre
                c/theade
                ctbodye
                  ${rows}
                /tbodye
              c/tablee
            `;

            container.parentNode.insertBefore(latestRepliesContainer, container.nextSibling);
            container.dataset.modified = "true";
          });
        })
        .catch(error =e {
          console.error("Error al buscar los últimos comentarios:", error);
        });
    }
  });
c/scriptpero tarda un promedio de 2 segundos en aparecer el contenido. Lo mismo sucede con los bloques de la barra lateral derecha con "respuestas recientes". ¿Es esto normal?

¿Por qué estás añadiendo algún código?

Esto funciona directamente sin nada especial; ¿te funciona mal?

Ese es su problema.

Yo diría que es esperado porque el código realiza varias solicitudes a la API.
Recupera las últimas publicaciones y luego realiza una solicitud por ID de tema (15 aquí) para recuperar el nombre de la categoría.

Por el momento, no sé si hay otra forma además de usar un plugin y hacer una consulta SQL personalizada, por ejemplo.

Exacto.
Pero los Bloques de Barra Lateral Derecha ¿también hacen solicitudes? Me da los mismos resultados para las respuestas recientes. Tarda 2 segundos en aparecer.

Veré cómo hacer un plugin. Gracias.

Sí, hace lo mismo al recuperar las últimas publicaciones, pero eso es todo. No intenta obtener el nombre de la categoría, esa es la diferencia.

Entiendo. Gracias por la ayuda.

Intentaré encontrar otra solución

finalmente, por ahora estoy usando esto:

<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;
      
      // Evita múltiples inicializaciones
      if (window.latestRepliesInitialized) return;
      window.latestRepliesInitialized = true;
      
      // Configuraciones
      const POLLING_INTERVAL = 2000; // 2 segundos
      const COMMENTS_TO_SHOW = 15;
      const CACHE_DURATION = 30 * 60 * 1000; // 30 minutos
      
      // Claves de caché
      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";
      
      // Almacena el último ID de publicación visto para comparación
      let lastSeenPostId = parseInt(localStorage.getItem(CACHE_LAST_ID_KEY) || "0");
      let pollingIntervalId = null;
      
      console.log(`Inicializando plugin de últimos comentarios (último ID en caché: ${lastSeenPostId})`);
      
      // Función para cargar los comentarios
      function loadLatestReplies(silent = false, forceRefresh = false) {
        // Verifica el caché primero, si no es una actualización forzada
        if (!forceRefresh) {
          const cachedData = localStorage.getItem(CACHE_KEY);
          const cacheTimestamp = localStorage.getItem(CACHE_TIMESTAMP_KEY);
          const now = Date.now();
          
          // Si tenemos datos en caché válidos
          if (cachedData && cacheTimestamp && now - parseInt(cacheTimestamp) < CACHE_DURATION) {
            try {
              const results = JSON.parse(cachedData);
              console.log(`Usando datos en caché (${results.length} comentarios, caché de ${Math.round((now - parseInt(cacheTimestamp)) / 1000 / 60)} minutos atrás)`);
              renderLatestReplies(results, false);
              
              // Si no es silencioso, no necesitamos hacer nada más
              if (!silent) {
                return;
              }
              
              // Si es silencioso, continuamos para verificar actualizaciones
            } catch (e) {
              console.error("Error al procesar caché:", e);
              // Si hay error en el caché, continuamos para buscar datos frescos
            }
          } else if (cachedData) {
            console.log("Caché expirado, buscando datos frescos");
          } else {
            console.log("Ningún caché encontrado, buscando datos frescos");
          }
        } else {
          console.log("Forzando actualización, ignorando caché");
        }
        
        // Si no es silencioso, muestra el indicador de carga
        if (!silent) {
          // Si ya existe un contenedor de comentarios, no muestra el indicador
          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;">Cargando comentarios recientes...</span>
                </div>
              `;
              container.parentNode.insertBefore(loadingIndicator, container.nextSibling);
            }
          }
        }
        
        // Busca los datos más recientes
        fetch("/posts.json?order=created")
          .then(res => res.json())
          .then(data => {
            // Log para diagnóstico
            if (!silent) {
              console.log("Datos recibidos de la 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);
            
            // Verifica si hay nuevos posts
            const maxId = replies.length > 0 ? Math.max(...replies.map(post => post.id)) : 0;
            const hasNewPosts = maxId > lastSeenPostId;
            
            // Log para diagnóstico
            if (hasNewPosts && !silent) {
              console.log(`Nuevos posts detectados. Último ID: ${lastSeenPostId}, Nuevo máximo ID: ${maxId}`);
            }
            
            // Actualiza el último ID visto
            if (maxId > lastSeenPostId) {
              lastSeenPostId = maxId;
              localStorage.setItem(CACHE_LAST_ID_KEY, lastSeenPostId.toString());
            }
            
            // Si no hay nuevos posts y es una verificación silenciosa, no hace nada
            if (!hasNewPosts && silent && !forceRefresh) {
              return;
            }
            
            // Busca los detalles de los temas
            const topicPromises = replies.map(post => {
              // Verifica si tenemos el tema en caché
              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(`Error al procesar caché del tema ${post.topic_id}:`, e);
                  // Si hay error en el caché, buscamos del servidor
                }
              }
              
              return fetch(`/t/${post.topic_id}.json`)
                .then(res => res.json())
                .then(topic => {
                  // Almacena el tema en caché
                  localStorage.setItem(topicCacheKey, JSON.stringify(topic));
                  return topic;
                })
                .catch(error => {
                  console.error(`Error al buscar tema ${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 || []
                  };
                });
                
                // Almacena los resultados en caché
                localStorage.setItem(CACHE_KEY, JSON.stringify(results));
                localStorage.setItem(CACHE_TIMESTAMP_KEY, Date.now().toString());
                
                // Renderiza los resultados
                renderLatestReplies(results, hasNewPosts || forceRefresh);
              })
              .catch(error => {
                console.error("Error al procesar temas:", error);
              });
          })
          .catch(error => {
            console.error("Error al buscar últimos comentarios:", error);
            // Elimina el indicador de carga en caso de error
            if (!silent) {
              const loadingElement = document.getElementById("latest-replies-loading");
              if (loadingElement) loadingElement.remove();
            }
          });
      }
      
      // Función para renderizar los resultados
      function renderLatestReplies(results, animate = false) {
        // Elimina el indicador de carga
        const loadingElement = document.getElementById("latest-replies-loading");
        if (loadingElement) loadingElement.remove();
        
        // Si no hay resultados, no hace nada
        if (!results || results.length === 0) {
          console.log("Ningún comentario encontrado para mostrar");
          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;">Categoría: <strong>${category}</strong></span><br>`
            : '';

          const tagsHtml = tags.length
            ? `<span style="font-size: 0.85em; color: #666;">Etiquetas: ${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("");

        // Añade el estilo de animación si aún no existe
        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);
        }

        // Elimina el contenedor existente, si lo hay
        const existingContainer = document.querySelector(".latest-replies-container");
        if (existingContainer) {
          existingContainer.remove();
        }

        // Crea el contenedor de la sección de últimos comentarios
        const latestRepliesContainer = document.createElement("div");
        latestRepliesContainer.className = "latest-replies-container";
        latestRepliesContainer.style.marginTop = "2em";

        // Añade botón de actualización manual e indicador de estado
        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">
                  Últimos Comentarios
                  <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="Actualizar comentarios">
                    <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";
        
        // Añade evento de clic al botón de actualización
        document.getElementById("refresh-comments").addEventListener("click", function() {
          // Actualiza el texto de estado
          const statusElement = document.getElementById("comments-status");
          if (statusElement) {
            statusElement.textContent = "(actualizando...)";
          }
          
          // Fuerza la actualización
          loadLatestReplies(false, true);
        });
        
        console.log(`Renderizados ${results.length} comentarios`);
      }
      
      // Inicia la carga inicial (usando caché)
      loadLatestReplies(false, false);
      
      // Configura el polling de alta frecuencia
      pollingIntervalId = setInterval(() => {
        // Solo actualiza si el usuario está en la página de inicio
        if (window.location.pathname === "/") {
          loadLatestReplies(true, false); // Silencioso, usa caché si está disponible
        }
      }, POLLING_INTERVAL);
      
      console.log(`Polling configurado cada ${POLLING_INTERVAL}ms`);
      
      // Limpia el intervalo cuando el usuario sale de la página
      api.onPageChange((url) => {
        if (url !== "/") {
          console.log("Saliendo de la página de inicio, limpiando recursos");
          
          if (pollingIntervalId) {
            clearInterval(pollingIntervalId);
            pollingIntervalId = null;
          }
          
          window.latestRepliesInitialized = false;
        }
      });
    }
  });
</script>

funciona como se espera. Agrega automáticamente los nuevos comentarios y luego los añade al caché. Sé que no es lo ideal. Pronto probaré un plugin.

el objetivo aquí es: en lugar de una categoría con el estilo de página de temas más recientes, me encantaría tener una categoría con las últimas respuestas.