الردود الأخيرة تستغرق 2 ثانية لتظهر

هل هناك أي طريقة لعرض الردود الأخيرة على الفور؟ حاليًا، أستخدم هذا الرمز:

<معرف_نوع="text/discourse-plugin" الإصدار="0.11.3">
  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 && !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;">Categoría: <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>`
                : '';

              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.
يقوم باسترداد أحدث المشاركات ثم يقوم بطلب واحد لكل معرف موضوع (15 هنا) لاسترداد اسم الفئة.

في الوقت الحالي، لا أعرف ما إذا كانت هناك طريقة أخرى بخلاف استخدام إضافة وإجراء استعلام SQL مخصص، على سبيل المثال.

هذا صحيح تمامًا.
ولكن هل تقوم كتل الشريط الجانبي الأيمن بطلبات أيضًا؟ إنها تعطيني نفس النتائج للردود الأخيرة. يستغرق ظهورها ثانيتين.

سأرى كيفية إنشاء إضافة. شكراً لك.

نعم، يفعل الشيء نفسه في استرجاع أحدث المنشورات، لكن هذا هو كل شيء. لا يحاول الحصول على اسم التصنيف، هذا هو الفرق.

أنا أفهم. شكراً لك على المساعدة.

سأحاول أن أجد حلاً آخر

أخيرًا، سأستخدم هذا الحل مؤقتًا:

<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";
      
      // تخزين آخر معرف منشور تمت مشاهدته للمقارنة
      let lastSeenPostId = parseInt(localStorage.getItem(CACHE_LAST_ID_KEY) || "0");
      let pollingIntervalId = null;
      
      console.log(`تهيئة إضافة آخر التعليقات (آخر معرف في التخزين المؤقت: ${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(`تم اكتشاف منشورات جديدة. آخر معرف: ${lastSeenPostId}, أقصى معرف جديد: ${maxId}`);
            }
            
            // تحديث آخر معرف تمت مشاهدته
            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} مللي ثانية`);
      
      // تنظيف الفاصل الزمني عند خروج المستخدم من الصفحة
      api.onPageChange((url) => {
        if (url !== "/") {
          console.log("الخروج من الصفحة الرئيسية، تنظيف الموارد");
          
          if (pollingIntervalId) {
            clearInterval(pollingIntervalId);
            pollingIntervalId = null;
          }
          
          window.latestRepliesInitialized = false;
        }
      });
    }
  });
</script>

إنه يعمل كما هو متوقع. يقوم بإضافة التعليقات الجديدة تلقائياً، ثم يضيفها إلى التخزين المؤقت. أعرف أن هذا ليس الحل المثالي. سأحاول استخدام إضافة قريباً.

الهدف هنا هو: بدلاً من فئة بأسلوب صفحة أحدث المواضيع، أود أن يكون لدي فئة تحتوي على آخر الردود.