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
@@ -1079,5 +1079,6 @@
"Authors": "المؤلفون",
"Books": "الكتب",
"Groups": "المجموعات",
"Back to TTS Location": "العودة إلى موقع القراءة الآلية"
"Back to TTS Location": "العودة إلى موقع القراءة الآلية",
"Metadata": "بيانات وصفية"
}
@@ -1031,5 +1031,6 @@
"Authors": "লেখকগণ",
"Books": "বই",
"Groups": "দল",
"Back to TTS Location": "টিটিএস অবস্থানে ফিরে যান"
"Back to TTS Location": "টিটিএস অবস্থানে ফিরে যান",
"Metadata": "মেটাডেটা"
}
@@ -1019,5 +1019,6 @@
"Authors": "རྩོམ་པ་པོ།",
"Books": "དཔེ་ཆ།",
"Groups": "ཚོགས་པ།",
"Back to TTS Location": "TTS གནས་སར་ཕྱིར་ལོག་པ།"
"Back to TTS Location": "TTS གནས་སར་ཕྱིར་ལོག་པ།",
"Metadata": "གནད་སྨིན་གོ་དོན།"
}
@@ -1031,5 +1031,6 @@
"Authors": "Autoren",
"Books": "Bücher",
"Groups": "Gruppen",
"Back to TTS Location": "Zurück zur TTS-Position"
"Back to TTS Location": "Zurück zur TTS-Position",
"Metadata": "Metadaten"
}
@@ -1031,5 +1031,6 @@
"Authors": "Συγγραφείς",
"Books": "Βιβλία",
"Groups": "Ομάδες",
"Back to TTS Location": "Επιστροφή στην τοποθεσία TTS"
"Back to TTS Location": "Επιστροφή στην τοποθεσία TTS",
"Metadata": "Μεταδεδομένα"
}
@@ -1043,5 +1043,6 @@
"Authors": "Autores",
"Books": "Libros",
"Groups": "Grupos",
"Back to TTS Location": "Volver a la ubicación de TTS"
"Back to TTS Location": "Volver a la ubicación de TTS",
"Metadata": "Metadatos"
}
@@ -1031,5 +1031,6 @@
"Authors": "نویسندگان",
"Books": "کتاب‌ها",
"Groups": "گروه‌ها",
"Back to TTS Location": "بازگشت به مکان TTS"
"Back to TTS Location": "بازگشت به مکان TTS",
"Metadata": "فراداده"
}
@@ -1043,5 +1043,6 @@
"Authors": "Auteurs",
"Books": "Livres",
"Groups": "Groupes",
"Back to TTS Location": "Retour à l'emplacement TTS"
"Back to TTS Location": "Retour à l'emplacement TTS",
"Metadata": "Métadonnées"
}
@@ -1001,50 +1001,6 @@
"Select Video": "בחר וידאו",
"Select Audio": "בחר אודיו",
"Select Fonts": "בחר גופנים",
"Invalid file type. Please select {{types}}.": "סוג קובץ לא תקין. אנא בחר {{types}}.",
"Add Files": "הוסף קבצים",
"Failed to download the book. Please check your internet connection and try again.": "הורדת הספר נכשלה. אנא בדוק את חיבור האינטרנט שלך ונסה שוב.",
"An unexpected error occurred. Please try again later.": "אירעה שגיאה בלתי צפויה. אנא נסה שוב מאוחר יותר.",
"Please enter a valid email address and password.": "אנא הזן כתובת אימייל וסיסמה תקינות.",
"Verification email sent. Please check your inbox.": "אימייל אימות נשלח. אנא בדוק את תיבת הדואר הנכנס שלך.",
"Verification failed. Please try again or contact support.": "האימות נכשל. אנא נסה שוב או צור קשר עם התמיכה.",
"Email updated successfully.": "האימייל עודכן בהצלחה.",
"Password reset email sent. Please check your inbox.": "אימייל לאיפוס סיסמה נשלח. אנא בדוק את תיבת הדואר הנכנס שלך.",
"Account deleted successfully.": "החשבון נמחק בהצלחה.",
"Failed to update email. Please try again.": "עדכון האימייל נכשל. אנא נסה שוב.",
"Failed to reset password. Please try again.": "איפוס הסיסמה נכשל. אנא נסה שוב.",
"Invalid name. Please enter a valid name.": "שם לא תקין. אנא הזן שם תקין.",
"Profile updated successfully.": "הפרופיל עודכן בהצלחה.",
"Failed to update profile. Please try again.": "עדכון הפרופיל נכשל. אנא נסה שוב.",
"Cloud storage is full. Please upgrade your plan for more space.": "אחסון הענן מלא. אנא שדרג את התוכנית שלך לקבלת מקום נוסף.",
"File uploaded successfully.": "הקובץ הועלה בהצלחה.",
"Failed to upload file. Please try again.": "העלאת הקובץ נכשלה. אנא נסה שוב.",
"File deleted successfully from cloud.": "הקובץ נמחק בהצלחה מהענן.",
"Cloud backup removed successfully.": "גיבוי הענן הוסר בהצלחה.",
"Failed to remove cloud backup.": "הסרת גיבוי הענן נכשלה.",
"Reading progress updated.": "התקדמות הקריאה עודכנה.",
"Failed to update reading progress.": "עדכון התקדמות הקריאה נכשל.",
"Syncing...": "מסנכרן...",
"Sync completed.": "הסנכרון הושלם.",
"Sync failed. Please check your connection.": "הסנכרון נכשל. אנא בדוק את החיבור שלך.",
"Update check failed.": "הבדיקה לעדכונים נכשלה.",
"New version {{version}} is available for download.": "גרסה חדשה {{version}} זמינה להורדה.",
"Export successful.": "הייצוא הצליח.",
"Export failed.": "הייצוא נכשל.",
"File not found.": "הקובץ לא נמצא.",
"Book re-indexed successfully.": "הספר אונדקס מחדש בהצלחה.",
"Failed to re-index book.": "אינדוקס מחדש של הספר נכשל.",
"Indexing in progress...": "אינדוקס בתהליך...",
"Please enter a valid server URL.": "אנא הזן כתובת שרת תקינה.",
"Connected to KOReader Sync server.": "מחובר לשרת סנכרון KOReader.",
"Failed to connect to KOReader Sync server.": "התחברות לשרת סנכרון KOReader נכשלה.",
"Subscription details updated.": "פרטי המנוי עודכנו.",
"Failed to load subscription details.": "טעינת פרטי המנוי נכשלה.",
"Session expired. Please sign in again.": "תוקף הפגישה פג. אנא התחבר שוב.",
"Subscription cancelled successfully.": "המנוי בוטל בהצלחה.",
"Failed to cancel subscription.": "ביטול המנוי נכשל.",
"Storage management is not available in offline mode.": "ניהול אחסון אינו זמין במצב אופליין.",
"Please enter a display name.": "אנא הזן שם לתצוגה.",
"Storage": "אחסון",
"{{percentage}}% of Cloud Sync Space Used.": "{{percentage}}% משטח האחסון בענן בשימוש.",
"Translation Characters": "תווי תרגום",
@@ -1087,5 +1043,6 @@
"Sunset": "שקיעה",
"Reveal in Finder": "הצג ב-Finder",
"Reveal in File Explorer": "הצג בסייר הקבצים",
"Reveal in Folder": "הצג בתיקייה"
"Reveal in Folder": "הצג בתיקייה",
"Metadata": "מטא-נתונים"
}
@@ -1031,5 +1031,6 @@
"Authors": "लेखक",
"Books": "पुस्तकें",
"Groups": "समूह",
"Back to TTS Location": "टीटीएस स्थान पर वापस जाएं"
"Back to TTS Location": "टीटीएस स्थान पर वापस जाएं",
"Metadata": "मेटाडाटा"
}
@@ -1019,5 +1019,6 @@
"Authors": "Penulis",
"Books": "Buku",
"Groups": "Grup",
"Back to TTS Location": "Kembali ke Lokasi TTS"
"Back to TTS Location": "Kembali ke Lokasi TTS",
"Metadata": "Metadata"
}
@@ -1043,5 +1043,6 @@
"Authors": "Autori",
"Books": "Libri",
"Groups": "Gruppi",
"Back to TTS Location": "Torna alla posizione TTS"
"Back to TTS Location": "Torna alla posizione TTS",
"Metadata": "Metadati"
}
@@ -1019,5 +1019,6 @@
"Authors": "著者",
"Books": "書籍",
"Groups": "グループ",
"Back to TTS Location": "TTSの位置に戻る"
"Back to TTS Location": "TTSの位置に戻る",
"Metadata": "メタデータ"
}
@@ -1019,5 +1019,6 @@
"Authors": "저자",
"Books": "도서",
"Groups": "그룹",
"Back to TTS Location": "TTS 위치로 돌아가기"
"Back to TTS Location": "TTS 위치로 돌아가기",
"Metadata": "메타데이터"
}
@@ -1019,5 +1019,6 @@
"Authors": "Penulis",
"Books": "Buku",
"Groups": "Kumpulan",
"Back to TTS Location": "Kembali ke Lokasi TTS"
"Back to TTS Location": "Kembali ke Lokasi TTS",
"Metadata": "Metadata"
}
@@ -1031,5 +1031,6 @@
"Authors": "Auteurs",
"Books": "Boeken",
"Groups": "Groepen",
"Back to TTS Location": "Terug naar TTS-locatie"
"Back to TTS Location": "Terug naar TTS-locatie",
"Metadata": "Metadata"
}
@@ -1055,5 +1055,6 @@
"Authors": "Autorzy",
"Books": "Książki",
"Groups": "Grupy",
"Back to TTS Location": "Powrót do lokalizacji TTS"
"Back to TTS Location": "Powrót do lokalizacji TTS",
"Metadata": "Metadane"
}
@@ -1043,5 +1043,6 @@
"Authors": "Autores",
"Books": "Livros",
"Groups": "Grupos",
"Back to TTS Location": "Voltar para a localização do TTS"
"Back to TTS Location": "Voltar para a localização do TTS",
"Metadata": "Metadados"
}
@@ -1055,5 +1055,6 @@
"Authors": "Авторы",
"Books": "Книги",
"Groups": "Группы",
"Back to TTS Location": "Вернуться к местоположению TTS"
"Back to TTS Location": "Вернуться к местоположению TTS",
"Metadata": "Метаданные"
}
@@ -1031,5 +1031,6 @@
"Authors": "කතුවරුන්",
"Books": "පොත්",
"Groups": "කණ්ඩායම්",
"Back to TTS Location": "TTS ස්ථානයට ආපසු යන්න"
"Back to TTS Location": "TTS ස්ථානයට ආපසු යන්න",
"Metadata": "මෙටාඩේටා"
}
@@ -1031,5 +1031,6 @@
"Authors": "Författare",
"Books": "Böcker",
"Groups": "Grupper",
"Back to TTS Location": "Tillbaka till TTS-plats"
"Back to TTS Location": "Tillbaka till TTS-plats",
"Metadata": "Metadata"
}
@@ -1031,5 +1031,6 @@
"Authors": "ஆசிரியர்கள்",
"Books": "புத்தகங்கள்",
"Groups": "குழுக்கள்",
"Back to TTS Location": "TTS இடத்திற்குத் திரும்பு"
"Back to TTS Location": "TTS இடத்திற்குத் திரும்பு",
"Metadata": "மெட்டாடேட்டா"
}
@@ -1019,5 +1019,6 @@
"Authors": "ผู้เขียน",
"Books": "หนังสือ",
"Groups": "กลุ่ม",
"Back to TTS Location": "กลับไปยังตำแหน่ง TTS"
"Back to TTS Location": "กลับไปยังตำแหน่ง TTS",
"Metadata": "เมทาดาตา"
}
@@ -1031,5 +1031,6 @@
"Authors": "Yazarlar",
"Books": "Kitaplar",
"Groups": "Gruplar",
"Back to TTS Location": "TTS Konumuna Geri Dön"
"Back to TTS Location": "TTS Konumuna Geri Dön",
"Metadata": "Meta veriler"
}
@@ -1055,5 +1055,6 @@
"Authors": "Автори",
"Books": "Книги",
"Groups": "Групи",
"Back to TTS Location": "Повернутися до розташування TTS"
"Back to TTS Location": "Повернутися до розташування TTS",
"Metadata": "Метадані"
}
@@ -1019,5 +1019,6 @@
"Authors": "Tác giả",
"Books": "Sách",
"Groups": "Nhóm",
"Back to TTS Location": "Quay lại vị trí TTS"
"Back to TTS Location": "Quay lại vị trí TTS",
"Metadata": "Siêu dữ liệu"
}
@@ -1019,5 +1019,6 @@
"Authors": "作者",
"Books": "书籍",
"Groups": "分组",
"Back to TTS Location": "回到朗读位置"
"Back to TTS Location": "回到朗读位置",
"Metadata": "元数据"
}
@@ -1019,5 +1019,6 @@
"Authors": "作者",
"Books": "書籍",
"Groups": "分組",
"Back to TTS Location": "回到朗讀位置"
"Back to TTS Location": "回到朗讀位置",
"Metadata": "元數據"
}
@@ -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>
@@ -88,6 +88,10 @@ export const DEFAULT_SYSTEM_SETTINGS: Partial<SystemSettings> = {
libraryAutoColumns: true,
libraryColumns: 6,
metadataSeriesCollapsed: false,
metadataOthersCollapsed: false,
metadataDescriptionCollapsed: false,
kosync: DEFAULT_KOSYNC_SETTINGS,
aiSettings: DEFAULT_AI_SETTINGS,
+3
View File
@@ -95,6 +95,9 @@ export interface SystemSettings {
customFonts: CustomFont[];
customTextures: CustomTexture[];
opdsCatalogs: OPDSCatalog[];
metadataSeriesCollapsed: boolean;
metadataOthersCollapsed: boolean;
metadataDescriptionCollapsed: boolean;
kosync: KOSyncSettings;