From 0ddb22f5fa14235d66cf24dffe569a5a7f47602e Mon Sep 17 00:00:00 2001 From: plebeius Date: Tue, 24 Feb 2026 18:15:50 +0800 Subject: [PATCH] feat(thread-stats): show board page number in post stats via cache-first lookup --- public/translations/ar/default.json | 3 +- public/translations/bn/default.json | 3 +- public/translations/cs/default.json | 3 +- public/translations/da/default.json | 3 +- public/translations/de/default.json | 3 +- public/translations/el/default.json | 3 +- public/translations/en/default.json | 3 +- public/translations/es/default.json | 3 +- public/translations/fa/default.json | 3 +- public/translations/fi/default.json | 3 +- public/translations/fil/default.json | 3 +- public/translations/fr/default.json | 3 +- public/translations/he/default.json | 3 +- public/translations/hi/default.json | 3 +- public/translations/hu/default.json | 3 +- public/translations/id/default.json | 3 +- public/translations/it/default.json | 3 +- public/translations/ja/default.json | 3 +- public/translations/ko/default.json | 3 +- public/translations/mr/default.json | 3 +- public/translations/nl/default.json | 3 +- public/translations/no/default.json | 3 +- public/translations/pl/default.json | 3 +- public/translations/pt/default.json | 3 +- public/translations/ro/default.json | 3 +- public/translations/ru/default.json | 3 +- public/translations/sq/default.json | 3 +- public/translations/sv/default.json | 3 +- public/translations/te/default.json | 3 +- public/translations/th/default.json | 3 +- public/translations/tr/default.json | 3 +- public/translations/uk/default.json | 3 +- public/translations/ur/default.json | 3 +- public/translations/vi/default.json | 3 +- public/translations/zh/default.json | 3 +- .../board-buttons/board-buttons.tsx | 18 ++++ src/hooks/use-post-page-number.ts | 55 +++++++++++ src/lib/utils/post-page-resolution.ts | 96 +++++++++++++++++++ 38 files changed, 239 insertions(+), 35 deletions(-) create mode 100644 src/hooks/use-post-page-number.ts create mode 100644 src/lib/utils/post-page-resolution.ts diff --git a/public/translations/ar/default.json b/public/translations/ar/default.json index 3af1fe11..7db592cb 100644 --- a/public/translations/ar/default.json +++ b/public/translations/ar/default.json @@ -281,5 +281,6 @@ "show_blotter": "إظهار لوحة الإعلانات", "top": "أعلى", "reply_count": "عدد الردود", - "pagination.pageCurrent": "الصفحة {{page}} (الحالية)" + "pagination.pageCurrent": "الصفحة {{page}} (الحالية)", + "pagination.pageLabel": "صفحة" } diff --git a/public/translations/bn/default.json b/public/translations/bn/default.json index 6d6a64ce..8d2567cd 100644 --- a/public/translations/bn/default.json +++ b/public/translations/bn/default.json @@ -281,5 +281,6 @@ "show_blotter": "ঘোষণা দেখান", "top": "শীর্ষ", "reply_count": "উত্তরসংখ্যা", - "pagination.pageCurrent": "পৃষ্ঠা {{page}} (বর্তমান)" + "pagination.pageCurrent": "পৃষ্ঠা {{page}} (বর্তমান)", + "pagination.pageLabel": "পৃষ্ঠা" } diff --git a/public/translations/cs/default.json b/public/translations/cs/default.json index 3ce2cb71..60df989d 100644 --- a/public/translations/cs/default.json +++ b/public/translations/cs/default.json @@ -281,5 +281,6 @@ "show_blotter": "Zobrazit Oznámení", "top": "Nahoru", "reply_count": "Počet odpovědí", - "pagination.pageCurrent": "Stránka {{page}} (aktuální)" + "pagination.pageCurrent": "Stránka {{page}} (aktuální)", + "pagination.pageLabel": "Stránka" } diff --git a/public/translations/da/default.json b/public/translations/da/default.json index 061d5b0d..8660dcd6 100644 --- a/public/translations/da/default.json +++ b/public/translations/da/default.json @@ -281,5 +281,6 @@ "show_blotter": "Vis Meddelelser", "top": "Top", "reply_count": "Antal svar", - "pagination.pageCurrent": "Side {{page}} (nuværende)" + "pagination.pageCurrent": "Side {{page}} (nuværende)", + "pagination.pageLabel": "Side" } diff --git a/public/translations/de/default.json b/public/translations/de/default.json index c0d5ecd6..5c02a88b 100644 --- a/public/translations/de/default.json +++ b/public/translations/de/default.json @@ -281,5 +281,6 @@ "show_blotter": "Ankündigungen anzeigen", "top": "Oben", "reply_count": "Anzahl Antworten", - "pagination.pageCurrent": "Seite {{page}} (aktuell)" + "pagination.pageCurrent": "Seite {{page}} (aktuell)", + "pagination.pageLabel": "Seite" } diff --git a/public/translations/el/default.json b/public/translations/el/default.json index 2aa1602a..23de1028 100644 --- a/public/translations/el/default.json +++ b/public/translations/el/default.json @@ -281,5 +281,6 @@ "show_blotter": "Εμφάνιση Ανακοινώσεων", "top": "Πάνω", "reply_count": "Αριθμός απαντήσεων", - "pagination.pageCurrent": "Σελίδα {{page}} (τρέχουσα)" + "pagination.pageCurrent": "Σελίδα {{page}} (τρέχουσα)", + "pagination.pageLabel": "Σελίδα" } diff --git a/public/translations/en/default.json b/public/translations/en/default.json index d8d555a4..3baa4935 100644 --- a/public/translations/en/default.json +++ b/public/translations/en/default.json @@ -282,5 +282,6 @@ "message": "Message", "show_all": "Show All", "show_blotter": "Show Blotter", - "reply_count": "Reply count" + "reply_count": "Reply count", + "pagination.pageLabel": "Page" } diff --git a/public/translations/es/default.json b/public/translations/es/default.json index 052a9dad..23d0006b 100644 --- a/public/translations/es/default.json +++ b/public/translations/es/default.json @@ -281,5 +281,6 @@ "show_blotter": "Mostrar Anuncios", "top": "Arriba", "reply_count": "Número de respuestas", - "pagination.pageCurrent": "Página {{page}} (actual)" + "pagination.pageCurrent": "Página {{page}} (actual)", + "pagination.pageLabel": "Página" } diff --git a/public/translations/fa/default.json b/public/translations/fa/default.json index 812d54b3..92cb5249 100644 --- a/public/translations/fa/default.json +++ b/public/translations/fa/default.json @@ -281,5 +281,6 @@ "show_blotter": "نمایش اعلانات", "top": "بالا", "reply_count": "تعداد پاسخ‌ها", - "pagination.pageCurrent": "صفحه {{page}} (فعلی)" + "pagination.pageCurrent": "صفحه {{page}} (فعلی)", + "pagination.pageLabel": "صفحه" } diff --git a/public/translations/fi/default.json b/public/translations/fi/default.json index ab291de3..f6caa509 100644 --- a/public/translations/fi/default.json +++ b/public/translations/fi/default.json @@ -281,5 +281,6 @@ "show_blotter": "Näytä ilmoitukset", "top": "Ylös", "reply_count": "Vastausten määrä", - "pagination.pageCurrent": "Sivu {{page}} (nykyinen)" + "pagination.pageCurrent": "Sivu {{page}} (nykyinen)", + "pagination.pageLabel": "Sivu" } diff --git a/public/translations/fil/default.json b/public/translations/fil/default.json index 097ea00d..f43fd13c 100644 --- a/public/translations/fil/default.json +++ b/public/translations/fil/default.json @@ -281,5 +281,6 @@ "show_blotter": "Ipakita ang Mga anunsyo", "top": "Itaas", "reply_count": "Bilang ng mga tugon", - "pagination.pageCurrent": "Pahina {{page}} (kasalukuyan)" + "pagination.pageCurrent": "Pahina {{page}} (kasalukuyan)", + "pagination.pageLabel": "Pahina" } diff --git a/public/translations/fr/default.json b/public/translations/fr/default.json index 958f5b12..63f4e702 100644 --- a/public/translations/fr/default.json +++ b/public/translations/fr/default.json @@ -281,5 +281,6 @@ "show_blotter": "Afficher les annonces", "top": "Haut", "reply_count": "Nombre de réponses", - "pagination.pageCurrent": "Page {{page}} (actuelle)" + "pagination.pageCurrent": "Page {{page}} (actuelle)", + "pagination.pageLabel": "Page" } diff --git a/public/translations/he/default.json b/public/translations/he/default.json index ce6e8cd1..2c158e69 100644 --- a/public/translations/he/default.json +++ b/public/translations/he/default.json @@ -281,5 +281,6 @@ "show_blotter": "הצג הודעות", "top": "למעלה", "reply_count": "מספר תגובות", - "pagination.pageCurrent": "עמוד {{page}} (נוכחי)" + "pagination.pageCurrent": "עמוד {{page}} (נוכחי)", + "pagination.pageLabel": "עמוד" } diff --git a/public/translations/hi/default.json b/public/translations/hi/default.json index df5f768c..7645a0bb 100644 --- a/public/translations/hi/default.json +++ b/public/translations/hi/default.json @@ -281,5 +281,6 @@ "show_blotter": "सूचनाएं दिखाएं", "top": "शीर्ष", "reply_count": "जवाबों की संख्या", - "pagination.pageCurrent": "पृष्ठ {{page}} (वर्तमान)" + "pagination.pageCurrent": "पृष्ठ {{page}} (वर्तमान)", + "pagination.pageLabel": "पृष्ठ" } diff --git a/public/translations/hu/default.json b/public/translations/hu/default.json index 8e94f1ed..2921a1ac 100644 --- a/public/translations/hu/default.json +++ b/public/translations/hu/default.json @@ -281,5 +281,6 @@ "show_blotter": "Közlemények megjelenítése", "top": "Felül", "reply_count": "Válaszok száma", - "pagination.pageCurrent": "{{page}}. oldal (jelenlegi)" + "pagination.pageCurrent": "{{page}}. oldal (jelenlegi)", + "pagination.pageLabel": "Oldal" } diff --git a/public/translations/id/default.json b/public/translations/id/default.json index d0825594..d6412bf2 100644 --- a/public/translations/id/default.json +++ b/public/translations/id/default.json @@ -281,5 +281,6 @@ "show_blotter": "Tampilkan Pengumuman", "top": "Atas", "reply_count": "Jumlah balasan", - "pagination.pageCurrent": "Halaman {{page}} (saat ini)" + "pagination.pageCurrent": "Halaman {{page}} (saat ini)", + "pagination.pageLabel": "Halaman" } diff --git a/public/translations/it/default.json b/public/translations/it/default.json index e2bfeeb4..bfd1183a 100644 --- a/public/translations/it/default.json +++ b/public/translations/it/default.json @@ -281,5 +281,6 @@ "show_blotter": "Mostra annunci", "top": "In alto", "reply_count": "Numero di risposte", - "pagination.pageCurrent": "Pagina {{page}} (corrente)" + "pagination.pageCurrent": "Pagina {{page}} (corrente)", + "pagination.pageLabel": "Pagina" } diff --git a/public/translations/ja/default.json b/public/translations/ja/default.json index 90dc7c0c..4cb4724e 100644 --- a/public/translations/ja/default.json +++ b/public/translations/ja/default.json @@ -281,5 +281,6 @@ "show_blotter": "お知らせを表示", "top": "上", "reply_count": "返信数", - "pagination.pageCurrent": "{{page}}ページ(現在)" + "pagination.pageCurrent": "{{page}}ページ(現在)", + "pagination.pageLabel": "ページ" } diff --git a/public/translations/ko/default.json b/public/translations/ko/default.json index a27f8e6e..94532d80 100644 --- a/public/translations/ko/default.json +++ b/public/translations/ko/default.json @@ -281,5 +281,6 @@ "show_blotter": "공지 표시", "top": "맨 위", "reply_count": "답글 수", - "pagination.pageCurrent": "{{page}}페이지 (현재)" + "pagination.pageCurrent": "{{page}}페이지 (현재)", + "pagination.pageLabel": "페이지" } diff --git a/public/translations/mr/default.json b/public/translations/mr/default.json index cef0bb8c..5724e3af 100644 --- a/public/translations/mr/default.json +++ b/public/translations/mr/default.json @@ -281,5 +281,6 @@ "show_blotter": "जाहिराती दाखवा", "top": "वर", "reply_count": "प्रत्युत्तरांची संख्या", - "pagination.pageCurrent": "पृष्ठ {{page}} (वर्तमान)" + "pagination.pageCurrent": "पृष्ठ {{page}} (वर्तमान)", + "pagination.pageLabel": "पृष्ठ" } diff --git a/public/translations/nl/default.json b/public/translations/nl/default.json index aaaa5392..64b1c0d9 100644 --- a/public/translations/nl/default.json +++ b/public/translations/nl/default.json @@ -281,5 +281,6 @@ "show_blotter": "Mededelingen tonen", "top": "Boven", "reply_count": "Aantal reacties", - "pagination.pageCurrent": "Pagina {{page}} (huidige)" + "pagination.pageCurrent": "Pagina {{page}} (huidige)", + "pagination.pageLabel": "Pagina" } diff --git a/public/translations/no/default.json b/public/translations/no/default.json index 49d3a8da..3afadad9 100644 --- a/public/translations/no/default.json +++ b/public/translations/no/default.json @@ -281,5 +281,6 @@ "show_blotter": "Vis Kunngjøringer", "top": "Topp", "reply_count": "Antall svar", - "pagination.pageCurrent": "Side {{page}} (nåværende)" + "pagination.pageCurrent": "Side {{page}} (nåværende)", + "pagination.pageLabel": "Side" } diff --git a/public/translations/pl/default.json b/public/translations/pl/default.json index 48d3807d..0701b1ef 100644 --- a/public/translations/pl/default.json +++ b/public/translations/pl/default.json @@ -281,5 +281,6 @@ "show_blotter": "Pokaż Ogłoszenia", "top": "Góra", "reply_count": "Liczba odpowiedzi", - "pagination.pageCurrent": "Strona {{page}} (bieżąca)" + "pagination.pageCurrent": "Strona {{page}} (bieżąca)", + "pagination.pageLabel": "Strona" } diff --git a/public/translations/pt/default.json b/public/translations/pt/default.json index 5bd352ae..16cf2b2c 100644 --- a/public/translations/pt/default.json +++ b/public/translations/pt/default.json @@ -281,5 +281,6 @@ "show_blotter": "Mostrar Anúncios", "top": "Topo", "reply_count": "Número de respostas", - "pagination.pageCurrent": "Página {{page}} (atual)" + "pagination.pageCurrent": "Página {{page}} (atual)", + "pagination.pageLabel": "Página" } diff --git a/public/translations/ro/default.json b/public/translations/ro/default.json index 203d3469..b1a31d88 100644 --- a/public/translations/ro/default.json +++ b/public/translations/ro/default.json @@ -281,5 +281,6 @@ "show_blotter": "Afișează Blotter", "top": "Sus", "reply_count": "Număr de răspunsuri", - "pagination.pageCurrent": "Pagina {{page}} (curentă)" + "pagination.pageCurrent": "Pagina {{page}} (curentă)", + "pagination.pageLabel": "Pagină" } diff --git a/public/translations/ru/default.json b/public/translations/ru/default.json index 05c47457..89425173 100644 --- a/public/translations/ru/default.json +++ b/public/translations/ru/default.json @@ -281,5 +281,6 @@ "show_blotter": "Показать объявления", "top": "Вверх", "reply_count": "Количество ответов", - "pagination.pageCurrent": "Страница {{page}} (текущая)" + "pagination.pageCurrent": "Страница {{page}} (текущая)", + "pagination.pageLabel": "Страница" } diff --git a/public/translations/sq/default.json b/public/translations/sq/default.json index 04e92744..a23fd440 100644 --- a/public/translations/sq/default.json +++ b/public/translations/sq/default.json @@ -281,5 +281,6 @@ "show_blotter": "Shfaq Njoftimet", "top": "Lart", "reply_count": "Numri i përgjigjeve", - "pagination.pageCurrent": "Faqja {{page}} (aktuale)" + "pagination.pageCurrent": "Faqja {{page}} (aktuale)", + "pagination.pageLabel": "Faqe" } diff --git a/public/translations/sv/default.json b/public/translations/sv/default.json index dab86a31..4db7e962 100644 --- a/public/translations/sv/default.json +++ b/public/translations/sv/default.json @@ -281,5 +281,6 @@ "show_blotter": "Visa Meddelanden", "top": "Topp", "reply_count": "Antal svar", - "pagination.pageCurrent": "Sida {{page}} (nuvarande)" + "pagination.pageCurrent": "Sida {{page}} (nuvarande)", + "pagination.pageLabel": "Sida" } diff --git a/public/translations/te/default.json b/public/translations/te/default.json index 04667948..3bf1484e 100644 --- a/public/translations/te/default.json +++ b/public/translations/te/default.json @@ -281,5 +281,6 @@ "show_blotter": "ప్రకటనలు చూపించు", "top": "పై", "reply_count": "సమాధానాల సంఖ్య", - "pagination.pageCurrent": "పేజీ {{page}} (ప్రస్తుత)" + "pagination.pageCurrent": "పేజీ {{page}} (ప్రస్తుత)", + "pagination.pageLabel": "పేజీ" } diff --git a/public/translations/th/default.json b/public/translations/th/default.json index 7647eba8..00a0760b 100644 --- a/public/translations/th/default.json +++ b/public/translations/th/default.json @@ -281,5 +281,6 @@ "show_blotter": "แสดงประกาศ", "top": "บน", "reply_count": "จำนวนการตอบกลับ", - "pagination.pageCurrent": "หน้า {{page}} (ปัจจุบัน)" + "pagination.pageCurrent": "หน้า {{page}} (ปัจจุบัน)", + "pagination.pageLabel": "หน้า" } diff --git a/public/translations/tr/default.json b/public/translations/tr/default.json index 2aeb9962..de966b9e 100644 --- a/public/translations/tr/default.json +++ b/public/translations/tr/default.json @@ -281,5 +281,6 @@ "show_blotter": "Duyuruları Göster", "top": "Üst", "reply_count": "Yanıt sayısı", - "pagination.pageCurrent": "Sayfa {{page}} (mevcut)" + "pagination.pageCurrent": "Sayfa {{page}} (mevcut)", + "pagination.pageLabel": "Sayfa" } diff --git a/public/translations/uk/default.json b/public/translations/uk/default.json index f5a2587b..9b352716 100644 --- a/public/translations/uk/default.json +++ b/public/translations/uk/default.json @@ -281,5 +281,6 @@ "show_blotter": "Показати оголошення", "top": "Вгору", "reply_count": "Кількість відповідей", - "pagination.pageCurrent": "Сторінка {{page}} (поточна)" + "pagination.pageCurrent": "Сторінка {{page}} (поточна)", + "pagination.pageLabel": "Сторінка" } diff --git a/public/translations/ur/default.json b/public/translations/ur/default.json index f26db717..62437cfa 100644 --- a/public/translations/ur/default.json +++ b/public/translations/ur/default.json @@ -281,5 +281,6 @@ "show_blotter": "اعلانات دکھائیں", "top": "اوپر", "reply_count": "جوابوں کی تعداد", - "pagination.pageCurrent": "صفحہ {{page}} (موجودہ)" + "pagination.pageCurrent": "صفحہ {{page}} (موجودہ)", + "pagination.pageLabel": "صفحہ" } diff --git a/public/translations/vi/default.json b/public/translations/vi/default.json index ddd559eb..7ad325c6 100644 --- a/public/translations/vi/default.json +++ b/public/translations/vi/default.json @@ -281,5 +281,6 @@ "show_blotter": "Hiển thị Thông báo", "top": "Trên", "reply_count": "Số lượng phản hồi", - "pagination.pageCurrent": "Trang {{page}} (hiện tại)" + "pagination.pageCurrent": "Trang {{page}} (hiện tại)", + "pagination.pageLabel": "Trang" } diff --git a/public/translations/zh/default.json b/public/translations/zh/default.json index e599aa50..37f032be 100644 --- a/public/translations/zh/default.json +++ b/public/translations/zh/default.json @@ -281,5 +281,6 @@ "show_blotter": "显示公告", "top": "顶部", "reply_count": "回复数", - "pagination.pageCurrent": "第{{page}}页(当前)" + "pagination.pageCurrent": "第{{page}}页(当前)", + "pagination.pageLabel": "页" } diff --git a/src/components/board-buttons/board-buttons.tsx b/src/components/board-buttons/board-buttons.tsx index f30397bf..de58c127 100644 --- a/src/components/board-buttons/board-buttons.tsx +++ b/src/components/board-buttons/board-buttons.tsx @@ -2,6 +2,7 @@ import { useTranslation } from 'react-i18next'; import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'; import { useAccountComment, useComment, useSubscribe } from '@plebbit/plebbit-react-hooks'; import { isAllView, isCatalogView, isModView, isModQueueView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; +import { usePostPageNumber } from '../../hooks/use-post-page-number'; import { useDirectories } from '../../hooks/use-directories'; import { getBoardPath, isDirectoryBoard } from '../../lib/utils/route-utils'; import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address'; @@ -457,7 +458,11 @@ export const MobileBoardButtons = () => { export const PostPageStats = () => { const { t } = useTranslation(); const params = useParams(); + const location = useLocation(); const commentCid = params?.commentCid as string | undefined; + const resolvedAddress = useResolvedSubplebbitAddress(); + const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any }); + const subplebbitAddress = resolvedAddress || accountComment?.subplebbitAddress; const comment = useComment({ commentCid }); const postCid = comment?.parentCid ?? commentCid; @@ -466,6 +471,13 @@ export const PostPageStats = () => { const { closed, pinned, replyCount } = post || {}; const linkCount = useCountLinksInReplies(post); + const isThreadView = isPostPageView(location.pathname, params); + const pageNumber = usePostPageNumber({ + subplebbitAddress, + postCid, + enabled: isThreadView, + }); + const displayReplyCount = replyCount !== undefined ? replyCount.toString() : '?'; const replyCountTooltip = replyCount !== undefined ? capitalize(t('replies')) : t('loading'); @@ -474,6 +486,12 @@ export const PostPageStats = () => { {pinned && `${capitalize(t('sticky'))} / `} {closed && `${capitalize(t('closed'))} / `} {displayReplyCount} / {linkCount?.toString()} + {isThreadView && ( + <> + {' '} + / {pageNumber?.toString() ?? '?'} + + )} ); }; diff --git a/src/hooks/use-post-page-number.ts b/src/hooks/use-post-page-number.ts new file mode 100644 index 00000000..614d5cd8 --- /dev/null +++ b/src/hooks/use-post-page-number.ts @@ -0,0 +1,55 @@ +import { useMemo } from 'react'; +import { useFeed } from '@plebbit/plebbit-react-hooks'; +import useFeedsStore from '@plebbit/plebbit-react-hooks/dist/stores/feeds'; +import { useDirectoryByAddress } from './use-directories'; +import { useBoardFeedPageSize } from './use-board-feed-page-size'; +import { findPostPageInFeed, findPostPageInLoadedBoardFeeds, type FeedsOptionsLike, type LoadedFeedsLike } from '../lib/utils/post-page-resolution'; + +export interface UsePostPageNumberOptions { + subplebbitAddress: string | undefined; + postCid: string | undefined; + /** When false, page segment is excluded (e.g. pending-post view). When true, resolve and show page. */ + enabled?: boolean; +} + +/** + * Resolve the board pagination page (1-based) for a post in a thread view. + * Uses cache-first lookup from feeds store, then preloads via useFeed if miss. + * Preload depth is capped to board pagination window (10 pages via paginationFeedPostsPerPage). + * + * @returns 1-based page number, or undefined when unresolved (render as "?") + */ +export function usePostPageNumber({ subplebbitAddress, postCid, enabled = true }: UsePostPageNumberOptions): number | undefined { + const community = useDirectoryByAddress(subplebbitAddress); + const { guiPostsPerPage, paginationFeedPostsPerPage } = useBoardFeedPageSize(community); + + const canResolve = Boolean(enabled && subplebbitAddress && postCid && guiPostsPerPage > 0); + + // Cache-first: selector returns only computed page to minimize rerenders + const cachedPage = useFeedsStore((state) => { + if (!canResolve) return undefined; + return findPostPageInLoadedBoardFeeds(state.feedsOptions as FeedsOptionsLike, state.loadedFeeds as LoadedFeedsLike, subplebbitAddress!, postCid!, guiPostsPerPage); + }); + + // Preload when cache miss and enabled (10 GUI pages) + const preloadOptions = useMemo( + () => + canResolve + ? { + subplebbitAddresses: [subplebbitAddress!], + sortType: 'active' as const, + postsPerPage: paginationFeedPostsPerPage, + } + : undefined, + [canResolve, subplebbitAddress, paginationFeedPostsPerPage], + ); + + const { feed: preloadFeed } = useFeed(preloadOptions); + + const preloadedPage = useMemo(() => { + if (!canResolve || !preloadFeed?.length) return undefined; + return findPostPageInFeed(preloadFeed, postCid!, guiPostsPerPage); + }, [canResolve, postCid, preloadFeed, guiPostsPerPage]); + + return cachedPage ?? preloadedPage; +} diff --git a/src/lib/utils/post-page-resolution.ts b/src/lib/utils/post-page-resolution.ts new file mode 100644 index 00000000..3b90be39 --- /dev/null +++ b/src/lib/utils/post-page-resolution.ts @@ -0,0 +1,96 @@ +/** + * Post page resolution utilities. + * Page semantics are board pagination pages (not catalog ordering, not fetch-chunk index). + */ + +/** Minimal Comment shape with cid */ +export interface CommentWithCid { + cid?: string; + [key: string]: unknown; +} + +/** Minimal FeedOptions shape for board-feed filtering */ +export interface FeedOptionsLike { + subplebbitAddresses: string[]; + sortType: string; + postsPerPage?: number; + filter?: unknown; + newerThan?: number; + modQueue?: unknown; + accountComments?: unknown; +} + +/** FeedsOptions-like map */ +export type FeedsOptionsLike = Record; + +/** Loaded feeds map: feedName -> Comment[] */ +export type LoadedFeedsLike = Record; + +/** + * Find the board pagination page (1-based) containing postCid in a feed. + * Pure index math: page = floor(index / guiPostsPerPage) + 1. + * + * @param feed - Array of comments (posts) in feed order + * @param postCid - CID of the post (OP) to locate + * @param guiPostsPerPage - Posts per GUI page + * @returns 1-based page number, or undefined if post not found + */ +export function findPostPageInFeed(feed: CommentWithCid[], postCid: string, guiPostsPerPage: number): number | undefined { + if (!postCid || guiPostsPerPage <= 0) return undefined; + const index = feed.findIndex((c) => c.cid === postCid); + if (index < 0) return undefined; + return Math.floor(index / guiPostsPerPage) + 1; +} + +/** + * Strict board-feed filter criteria. + * A feed is a "board feed" iff: + * - sortType === 'active' + * - single-sub feed (subplebbitAddresses.length === 1) + * - no filter, no newerThan, no modQueue, no accountComments + */ +export function isBoardFeedOptions(opts: FeedOptionsLike, subplebbitAddress: string): boolean { + return ( + opts.sortType === 'active' && + opts.subplebbitAddresses?.length === 1 && + opts.subplebbitAddresses[0] === subplebbitAddress && + !opts.filter && + opts.newerThan == null && + !opts.modQueue && + !opts.accountComments + ); +} + +/** + * Find the board pagination page for postCid by searching loaded board feeds. + * Only considers feeds matching strict board-feed criteria. + * + * @param feedsOptions - Feeds store feedsOptions + * @param loadedFeeds - Feeds store loadedFeeds + * @param subplebbitAddress - Board subplebbit address + * @param postCid - CID of the post (OP) to locate + * @param guiPostsPerPage - Posts per GUI page + * @returns 1-based page number, or undefined if not found in any matching feed + */ +export function findPostPageInLoadedBoardFeeds( + feedsOptions: FeedsOptionsLike, + loadedFeeds: LoadedFeedsLike, + subplebbitAddress: string, + postCid: string, + guiPostsPerPage: number, +): number | undefined { + if (!subplebbitAddress || !postCid || guiPostsPerPage <= 0) return undefined; + + for (const feedName of Object.keys(feedsOptions)) { + const opts = feedsOptions[feedName]; + if (!opts || !isBoardFeedOptions(opts, subplebbitAddress)) continue; + + const feed = loadedFeeds[feedName]; + if (!feed || !Array.isArray(feed)) continue; + + const page = findPostPageInFeed(feed, postCid, guiPostsPerPage); + if (page !== undefined) return page; + } + + return undefined; +}