Commit 747c3ee

HPCesia <me@hpcesia.com>
2025-09-03 10:09:04
fix: artalk recent comment
hide collapsed comments' content and delete invisible comments
1 parent 05e975c
Changed files (5)
packages
i18n
src
components
aside
recent-comments
packages/i18n/src/en/web/index.ts
@@ -65,6 +65,8 @@ const en_web = {
       image: '[Image]',
       link: '[Link]',
       code: '[Code]',
+      collapsed: '[Collapsed]',
+      pending: '[Pending]',
     },
     backLinks: 'Back Links',
     devNote:
packages/i18n/src/zh-CN/web/index.ts
@@ -65,6 +65,8 @@ const zh_CN_web = {
       image: '[图片]',
       link: '[链接]',
       code: '[代码]',
+      collapsed: '[已折叠]',
+      pending: '[待审]',
     },
     backLinks: '反向链接',
     devNote:
packages/i18n/src/zh-TW/web/index.ts
@@ -65,6 +65,8 @@ const zh_TW_web = {
       image: '[圖片]',
       link: '[連結]',
       code: '[程式碼]',
+      collapsed: '[已摺疊]',
+      pending: '[待審]',
     },
     backLinks: '反向連結',
     devNote:
packages/i18n/src/i18n-types.ts
@@ -387,6 +387,14 @@ export type NamespaceWebTranslation = {
 			 * [​C​o​d​e​]
 			 */
 			code: string
+			/**
+			 * [​C​o​l​l​a​p​s​e​d​]
+			 */
+			collapsed: string
+			/**
+			 * [​P​e​n​d​i​n​g​]
+			 */
+			pending: string
 		}
 		/**
 		 * B​a​c​k​ ​L​i​n​k​s
@@ -794,6 +802,14 @@ export type TranslationFunctions = {
 				 * [Code]
 				 */
 				code: () => LocalizedString
+				/**
+				 * [Collapsed]
+				 */
+				collapsed: () => LocalizedString
+				/**
+				 * [Pending]
+				 */
+				pending: () => LocalizedString
 			}
 			/**
 			 * Back Links
src/components/aside/recent-comments/Artalk.ts
@@ -1,54 +1,91 @@
 import type { CommentData, CommentProvider } from './types';
 import { cleanCommentHtml } from './utils';
 import { asideConfig, commentConfig } from '@/config';
+import { t } from '@utils/i18n';
 
 export const ArtalkProvider: CommentProvider = {
   async setup() {
     const artalkConfig = commentConfig.artalk!;
-    const commentCount = asideConfig.recentComment.count;
-
-    const apiCommentUrl = new URL(
-      `api/v2/stats/latest_comments?limit=${commentCount}`,
-      artalkConfig.serverURL
-    ).toString();
-    const apiConfigUrl = new URL(`api/v2/conf`, artalkConfig.serverURL).toString();
-
-    const responseComment = fetch(apiCommentUrl, {
-      method: 'GET',
-    });
-    const responseConfig = fetch(apiConfigUrl, {
-      method: 'GET',
-    });
-    if (!(await responseComment).ok) {
-      throw new Error('Failed to fetch recent comments');
-    }
-    if (!(await responseConfig).ok) {
+    const desiredCount = asideConfig.recentComment.count;
+
+    const apiBase = artalkConfig.serverURL;
+    const apiConfigUrl = new URL(`api/v2/conf`, apiBase).toString();
+
+    // fetch comment config first
+    const responseConfig = await fetch(apiConfigUrl, { method: 'GET' });
+    if (!responseConfig.ok) {
       throw new Error('Failed to fetch comment config');
     }
-
-    const commentData: {
-      id: number;
-      nick: string;
-      content_marked: string;
-      date: string;
-      email_encrypted: string;
-      page_url: string;
-    }[] = (await (await responseComment).json()).data;
     const configData: {
       gravatar: {
         mirror: string;
         params: string;
       };
-    } = (await (await responseConfig).json()).frontend_conf;
+    } = (await responseConfig.json()).frontend_conf;
 
     const getAvatarUrl = (email: string) => {
       return `${configData.gravatar.mirror}${email}?${configData.gravatar.params}`;
     };
 
-    return commentData.map(
+    // type for items returned by Artalk
+    type FetchItem = {
+      id: number;
+      nick: string;
+      content_marked: string;
+      date: string;
+      email_encrypted: string;
+      page_url: string;
+      is_collapsed: boolean;
+      visible: boolean;
+    }[];
+
+    const collected: FetchItem = [];
+    const seenIds = new Set<number>();
+    let attempt = 0;
+    let limit = desiredCount;
+    let prevCollectedCount = 0;
+
+    while (collected.length < desiredCount && attempt < 3) {
+      const apiCommentUrl = new URL(
+        `api/v2/stats/latest_comments?limit=${limit}`,
+        apiBase
+      ).toString();
+      const resp = await fetch(apiCommentUrl, { method: 'GET' });
+      if (!resp.ok) {
+        throw new Error('Failed to fetch recent comments');
+      }
+      const data: FetchItem = (await resp.json()).data || [];
+
+      let addedThisRound = 0;
+      for (const item of data) {
+        if (!item.visible) continue; // skip invisible comments
+        if (seenIds.has(item.id)) continue; // dedupe
+        collected.push(item);
+        seenIds.add(item.id);
+        addedThisRound++;
+        if (collected.length >= desiredCount) break;
+      }
+
+      // if increasing limit didn't yield any new visible comments, stop
+      if (addedThisRound === 0 && prevCollectedCount === collected.length) {
+        break;
+      }
+
+      prevCollectedCount = collected.length;
+      if (collected.length >= desiredCount) break;
+
+      // increase limit to try to fetch more next round
+      limit = limit + (desiredCount - collected.length);
+      attempt++;
+    }
+
+    // map to CommentData and return up to desiredCount
+    return collected.slice(0, desiredCount).map(
       (item): CommentData => ({
         avatarUrl: getAvatarUrl(item.email_encrypted),
-        commentContent: cleanCommentHtml(item.content_marked),
+        commentContent: item.is_collapsed
+          ? t.info.commentAbbrs.collapsed()
+          : cleanCommentHtml(item.content_marked),
         commentUrl: `${item.page_url}#atk-comment-${item.id}`,
         author: item.nick,
         time: new Date(item.date),