diff --git a/apps/readest-app/src/app/library/hooks/useBooksSync.ts b/apps/readest-app/src/app/library/hooks/useBooksSync.ts index cedab7c1..0de627ba 100644 --- a/apps/readest-app/src/app/library/hooks/useBooksSync.ts +++ b/apps/readest-app/src/app/library/hooks/useBooksSync.ts @@ -72,6 +72,7 @@ export const useBooksSync = ({ onSyncStart, onSyncEnd }: UseBooksSyncProps) => { if (matchingBook) { if (!matchingBook.deletedAt && matchingBook.uploadedAt && !oldBook.coverDownloadedAt) { await appService?.downloadBook(oldBook, true); + oldBook.coverImageUrl = await appService?.generateCoverImageUrl(oldBook); } const mergedBook = matchingBook.updatedAt > oldBook.updatedAt @@ -92,7 +93,6 @@ export const useBooksSync = ({ onSyncStart, onSyncEnd }: UseBooksSyncProps) => { console.error('Failed to download book:', newBook); } finally { newBook.coverImageUrl = await appService?.generateCoverImageUrl(newBook); - newBook.coverImageUrl += '?timestamp=' + Date.now(); updatedLibrary.push(newBook); setLibrary(updatedLibrary); } diff --git a/apps/readest-app/src/services/webAppService.ts b/apps/readest-app/src/services/webAppService.ts index f7c750f4..da7ff24e 100644 --- a/apps/readest-app/src/services/webAppService.ts +++ b/apps/readest-app/src/services/webAppService.ts @@ -51,6 +51,7 @@ const indexedDBFileSystem: FileSystem = { const content = await this.readFile(path, base, 'binary'); return URL.createObjectURL(new Blob([content])); } catch { + console.warn(`Failed to get Blob URL for path: ${path} in base: ${base}`); return path; } }, diff --git a/apps/readest-app/src/utils/txt.ts b/apps/readest-app/src/utils/txt.ts index ec4f495d..b459d7fa 100644 --- a/apps/readest-app/src/utils/txt.ts +++ b/apps/readest-app/src/utils/txt.ts @@ -21,7 +21,7 @@ interface Txt2EpubOptions { interface ExtractChapterOptions { linesBetweenSegments: number; - paragraphsPerChapter?: number; + fallbackParagraphsPerChapter: number; } interface ConversionResult { @@ -68,9 +68,10 @@ export class TxtToEpubConverter { const metadata = { bookTitle, author, language, identifier }; let chapters: Chapter[] = []; - for (let i = 4; i >= 3; i--) { + for (let i = 8; i >= 6; i--) { chapters = this.extractChapters(txtContent, metadata, { linesBetweenSegments: i, + fallbackParagraphsPerChapter: 100, }); if (chapters.length === 0) { @@ -79,12 +80,6 @@ export class TxtToEpubConverter { break; } } - if (chapters.length === 1) { - chapters = this.extractChapters(txtContent, metadata, { - linesBetweenSegments: 4, - paragraphsPerChapter: 100, - }); - } const blob = await this.createEpub(chapters, metadata); return { @@ -101,24 +96,38 @@ export class TxtToEpubConverter { option: ExtractChapterOptions, ): Chapter[] { const { language } = metadata; - const { linesBetweenSegments, paragraphsPerChapter } = option; + const { linesBetweenSegments, fallbackParagraphsPerChapter } = option; const segmentRegex = new RegExp(`(?:\\r?\\n){${linesBetweenSegments},}|-{8,}\r?\n`); - let chapterRegex: RegExp; + const chapterRegexps: RegExp[] = []; if (language === 'zh') { - chapterRegex = new RegExp( - String.raw`(?:^|\n|\s)` + - '(' + - [ - String.raw`第[零〇一二三四五六七八九十0-9][零〇一二三四五六七八九十百千万0-9]*(?:[章卷节回讲篇封])(?:[::、 \(\)0-9]*[^\n-]{0,24})(?!\S)`, - String.raw`(?:^|\n|\s|《[^》]+》)[一二三四五六七八九十][零〇一二三四五六七八九十百千万]*(?:[:: ][^\n-]{0,24})(?!\S)`, - String.raw`(?:楔子|前言|引言|序言|序章|总论|概论)(?:[:: ][^\n-]{0,24})?(?!\S)`, - ].join('|') + - ')', - 'gu', + chapterRegexps.push( + new RegExp( + String.raw`(?:^|\n)\s*` + + '(' + + [ + String.raw`第[零〇一二三四五六七八九十0-9][零〇一二三四五六七八九十百千万0-9]*(?:[章卷节回讲篇封])(?:[::、 \(\)0-9]*[^\n-]{0,24})(?!\S)`, + String.raw`(?:楔子|前言|简介|引言|序言|序章|总论|概论)(?:[:: ][^\n-]{0,24})?(?!\S)`, + ].join('|') + + ')', + 'gu', + ), + ); + chapterRegexps.push( + new RegExp( + String.raw`(?:^|\n)\s*` + + '(' + + [ + String.raw`[一二三四五六七八九十][零〇一二三四五六七八九十百千万]?[::、 ][^\n-]{0,24}(?=\n|$)`, + String.raw`[0-9]+[^\n]{0,16}(?=\n|$)`, + ].join('|') + + ')', + 'gu', + ), ); } else { - chapterRegex = - /(?:^|\n|\s)(?:(Chapter|Part)\s+(\d+|[IVXLCDM]+)(?:[:.\-–—]?\s+[^\n]*)?|(?:Prologue|Epilogue|Introduction|Foreword)(?:[:.\-–—]?\s+[^\n]*)?)(?=\s|$)/gi; + chapterRegexps.push( + /(?:^|\n|\s)(?:(Chapter|Part)\s+(\d+|[IVXLCDM]+)(?:[:.\-–—]?\s+[^\n]*)?|(?:Prologue|Epilogue|Introduction|Foreword)(?:[:.\-–—]?\s+[^\n]*)?)(?=\s|$)/gi, + ); } const formatSegment = (segment: string): string => { @@ -149,17 +158,35 @@ export class TxtToEpubConverter { return acc; }, []); + const isGoodMatches = (matches: string[], maxLength: number = 100000): boolean => { + const meaningfulParts = matches.filter((part) => part.trim().length > 0); + if (meaningfulParts.length <= 1) return false; + + const hasLongParts = meaningfulParts.some((part) => part.length > maxLength); + return !hasLongParts; + }; + const chapters: Chapter[] = []; const segments = txtContent.split(segmentRegex); for (const segment of segments) { const trimmedSegment = segment.replace(//g, '').trim(); if (!trimmedSegment) continue; - if (paragraphsPerChapter && paragraphsPerChapter > 0) { + const segmentChapters = []; + let matches: string[] = []; + for (const chapterRegex of chapterRegexps) { + const tryMatches = trimmedSegment.split(chapterRegex); + if (isGoodMatches(tryMatches)) { + matches = joinAroundUndefined(tryMatches); + break; + } + } + + if (matches.length === 0 && fallbackParagraphsPerChapter > 0) { const paragraphs = trimmedSegment.split(/\n+/); const totalParagraphs = paragraphs.length; - for (let i = 0; i < totalParagraphs; i += paragraphsPerChapter) { - const chunks = paragraphs.slice(i, i + paragraphsPerChapter); + for (let i = 0; i < totalParagraphs; i += fallbackParagraphsPerChapter) { + const chunks = paragraphs.slice(i, i + fallbackParagraphsPerChapter); const formattedSegment = formatSegment(chunks.join('\n')); const title = `${chapters.length + 1}`; const content = `
${formattedSegment}
`; @@ -168,8 +195,6 @@ export class TxtToEpubConverter { continue; } - const segmentChapters = []; - const matches = joinAroundUndefined(trimmedSegment.split(chapterRegex)); for (let j = 1; j < matches.length; j += 2) { const title = matches[j]?.trim() || ''; const content = matches[j + 1]?.trim() || '';