feat(metadata): collapsible sections in book details, closes #3217 (#3243)

This commit is contained in:
Huang Xin
2026-02-10 00:56:34 +08:00
committed by GitHub
parent 7dd11c0fb0
commit 03fd6e2e6f
31 changed files with 220 additions and 121 deletions
@@ -6,11 +6,15 @@ import {
MdOutlineDelete,
MdOutlineEdit,
MdSaveAlt,
MdExpandMore,
MdExpandLess,
} from 'react-icons/md';
import { Book } from '@/types/book';
import { BookMetadata } from '@/libs/document';
import { useTranslation } from '@/hooks/useTranslation';
import { useSettingsStore } from '@/store/settingsStore';
import { useEnv } from '@/context/EnvContext';
import {
formatAuthors,
formatDate,
@@ -19,6 +23,7 @@ import {
formatPublisher,
formatTitle,
} from '@/utils/book';
import { saveSysSettings } from '@/helpers/settings';
import BookCover from '@/components/BookCover';
import Dropdown from '../Dropdown';
import MenuItem from '../MenuItem';
@@ -49,6 +54,24 @@ const BookDetailView: React.FC<BookDetailViewProps> = ({
onExport,
}) => {
const _ = useTranslation();
const { envConfig } = useEnv();
const { settings } = useSettingsStore();
const toggleSeriesCollapse = () => {
saveSysSettings(envConfig, 'metadataSeriesCollapsed', !settings.metadataSeriesCollapsed);
};
const toggleOthersCollapse = () => {
saveSysSettings(envConfig, 'metadataOthersCollapsed', !settings.metadataOthersCollapsed);
};
const toggleDescriptionCollapse = () => {
saveSysSettings(
envConfig,
'metadataDescriptionCollapsed',
!settings.metadataDescriptionCollapsed,
);
};
return (
<div className='relative w-full rounded-lg'>
@@ -131,56 +154,141 @@ const BookDetailView: React.FC<BookDetailViewProps> = ({
</div>
<div className='text-base-content my-4'>
<div className='mb-4 grid grid-cols-2 gap-4 sm:grid-cols-3'>
<div className='overflow-hidden'>
<span className='font-bold'>{_('Publisher')}</span>
<p className='text-neutral-content text-sm'>
{formatPublisher(metadata?.publisher || '') || _('Unknown')}
</p>
</div>
<div className='overflow-hidden'>
<span className='font-bold'>{_('Published')}</span>
<p className='text-neutral-content text-sm'>
{formatDate(metadata?.published, true) || _('Unknown')}
</p>
</div>
<div className='overflow-hidden'>
<span className='font-bold'>{_('Updated')}</span>
<p className='text-neutral-content text-sm'>{formatDate(book.updatedAt) || ''}</p>
</div>
<div className='overflow-hidden'>
<span className='font-bold'>{_('Added')}</span>
<p className='text-neutral-content text-sm'>{formatDate(book.createdAt) || ''}</p>
</div>
<div className='overflow-hidden'>
<span className='font-bold'>{_('Language')}</span>
<p className='text-neutral-content text-sm'>
{formatLanguage(metadata?.language) || _('Unknown')}
</p>
</div>
<div className='overflow-hidden'>
<span className='font-bold'>{_('Subjects')}</span>
<p className='text-neutral-content line-clamp-3 text-sm'>
{formatAuthors(metadata?.subject || '') || _('Unknown')}
</p>
</div>
<div className='overflow-hidden'>
<span className='font-bold'>{_('Format')}</span>
<p className='text-neutral-content text-sm'>{book.format || _('Unknown')}</p>
</div>
<div className='overflow-hidden'>
<span className='font-bold'>{_('File Size')}</span>
<p className='text-neutral-content text-sm'>{formatBytes(fileSize) || _('Unknown')}</p>
</div>
<div className='metadata-others'>
<button
className={clsx(
'flex w-full items-center justify-between px-4 py-3 text-left transition-colors',
settings.metadataOthersCollapsed ? 'hover:bg-base-200 rounded-lg' : '',
)}
onClick={toggleOthersCollapse}
>
<span className='text-neutral-content/85 text-base font-semibold'>{_('Metadata')}</span>
<div className='transition-transform duration-200'>
{settings.metadataOthersCollapsed ? (
<MdExpandMore className='h-5 w-5' />
) : (
<MdExpandLess className='h-5 w-5' />
)}
</div>
</button>
{!settings.metadataOthersCollapsed && (
<div className='px-4 py-1'>
<div className='grid grid-cols-2 gap-4 sm:grid-cols-3'>
<div className='overflow-hidden'>
<span className='font-bold'>{_('Publisher')}</span>
<p className='text-neutral-content text-sm'>
{formatPublisher(metadata?.publisher || '') || _('Unknown')}
</p>
</div>
<div className='overflow-hidden'>
<span className='font-bold'>{_('Published')}</span>
<p className='text-neutral-content text-sm'>
{formatDate(metadata?.published, true) || _('Unknown')}
</p>
</div>
<div className='overflow-hidden'>
<span className='font-bold'>{_('Updated')}</span>
<p className='text-neutral-content text-sm'>{formatDate(book.updatedAt) || ''}</p>
</div>
<div className='overflow-hidden'>
<span className='font-bold'>{_('Added')}</span>
<p className='text-neutral-content text-sm'>{formatDate(book.createdAt) || ''}</p>
</div>
<div className='overflow-hidden'>
<span className='font-bold'>{_('Language')}</span>
<p className='text-neutral-content text-sm'>
{formatLanguage(metadata?.language) || _('Unknown')}
</p>
</div>
<div className='overflow-hidden'>
<span className='font-bold'>{_('Subjects')}</span>
<p className='text-neutral-content line-clamp-3 text-sm'>
{formatAuthors(metadata?.subject || '') || _('Unknown')}
</p>
</div>
<div className='overflow-hidden'>
<span className='font-bold'>{_('Format')}</span>
<p className='text-neutral-content text-sm'>{book.format || _('Unknown')}</p>
</div>
<div className='overflow-hidden'>
<span className='font-bold'>{_('File Size')}</span>
<p className='text-neutral-content text-sm'>
{formatBytes(fileSize) || _('Unknown')}
</p>
</div>
<div className='overflow-hidden'>
<span className='font-bold'>{_('Identifier')}</span>
<p className='text-neutral-content line-clamp-1 text-sm'>
{metadata?.identifier || _('Unknown')}
</p>
</div>
</div>
</div>
)}
</div>
<div>
<span className='font-bold'>{_('Description')}</span>
<p
className='text-neutral-content prose prose-sm max-w-full whitespace-pre-line text-sm'
dangerouslySetInnerHTML={{
__html: metadata?.description || _('No description available'),
}}
></p>
<div className='metadata-series'>
<button
className={clsx(
'flex w-full items-center justify-between px-4 py-3 text-left transition-colors',
settings.metadataSeriesCollapsed ? 'hover:bg-base-200 rounded-lg' : '',
)}
onClick={toggleSeriesCollapse}
>
<span className='text-neutral-content/85 text-base font-semibold'>{_('Series')}</span>
<div className='transition-transform duration-200'>
{settings.metadataSeriesCollapsed ? (
<MdExpandMore className='h-5 w-5' />
) : (
<MdExpandLess className='h-5 w-5' />
)}
</div>
</button>
{!settings.metadataSeriesCollapsed && (
<div className='px-4 py-1'>
<div className='grid grid-cols-3 gap-4'>
<div className='col-span-2 overflow-hidden'>
<span className='font-bold'>{_('Series')}</span>
<p className='text-neutral-content text-sm'>{metadata?.series || _('Unknown')}</p>
</div>
<div className='overflow-hidden'>
<span className='font-bold'>{_('Series Index')}</span>
<p className='text-neutral-content text-sm'>
{metadata?.seriesIndex || _('Unknown')}
</p>
</div>
</div>
</div>
)}
</div>
<div className='metadata-description'>
<button
className={clsx(
'flex w-full items-center justify-between rounded-lg px-4 py-3 text-left transition-colors',
settings.metadataDescriptionCollapsed ? 'hover:bg-base-200' : '',
)}
onClick={toggleDescriptionCollapse}
>
<span className='text-neutral-content/85 text-base font-semibold'>
{_('Description')}
</span>
<div className='transition-transform duration-200'>
{settings.metadataDescriptionCollapsed ? (
<MdExpandMore className='h-5 w-5' />
) : (
<MdExpandLess className='h-5 w-5' />
)}
</div>
</button>
{!settings.metadataDescriptionCollapsed && (
<div className='px-4 py-1'>
<p
className='text-neutral-content prose prose-sm max-w-full whitespace-pre-line text-sm'
dangerouslySetInnerHTML={{
__html: metadata?.description || _('No description available'),
}}
></p>
</div>
)}
</div>
</div>
</div>