fix: TXT file correctly read and imported on desktop apps, closes #756 (#757)

This commit is contained in:
Huang Xin
2025-03-30 02:11:41 +08:00
committed by GitHub
parent 4acc0d8e12
commit ffd179bb06
23 changed files with 110 additions and 49 deletions
+13 -3
View File
@@ -296,6 +296,11 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
const importBooks = async (files: (string | File)[]) => {
setLoading(true);
const failedFiles = [];
const errorMap: [string, string][] = [
['No chapters detected.', _('No chapters detected.')],
['Failed to parse EPUB.', _('Failed to parse the EPUB file.')],
['Unsupported format.', _('This book format is not supported.')],
];
for (const file of files) {
try {
const book = await appService?.importBook(file, libraryBooks);
@@ -308,10 +313,15 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
const filename = typeof file === 'string' ? file : file.name;
const baseFilename = getBaseFilename(filename);
failedFiles.push(baseFilename);
const errorMessage =
error instanceof Error
? errorMap.find(([substring]) => error.message.includes(substring))?.[1] || ''
: '';
eventDispatcher.dispatch('toast', {
message: _('Failed to import book(s): {{filenames}}', {
filenames: listFormater(false).format(failedFiles),
}),
message:
_('Failed to import book(s): {{filenames}}', {
filenames: listFormater(false).format(failedFiles),
}) + (errorMessage ? `\n${errorMessage}` : ''),
type: 'error',
});
console.error('Failed to import book:', filename, error);
@@ -277,8 +277,8 @@ const TTSControl = () => {
className={clsx(
'fixed right-6 h-12 w-12',
appService?.hasSafeAreaInset
? 'bottom-[calc(env(safe-area-inset-bottom)+48px)]'
: 'bottom-12',
? 'bottom-[calc(env(safe-area-inset-bottom)+70px)]'
: 'bottom-[70px] sm:bottom-14',
)}
>
<TTSIcon isPlaying={isPlaying} onClick={togglePopup} />
+14 -14
View File
@@ -21,7 +21,20 @@ const Popup = ({
}) => (
<div>
<div
className={`triangle text-base-300 absolute z-40 ${triangleClassName}`}
id='popup-container'
className={`bg-base-300 absolute rounded-lg font-sans shadow-xl ${className}`}
style={{
width: `${width}px`,
height: `${height}px`,
left: `${position ? position.point.x : -999}px`,
top: `${position ? position.point.y : -999}px`,
...additionalStyle,
}}
>
{children}
</div>
<div
className={`triangle text-base-300 absolute ${triangleClassName}`}
style={{
left:
trianglePosition?.dir === 'left'
@@ -65,19 +78,6 @@ const Popup = ({
: 'translateX(-50%)',
}}
/>
<div
id='popup-container'
className={`bg-base-300 absolute z-30 rounded-lg font-sans shadow-xl ${className}`}
style={{
width: `${width}px`,
height: `${height}px`,
left: `${position ? position.point.x : -999}px`,
top: `${position ? position.point.y : -999}px`,
...additionalStyle,
}}
>
{children}
</div>
</div>
);
+6 -1
View File
@@ -69,7 +69,12 @@ export const Toast = () => {
messageClass.current,
)}
>
{toastMessage}
{toastMessage.split('\n').map((line, idx) => (
<React.Fragment key={idx}>
{line || <>&nbsp;</>}
<br />
</React.Fragment>
))}
</span>
</div>
</div>
+3 -11
View File
@@ -1,3 +1,4 @@
import { getBaseFilename } from './book';
import { partialMD5 } from './md5';
interface Metadata {
@@ -38,12 +39,12 @@ export class TxtToEpubConverter {
public async convert(options: Txt2EpubOptions): Promise<ConversionResult> {
const { file: txtFile, author: providedAuthor, language: providedLanguage } = options;
const fileContent = await this.readFileAsArrayBuffer(txtFile);
const fileContent = await txtFile.arrayBuffer();
const detectedEncoding = this.detectEncoding(fileContent) || 'utf-8';
const decoder = new TextDecoder(detectedEncoding);
const txtContent = decoder.decode(fileContent).trim();
const bookTitle = this.extractBookTitle(txtFile.name);
const bookTitle = this.extractBookTitle(getBaseFilename(txtFile.name));
const fileName = `${bookTitle}.epub`;
const fileHeader = txtContent.slice(0, 1024);
@@ -358,15 +359,6 @@ export class TxtToEpubConverter {
return 'en';
}
private readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as ArrayBuffer);
reader.onerror = reject;
reader.readAsArrayBuffer(file);
});
}
private extractBookTitle(filename: string): string {
const match = filename.match(/《([^》]+)》/);
return match ? match[1]! : filename.split('.')[0]!;