From 9482f78486c45187a0fd5b181ecc3f28495212a4 Mon Sep 17 00:00:00 2001 From: jarvisxyz Date: Sat, 18 Jul 2026 12:55:46 -0400 Subject: [PATCH] feat: Expandable rows in Kiwix Library browser (#1060) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: expandable rows in Kiwix Library browser (rebased onto dev) Click a row in the Content Explorer's 'Browse the Kiwix Library' table to expand it and reveal the full description (no longer truncated) along with additional metadata: author, publisher, language, category, article count, media count, issue date, file size, tags, and file name. Changes: - Extend RemoteZimFileEntry type with optional metadata fields (language, publisher, category, tags, article_count, media_count, issued) - Update zim_service.ts listRemote() to map these fields from the raw Kiwix API response in the paginated accumulator loop - Remove @tanstack/react-virtual virtualization from remote-explorer.tsx (12 items per page — virtualization not needed and incompatible with variable-height expanded rows) - Use StyledTable's built-in expandable prop with expandedRowRender to show full details when a row is clicked - Preserve upstream's custom libraries source selector and directory browser * fix(RemoteExplorer): guard against invalid dates being rendered --------- Co-authored-by: eizus Co-authored-by: jakeaturner --- admin/app/services/zim_service.ts | 7 ++ .../pages/settings/zim/remote-explorer.tsx | 119 ++++++++++++++---- admin/types/zim.ts | 13 +- 3 files changed, 115 insertions(+), 24 deletions(-) diff --git a/admin/app/services/zim_service.ts b/admin/app/services/zim_service.ts index 5a362e0..c305939 100644 --- a/admin/app/services/zim_service.ts +++ b/admin/app/services/zim_service.ts @@ -164,6 +164,13 @@ export class ZimService { download_url, author: entry.author.name, file_name, + language: entry.language, + category: entry.category, + tags: entry.tags, + article_count: entry.articleCount, + media_count: entry.mediaCount, + publisher: entry.publisher?.name, + issued: entry['dc:issued'], }) } diff --git a/admin/inertia/pages/settings/zim/remote-explorer.tsx b/admin/inertia/pages/settings/zim/remote-explorer.tsx index a19d9b3..1605d21 100644 --- a/admin/inertia/pages/settings/zim/remote-explorer.tsx +++ b/admin/inertia/pages/settings/zim/remote-explorer.tsx @@ -7,7 +7,6 @@ import { } from '@tanstack/react-query' import api from '~/lib/api' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { useVirtualizer } from '@tanstack/react-virtual' import StyledTable from '~/components/StyledTable' import SettingsLayout from '~/layouts/SettingsLayout' import { Head } from '@inertiajs/react' @@ -191,13 +190,6 @@ export default function ZimRemoteExplorer() { [fetchNextPage, isFetching, hasMore] ) - const virtualizer = useVirtualizer({ - count: flatData.length, - estimateSize: () => 48, // Estimate row height - getScrollElement: () => tableParentRef.current, - overscan: 5, // Number of items to render outside the visible area - }) - //a check on mount and after a fetch to see if the table is already scrolled to the bottom and immediately needs to fetch more data useEffect(() => { fetchOnBottomReached(tableParentRef.current) @@ -591,14 +583,7 @@ export default function ZimRemoteExplorer() { /> - data={flatData.map((i, idx) => { - const row = virtualizer.getVirtualItems().find((v) => v.index === idx) - return { - ...i, - height: `${row?.size || 48}px`, - translateY: row?.start || 0, - } - })} + data={flatData} ref={tableParentRef} loading={isLoading} columns={[ @@ -610,6 +595,13 @@ export default function ZimRemoteExplorer() { }, { accessor: 'summary', + render(record) { + return ( + + {record.summary} + + ) + }, }, { accessor: 'updated', @@ -644,11 +636,97 @@ export default function ZimRemoteExplorer() { }, }, ]} - className="relative overflow-x-auto overflow-y-auto h-[600px] w-full mt-4" - tableBodyStyle={{ - position: 'relative', - height: `${virtualizer.getTotalSize()}px`, + expandable={{ + expandedRowRender(record) { + const issuedDate = record.issued ? new Date(record.issued) : null + const hasValidIssuedDate = issuedDate && !Number.isNaN(issuedDate.getTime()) + return ( +
+

{record.summary}

+
+ {record.author && ( +
+ Author: + {record.author} +
+ )} + {record.publisher && ( +
+ Publisher: + {record.publisher} +
+ )} + {record.language && ( +
+ Language: + {record.language} +
+ )} + {record.category && ( +
+ Category: + {record.category} +
+ )} + {record.article_count != null && record.article_count > 0 && ( +
+ Articles: + + {record.article_count.toLocaleString()} + +
+ )} + {record.media_count != null && record.media_count > 0 && ( +
+ Media: + + {record.media_count.toLocaleString()} + +
+ )} + {hasValidIssuedDate && ( +
+ Issued: + + {new Intl.DateTimeFormat('en-US', { + dateStyle: 'medium', + }).format(issuedDate!)} + +
+ )} + {record.size_bytes > 0 && ( +
+ Size: + {formatBytes(record.size_bytes)} +
+ )} +
+ {record.tags && ( +
+ {record.tags + .split(';') + .filter(Boolean) + .map((tag, i) => ( + + {tag.trim()} + + ))} +
+ )} + {record.file_name && ( +
+ File: + {record.file_name} +
+ )} +
+ ) + }, }} + className="overflow-y-auto h-[600px] w-full mt-4" containerProps={{ onScroll: (e) => fetchOnBottomReached(e.currentTarget as HTMLDivElement), }} @@ -760,7 +838,6 @@ export default function ZimRemoteExplorer() { )} )} - {/* Manage Custom Libraries Modal */} diff --git a/admin/types/zim.ts b/admin/types/zim.ts index 96ed081..eb6e2b4 100644 --- a/admin/types/zim.ts +++ b/admin/types/zim.ts @@ -64,6 +64,13 @@ export type RemoteZimFileEntry = { download_url: string author: string file_name: string + language?: string + category?: string + tags?: string + article_count?: number + media_count?: number + publisher?: string + issued?: string } export type ExtractZIMContentOptions = { @@ -71,8 +78,8 @@ export type ExtractZIMContentOptions = { maxArticles?: number onProgress?: (processedArticles: number, totalArticles: number) => void // Batch processing options to avoid lock timeouts - startOffset?: number // Article index to start from for resuming - batchSize?: number // Max articles to process in this batch + startOffset?: number // Article index to start from for resuming + batchSize?: number // Max articles to process in this batch } export type ExtractZIMChunkingStrategy = 'structured' | 'simple' @@ -108,4 +115,4 @@ export type ZIMContentChunk = { // Extraction metadata strategy: ExtractZIMChunkingStrategy -} \ No newline at end of file +}