diff --git a/apps/readest-app/src/components/metadata/useMetadataEdit.ts b/apps/readest-app/src/components/metadata/useMetadataEdit.ts index 71bccbad..07a690a6 100644 --- a/apps/readest-app/src/components/metadata/useMetadataEdit.ts +++ b/apps/readest-app/src/components/metadata/useMetadataEdit.ts @@ -60,7 +60,7 @@ export const useMetadataEdit = (metadata: BookMetadata | null) => { const newMeta = { ...editedMeta } as { [key: string]: unknown }; switch (field) { case 'subject': - newMeta['subject'] = value ? value.split(/,|,|、/).map((s) => s.trim()) : []; + newMeta['subject'] = value ? value.split(/,|;|,|、/).map((s) => s.trim()) : []; break; default: newMeta[field] = value; diff --git a/apps/readest-app/src/services/appService.ts b/apps/readest-app/src/services/appService.ts index 73e77f93..ff81b823 100644 --- a/apps/readest-app/src/services/appService.ts +++ b/apps/readest-app/src/services/appService.ts @@ -18,7 +18,7 @@ import { getPrimaryLanguage, } from '@/utils/book'; import { partialMD5 } from '@/utils/md5'; -import { BookDoc, DocumentLoader } from '@/libs/document'; +import { BookDoc, DocumentLoader, EXTS } from '@/libs/document'; import { DEFAULT_BOOK_LAYOUT, DEFAULT_BOOK_STYLE, @@ -205,6 +205,7 @@ export abstract class BaseAppService implements AppService { hash, format, title: formatTitle(loadedBook.metadata.title), + sourceTitle: formatTitle(loadedBook.metadata.title), author: formatAuthors(loadedBook.metadata.author, loadedBook.metadata.language), primaryLanguage: getPrimaryLanguage(loadedBook.metadata.language), createdAt: existingBook ? existingBook.createdAt : Date.now(), @@ -215,8 +216,9 @@ export abstract class BaseAppService implements AppService { }; // update book metadata when reimporting the same book if (existingBook) { - existingBook.title = book.title; - existingBook.author = book.author; + existingBook.title = existingBook.title ?? book.title; + existingBook.sourceTitle = existingBook.sourceTitle ?? book.sourceTitle; + existingBook.author = existingBook.author ?? book.author; existingBook.primaryLanguage = existingBook.primaryLanguage ?? book.primaryLanguage; } @@ -467,7 +469,19 @@ export abstract class BaseAppService implements AppService { } else if (book.url) { file = await this.fs.openFile(book.url, 'None'); } else { - throw new Error(BOOK_FILE_NOT_FOUND_ERROR); + // 0.9.64 has a bug that book.title might be modified but the filename is not updated + const bookDir = getDir(book); + const files = await this.fs.readDir(getDir(book), 'Books'); + if (files.length > 0) { + const bookFile = files.find((f) => f.path.endsWith(`.${EXTS[book.format]}`)); + if (bookFile) { + file = await this.fs.openFile(`${bookDir}/${bookFile.path}`, 'Books'); + } else { + throw new Error(BOOK_FILE_NOT_FOUND_ERROR); + } + } else { + throw new Error(BOOK_FILE_NOT_FOUND_ERROR); + } } return { book, file, config: await this.loadBookConfig(book, settings) }; } diff --git a/apps/readest-app/src/services/webAppService.ts b/apps/readest-app/src/services/webAppService.ts index 97f50ce4..c393874b 100644 --- a/apps/readest-app/src/services/webAppService.ts +++ b/apps/readest-app/src/services/webAppService.ts @@ -151,8 +151,10 @@ const indexedDBFileSystem: FileSystem = { async removeDir() { // Directories are virtual in IndexedDB; no-op }, - async readDir(path: string) { + async readDir(path: string, base: BaseDir) { + const { fp } = resolvePath(path, base); const db = await openIndexedDB(); + return new Promise<{ path: string; isDir: boolean }[]>((resolve, reject) => { const transaction = db.transaction('files', 'readonly'); const store = transaction.objectStore('files'); @@ -162,8 +164,8 @@ const indexedDBFileSystem: FileSystem = { const files = request.result as { path: string }[]; resolve( files - .filter((file) => file.path.startsWith(path)) - .map((file) => ({ path: file.path, isDir: false })), + .filter((file) => file.path.startsWith(fp)) + .map((file) => ({ path: file.path.slice(fp.length + 1), isDir: false })), ); }; diff --git a/apps/readest-app/src/store/readerStore.ts b/apps/readest-app/src/store/readerStore.ts index ec126f2e..865d632d 100644 --- a/apps/readest-app/src/store/readerStore.ts +++ b/apps/readest-app/src/store/readerStore.ts @@ -16,7 +16,7 @@ import { updateToc } from '@/utils/toc'; import { useSettingsStore } from './settingsStore'; import { useBookDataStore } from './bookDataStore'; import { useLibraryStore } from './libraryStore'; -import { getPrimaryLanguage } from '@/utils/book'; +import { formatTitle, getBaseFilename, getPrimaryLanguage } from '@/utils/book'; interface ViewState { /* Unique key for each book view */ @@ -138,8 +138,12 @@ export const useReaderStore = create((set, get) => ({ const { book: bookDoc } = await new DocumentLoader(file).open(); updateToc(bookDoc, config.viewSettings?.sortedTOC ?? false); // Set the book's language for formerly imported books, newly imported books have this field set - book.primaryLanguage = - book.primaryLanguage ?? getPrimaryLanguage(bookDoc.metadata.language); + if (!bookDoc.metadata.title) { + bookDoc.metadata.title = getBaseFilename(file.name); + } + book.sourceTitle = formatTitle(bookDoc.metadata.title); + const primaryLanguage = getPrimaryLanguage(bookDoc.metadata.language); + book.primaryLanguage = book.primaryLanguage ?? primaryLanguage; book.metadata = book.metadata ?? bookDoc.metadata; useBookDataStore.setState((state) => ({ booksData: { diff --git a/apps/readest-app/src/types/book.ts b/apps/readest-app/src/types/book.ts index 52273cd8..2d9f56be 100644 --- a/apps/readest-app/src/types/book.ts +++ b/apps/readest-app/src/types/book.ts @@ -12,7 +12,8 @@ export interface Book { filePath?: string; hash: string; format: BookFormat; - title: string; + title: string; // editable title from metadata + sourceTitle?: string; // parsed when the book is imported and used to locate the file author: string; group?: string; // deprecated in favor of groupId and groupName groupId?: string; diff --git a/apps/readest-app/src/types/records.ts b/apps/readest-app/src/types/records.ts index ddacc14b..9c248c50 100644 --- a/apps/readest-app/src/types/records.ts +++ b/apps/readest-app/src/types/records.ts @@ -3,12 +3,14 @@ export interface DBBook { book_hash: string; format: string; title: string; + source_title?: string; author: string; group_id?: string; group_name?: string; tags?: string[]; progress?: [number, number]; + metadata?: string | null; created_at?: string; updated_at?: string; deleted_at?: string | null; diff --git a/apps/readest-app/src/utils/book.ts b/apps/readest-app/src/utils/book.ts index 9ab55194..a684127b 100644 --- a/apps/readest-app/src/utils/book.ts +++ b/apps/readest-app/src/utils/book.ts @@ -14,7 +14,7 @@ export const getLibraryFilename = () => { export const getRemoteBookFilename = (book: Book) => { // S3 storage: https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/object-keys.html if (getStorageType() === 'r2') { - return `${book.hash}/${makeSafeFilename(book.title)}.${EXTS[book.format]}`; + return `${book.hash}/${makeSafeFilename(book.sourceTitle || book.title)}.${EXTS[book.format]}`; } else if (getStorageType() === 's3') { return `${book.hash}/${book.hash}.${EXTS[book.format]}`; } else { @@ -22,7 +22,7 @@ export const getRemoteBookFilename = (book: Book) => { } }; export const getLocalBookFilename = (book: Book) => { - return `${book.hash}/${makeSafeFilename(book.title)}.${EXTS[book.format]}`; + return `${book.hash}/${makeSafeFilename(book.sourceTitle || book.title)}.${EXTS[book.format]}`; }; export const getCoverFilename = (book: Book) => { return `${book.hash}/cover.png`; diff --git a/apps/readest-app/src/utils/transform.ts b/apps/readest-app/src/utils/transform.ts index d0e1f639..7c525aa8 100644 --- a/apps/readest-app/src/utils/transform.ts +++ b/apps/readest-app/src/utils/transform.ts @@ -41,11 +41,13 @@ export const transformBookToDB = (book: unknown, userId: string): DBBook => { hash, format, title, + sourceTitle, author, groupId, groupName, tags, progress, + metadata, createdAt, updatedAt, deletedAt, @@ -62,6 +64,8 @@ export const transformBookToDB = (book: unknown, userId: string): DBBook => { group_name: groupName, tags: tags, progress: progress, + source_title: sourceTitle, + metadata: metadata ? JSON.stringify(metadata) : null, created_at: new Date(createdAt ?? Date.now()).toISOString(), updated_at: new Date(updatedAt ?? Date.now()).toISOString(), deleted_at: deletedAt ? new Date(deletedAt).toISOString() : null, @@ -79,6 +83,8 @@ export const transformBookFromDB = (dbBook: DBBook): Book => { group_name, tags, progress, + source_title, + metadata, created_at, updated_at, deleted_at, @@ -94,6 +100,8 @@ export const transformBookFromDB = (dbBook: DBBook): Book => { groupName: group_name, tags: tags, progress: progress, + sourceTitle: source_title, + metadata: metadata ? JSON.parse(metadata) : null, createdAt: new Date(created_at!).getTime(), updatedAt: new Date(updated_at!).getTime(), deletedAt: deleted_at ? new Date(deleted_at).getTime() : null,