Remove ExportBook component and integrate new export workflows

- Deleted `ExportBook` component and its usage in `BookCard.tsx`.
- Integrated improved book export workflows in `BookSettingOption` for better user experience.
- Updated database models and repositories to support export options with chapter/version selection.
- Added localization support for export-related messages and tooltips.
- Upgraded dependencies to include libraries required for export formats (e.g., DOCX, PDF, EPUB).
- Bumped app version to 0.4.1.
This commit is contained in:
natreex
2026-03-05 16:31:56 -05:00
parent 94cac463fb
commit ceaecb19fc
16 changed files with 780 additions and 245 deletions

View File

@@ -3,9 +3,12 @@ import { getUserEncryptionKey } from "../keyManager.js";
import Book, { CompleteBookData } from "./Book.js";
import ChapterRepo, {
ActChapterQuery,
ChapterExportInfoResult,
ChapterQueryResult,
ChapterSelectionParam,
ChapterStoryQueryResult,
LastChapterResult
LastChapterResult,
SelectedChapterContentResult
} from "../repositories/chapter.repository.js";
import { ActChapter, ActStory } from "./Act.js";
import ChapterContentRepository, {
@@ -65,6 +68,13 @@ export interface CompleteChapterContent {
version?: number;
}
export interface ChapterExportInfo {
chapterId: string;
title: string;
chapterOrder: number;
availableVersions: number[];
}
interface TipTapNode {
type?: string;
text?: string;
@@ -602,4 +612,53 @@ export default class Chapter {
return processedChapters;
}
static getChaptersExportInfo(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): ChapterExportInfo[] {
const results: ChapterExportInfoResult[] = ChapterRepo.fetchChaptersExportInfo(userId, bookId, lang);
const userEncryptionKey: string = getUserEncryptionKey(userId);
const exportInfos: ChapterExportInfo[] = [];
for (const result of results) {
if (!result.available_versions) continue;
const versions: number[] = result.available_versions
.split(',')
.map((v: string): number => parseInt(v, 10))
.filter((v: number): boolean => !isNaN(v));
if (versions.length === 0) continue;
exportInfos.push({
chapterId: result.chapter_id,
title: result.title ? System.decryptDataWithUserKey(result.title, userEncryptionKey) : '',
chapterOrder: result.chapter_order,
availableVersions: versions.sort((a: number, b: number): number => a - b)
});
}
return exportInfos;
}
static getCompleteBookDataWithSelections(userId: string, bookId: string, selections: ChapterSelectionParam[] | null, lang: 'fr' | 'en' = 'fr'): CompleteBookData {
if (!selections || selections.length === 0) {
return Book.completeBookData(userId, bookId, lang);
}
const bookData: CompleteBookData = Book.completeBookData(userId, bookId, lang);
const selectedResults: SelectedChapterContentResult[] = ChapterRepo.fetchSelectedChaptersContent(bookId, selections, lang);
const userEncryptionKey: string = getUserEncryptionKey(userId);
const selectedChapters: CompleteChapterContent[] = [];
for (const result of selectedResults) {
selectedChapters.push({
id: result.chapter_id,
title: result.title ? System.decryptDataWithUserKey(result.title, userEncryptionKey) : '',
content: result.content ? System.decryptDataWithUserKey(result.content, userEncryptionKey) : '',
order: result.chapter_order,
version: result.version
});
}
return {
...bookData,
chapters: selectedChapters
};
}
}

View File

@@ -82,6 +82,26 @@ export interface ChapterBookResult extends Record<string, SQLiteValue> {
content: string | null;
}
export interface ChapterExportInfoResult extends Record<string, SQLiteValue> {
chapter_id: string;
title: string;
chapter_order: number;
available_versions: string;
}
export interface SelectedChapterContentResult extends Record<string, SQLiteValue> {
chapter_id: string;
title: string;
chapter_order: number;
content: string;
version: number;
}
export interface ChapterSelectionParam {
chapterId: string;
version: number;
}
export default class ChapterRepo {
/**
* Checks if a chapter name already exists for a book.
@@ -698,4 +718,38 @@ export default class ChapterRepo {
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
static fetchChaptersExportInfo(userId: string, bookId: string, lang: 'fr' | 'en'): ChapterExportInfoResult[] {
try {
const db: Database = System.getDb();
const query: string = `SELECT bc.chapter_id, bc.title, bc.chapter_order, GROUP_CONCAT(DISTINCT bcc.version) AS available_versions FROM book_chapters bc LEFT JOIN book_chapter_content bcc ON bc.chapter_id = bcc.chapter_id WHERE bc.author_id = ? AND bc.book_id = ? GROUP BY bc.chapter_id, bc.title, bc.chapter_order ORDER BY bc.chapter_order`;
const params: SQLiteValue[] = [userId, bookId];
return db.all(query, params) as ChapterExportInfoResult[];
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`DB Error: ${error.message}`);
throw new Error(lang === 'fr' ? "Impossible de récupérer les informations d'export des chapitres." : 'Unable to retrieve chapters export info.');
}
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
static fetchSelectedChaptersContent(bookId: string, selections: ChapterSelectionParam[], lang: 'fr' | 'en'): SelectedChapterContentResult[] {
try {
const db: Database = System.getDb();
const conditions: string[] = selections.map((): string => '(chapter.chapter_id = ? AND content.version = ?)');
const query: string = `SELECT chapter.chapter_id, chapter.title, chapter.chapter_order, content.content, content.version FROM book_chapters AS chapter INNER JOIN book_chapter_content AS content ON chapter.chapter_id = content.chapter_id WHERE chapter.book_id = ? AND (${conditions.join(' OR ')}) ORDER BY chapter.chapter_order`;
const params: SQLiteValue[] = [bookId];
for (const selection of selections) {
params.push(selection.chapterId, selection.version);
}
return db.all(query, params) as SelectedChapterContentResult[];
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`DB Error: ${error.message}`);
throw new Error(lang === 'fr' ? 'Impossible de récupérer le contenu des chapitres sélectionnés.' : 'Unable to retrieve selected chapters content.');
}
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}