yeban8090_book-smith/src/components/TypographyView.ts
2025-06-12 10:10:35 +08:00

1022 lines
39 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { App, setIcon, Notice, TFile, MarkdownRenderer } from 'obsidian';
import { BookManager } from "../services/BookManager";
import { Book, ChapterNode } from "../types/book";
import { ExportService} from "../services/ExportService";
import { i18n } from "../i18n/i18n";
import BookSmithPlugin from '../main';
import { ImgTemplateManager, ImgTemplate } from '../services/ImgTemplateManager';
import { ThemeManager } from '../services/ThemeManager';
import { CoverManager, CoverSettings } from '../services/CoverManager';
import { CoverSettingModal } from '../modals/CoverSettingModal';
import { ExportModal } from '../modals/ExportModal';
import { PaginatedEngine, extractBlocks, generatePaginatedTOC } from '../utils/PaginatedEngine';
interface TypographySettings {
fontFamily: string;
fontSize: string;
lineHeight: string;
margin: string;
templateId: string;
themeId: string;
coverSettings?: CoverSettings; // 封面设置
showCover: boolean; // 是否显示封面
bookSize: string; // 新增:书籍开本大小
}
export class TypographyView {
private container: HTMLElement;
private bookManager: BookManager;
private exportService: ExportService;
private imgTemplateManager: ImgTemplateManager;
private themeManager: ThemeManager;
private coverManager: CoverManager;
private books: Book[] = [];
private selectedBook: Book | null = null;
private previewElement: HTMLElement | null = null;
private coverPreviewElement: HTMLElement | null = null;
private currentTemplate: ImgTemplate | null = null;
private coverSettings: CoverSettings | null = null;
// 自定义选择器引用
private customBookSelect: HTMLElement | null = null;
private customTemplateSelect: HTMLElement | null = null;
private customThemeSelect: HTMLElement | null = null;
private customFontSelect: HTMLElement | null = null;
private fontSizeInput: HTMLInputElement | null = null;
// 添加开本大小选择器引用
private customBookSizeSelect: HTMLElement | null = null;
private rootPath: string = ''; // 添加根路径属性
private tempRenderContainer: HTMLElement | null = null; // 临时渲染容器
constructor(
private app: App,
private plugin: BookSmithPlugin,
private parentEl: HTMLElement,
private onExit: () => void
) {
this.bookManager = new BookManager(app, plugin.settings);
this.exportService = new ExportService(app, plugin.settings);
this.themeManager = new ThemeManager(app, plugin.settings);
this.imgTemplateManager = new ImgTemplateManager(app, this.themeManager);
this.coverManager = new CoverManager(app);
this.rootPath = plugin.settings.defaultBookPath || ''; // 初始化根路径
this.createUI();
// 创建临时渲染容器
this.tempRenderContainer = document.createElement('div');
this.tempRenderContainer.style.display = 'none';
document.body.appendChild(this.tempRenderContainer);
}
async initialize() {
try {
// 加载所有书籍
this.books = await this.bookManager.getAllBooks();
// 初始化各个选择器
this.initializeBookSelect();
this.initializeTemplateSelect();
this.initializeThemeSelect();
this.initializeFontSelect();
this.initializeFontSizeControls();
this.initializeBookSizeSelect(); // 新增初始化开本大小选择器
// 更新预览
this.updatePreview();
} catch (error) {
console.error('加载书籍失败:', error);
}
}
private createUI() {
this.container = this.parentEl.createDiv({ cls: 'book-smith-typography-view' });
this.createHeader();
this.createContent();
}
private createHeader() {
const header = this.container.createDiv({ cls: 'typography-header' });
setIcon(header.createSpan({ cls: 'typography-header-icon' }), 'edit-3');
header.createSpan({ text: i18n.t('DESIGN_TYPOGRAPHY'), cls: 'typography-title' });
// 添加退出按钮
const exitBtn = header.createDiv({ cls: 'typography-exit-btn' });
setIcon(exitBtn, 'x');
exitBtn.addEventListener('click', () => this.onExit());
}
private createContent() {
// 创建主容器
const content = this.container.createDiv({ cls: 'typography-content' });
// 创建顶部选择器面板
const topPanel = content.createDiv({ cls: 'typography-top-panel' });
// 创建预览面板
const previewPanel = content.createDiv({ cls: 'typography-preview-panel' });
// 创建底部按钮面板
const bottomPanel = content.createDiv({ cls: 'typography-bottom-panel' });
// 创建顶部选择器
this.createTopSelectors(topPanel);
// 创建预览区域
this.createPreviewArea(previewPanel);
// 创建底部按钮
this.createButtons(bottomPanel);
}
private createPreviewArea(container: HTMLElement) {
// 创建一个包含封面和内容的容器
const previewContainer = container.createDiv({ cls: 'typography-preview-container' });
// 添加封面预览(独立容器)
const coverContainer = previewContainer.createDiv({ cls: 'typography-cover-container' });
this.coverPreviewElement = coverContainer.createDiv({ cls: 'typography-cover-preview' });
// 内容预览(独立容器)
const contentContainer = previewContainer.createDiv({ cls: 'typography-content-container' });
this.previewElement = contentContainer.createDiv({ cls: 'typography-preview' });
}
// 按照要求的顺序创建顶部选择器:书籍、模板、主题、字体、字号大小
private createTopSelectors(container: HTMLElement) {
// 创建第一行选择器容器
const selectorsRow = container.createDiv({ cls: 'typography-selectors-row' });
// 1. 创建书籍选择器
this.createBookSelector(selectorsRow);
// 2. 创建模板选择器
this.createTemplateSelector(selectorsRow);
// 3. 创建主题选择器
this.createThemeSelector(selectorsRow);
// 4. 创建字体选择器
this.createFontSelector(selectorsRow);
// 5. 创建字号大小控制
this.createFontSizeControls(selectorsRow);
// 6. 创建开本大小选择器
this.createBookSizeSelector(selectorsRow);
}
// 自定义选择器通用方法
private createCustomSelect(parent: HTMLElement, className: string, options: { value: string, text: string }[]): HTMLElement {
const container = parent.createDiv({ cls: `book-smith-select-container ${className}` });
const select = container.createDiv({ cls: 'book-smith-select' });
const textSpan = select.createSpan({ cls: 'book-smith-text' });
const arrowSpan = select.createSpan({ cls: 'book-smith-select-arrow' });
setIcon(arrowSpan, 'chevron-down');
// 默认选择第一个选项
if (options.length > 0) {
textSpan.textContent = options[0].text;
select.dataset.value = options[0].value;
}
// 创建下拉菜单
const dropdown = container.createDiv({ cls: 'book-smith-select-dropdown' });
// 添加选项
for (const option of options) {
const item = dropdown.createDiv({ cls: 'book-smith-select-item' });
item.textContent = option.text;
item.dataset.value = option.value;
// 默认选中第一个
if (options.indexOf(option) === 0) {
item.addClass('book-smith-selected');
}
// 点击选项
item.addEventListener('click', () => {
// 更新显示文本和值
textSpan.textContent = option.text;
select.dataset.value = option.value;
// 更新选中状态
dropdown.querySelectorAll('.book-select-item').forEach(el => {
el.removeClass('book-smith-selected');
});
item.addClass('book-smith-selected');
// 关闭下拉菜单
dropdown.removeClass('book-smith-show');
arrowSpan.style.transform = '';
// 触发自定义事件
select.dispatchEvent(new CustomEvent('change', {
detail: { value: option.value, text: option.text }
}));
});
}
// 点击选择器显示/隐藏下拉菜单
select.addEventListener('click', (e) => {
e.stopPropagation();
dropdown.toggleClass('book-smith-show', !dropdown.hasClass('book-smith-show'));
arrowSpan.style.transform = dropdown.hasClass('book-smith-show') ? 'rotate(180deg)' : '';
});
// 点击其他地方关闭下拉菜单
document.addEventListener('click', () => {
dropdown.removeClass('book-smith-show');
arrowSpan.style.transform = '';
});
return container;
}
// 更新自定义选择器的选项
private updateCustomSelectOptions(selectElement: HTMLElement, options: { value: string, text: string }[]) {
const dropdown = selectElement.querySelector('.book-smith-select-dropdown');
const select = selectElement.querySelector('.book-smith-select');
const textSpan = selectElement.querySelector('.book-smith-text');
if (!dropdown || !select || !textSpan) return;
// 清空现有选项
dropdown.innerHTML = '';
// 添加新选项
for (const option of options) {
const item = dropdown.createDiv({ cls: 'book-smith-select-item' });
item.textContent = option.text;
item.dataset.value = option.value;
// 默认选中第一个
if (options.indexOf(option) === 0) {
item.addClass('book-smith-selected');
textSpan.textContent = option.text;
(select as HTMLElement).setAttribute('data-value', option.value);
}
// 点击选项
item.addEventListener('click', () => {
// 更新显示文本和值
textSpan.textContent = option.text;
(select as HTMLElement).setAttribute('data-value', option.value);
// 更新选中状态
dropdown.querySelectorAll('.book-smith-select-item').forEach(el => {
el.removeClass('book-smith-selected');
});
item.addClass('book-smith-selected');
// 关闭下拉菜单
dropdown.removeClass('book-smith-show');
// 触发自定义事件
select.dispatchEvent(new CustomEvent('change', {
detail: { value: option.value, text: option.text }
}));
});
}
}
// 1. 书籍选择器
private createBookSelector(parent: HTMLElement) {
// 获取书籍选项
const bookOptions = this.books.map(book => ({
value: book.basic.uuid,
text: book.basic.title
}));
// 如果没有书籍,添加一个提示选项
if (bookOptions.length === 0) {
bookOptions.push({
value: '',
text: i18n.t('NO_BOOKS_AVAILABLE') || '没有可用的书籍'
});
}
// 创建自定义选择器
this.customBookSelect = this.createCustomSelect(
parent,
'book-smith-book-select',
bookOptions
);
this.customBookSelect.id = 'book-select';
}
// 初始化书籍选择器
private initializeBookSelect() {
if (!this.customBookSelect) return;
// 更新书籍选项
const bookOptions = this.books.map(book => ({
value: book.basic.uuid,
text: book.basic.title
}));
// 更新选择器选项
this.updateCustomSelectOptions(this.customBookSelect, bookOptions);
// 添加事件监听
this.customBookSelect.querySelector('.book-smith-select')?.addEventListener('change', async (e: any) => {
const value = e.detail.value;
this.selectedBook = this.books.find(book => book.basic.uuid === value) || null;
this.updatePreview();
});
// 如果有书籍,选择第一本
if (bookOptions.length > 0) {
const select = this.customBookSelect.querySelector('.book-smith-select');
if (select) {
(select as HTMLElement).setAttribute('data-value', bookOptions[0].value);
this.selectedBook = this.books[0];
this.updatePreview();
}
}
}
// 2. 模板选择器
private createTemplateSelector(parent: HTMLElement) {
this.customTemplateSelect = this.createCustomSelect(
parent,
'book-smith-template-select',
[{ value: '', text: i18n.t('LOADING') || '加载中...' }]
);
this.customTemplateSelect.id = 'template-select';
}
// 初始化模板选择器
private async initializeTemplateSelect() {
if (!this.customTemplateSelect) return;
// 获取模板选项
const templateOptions = await this.getTemplateOptions();
// 更新选择器选项
this.updateCustomSelectOptions(this.customTemplateSelect, templateOptions);
// 添加事件监听
this.customTemplateSelect.querySelector('.book-smith-select')?.addEventListener('change', async (e: any) => {
const value = e.detail.value;
this.currentTemplate = this.imgTemplateManager.getTemplate(value) || null;
this.updatePreview();
});
// 默认选择第一个模板
if (templateOptions.length > 0) {
const select = this.customTemplateSelect.querySelector('.book-smith-select');
if (select) {
(select as HTMLElement).setAttribute('data-value', templateOptions[0].value);
this.currentTemplate = this.imgTemplateManager.getTemplate(templateOptions[0].value) || null;
}
}
}
// 3. 主题选择器
private createThemeSelector(parent: HTMLElement) {
this.customThemeSelect = this.createCustomSelect(
parent,
'book-smith-theme-select',
[{ value: '', text: i18n.t('LOADING') || '加载中...' }]
);
this.customThemeSelect.id = 'theme-select';
}
// 初始化主题选择器
private async initializeThemeSelect() {
if (!this.customThemeSelect) return;
// 获取主题选项
const themes = await this.getThemeOptions();
// 更新选择器选项
this.updateCustomSelectOptions(this.customThemeSelect, themes);
// 添加事件监听
this.customThemeSelect.querySelector('.book-smith-select')?.addEventListener('change', async (e: any) => {
const value = e.detail.value;
this.themeManager.setCurrentTheme(value);
if (this.previewElement) {
this.themeManager.applyTheme(this.previewElement);
}
});
// 默认选择第一个主题
if (themes.length > 0) {
const select = this.customThemeSelect.querySelector('.book-smith-select');
if (select) {
(select as HTMLElement).setAttribute('data-value', themes[0].value);
this.themeManager.setCurrentTheme(themes[0].value);
}
}
}
// 4. 字体选择器
private createFontSelector(parent: HTMLElement) {
const fontOptions = [
{ value: 'default', text: i18n.t('DEFAULT_FONT') || '默认字体' },
{ value: 'songti', text: i18n.t('SONGTI_FONT') || '宋体' },
{ value: 'heiti', text: i18n.t('HEITI_FONT') || '黑体' },
{ value: 'kaiti', text: i18n.t('KAITI_FONT') || '楷体' },
{ value: 'fangsong', text: i18n.t('FANGSONG_FONT') || '仿宋' },
{ value: 'serif', text: i18n.t('SERIF_FONT') || '衬线字体' },
{ value: 'sans-serif', text: i18n.t('SANS_SERIF_FONT') || '无衬线字体' },
{ value: 'monospace', text: i18n.t('MONOSPACE_FONT') || '等宽字体' },
];
this.customFontSelect = this.createCustomSelect(
parent,
'book-smith-font-select',
fontOptions
);
this.customFontSelect.id = 'font-select';
}
// 初始化字体选择器
private initializeFontSelect() {
if (!this.customFontSelect) return;
// 添加事件监听
this.customFontSelect.querySelector('.book-smith-select')?.addEventListener('change', () => {
this.updatePreview();
});
}
// 5. 字号大小控制
private createFontSizeControls(parent: HTMLElement) {
const fontSizeGroup = parent.createEl('div', { cls: 'book-smith-font-size-group' });
// 添加减小按钮
const decreaseButton = fontSizeGroup.createEl('button', {
cls: 'book-smith-font-size-button book-smith-decrease-button',
text: '-'
});
this.fontSizeInput = fontSizeGroup.createEl('input', {
cls: 'book-smith-font-size-input',
type: 'text',
value: '16',
attr: {
style: 'border: none; outline: none; background: transparent;',
min: '12',
max: '30'
}
}) as HTMLInputElement;
// 添加增大按钮
const increaseButton = fontSizeGroup.createEl('button', {
cls: 'book-smith-font-size-button book-smith-increase-button',
text: '+'
});
// 添加单位标签
fontSizeGroup.createEl('span', {
cls: 'book-smith-font-size-unit'
});
}
// 初始化字号控制
private initializeFontSizeControls() {
if (!this.fontSizeInput) return;
const updateFontSize = () => {
if (!this.fontSizeInput) return;
// 获取当前值并确保在有效范围内
let currentSize = parseInt(this.fontSizeInput.value) || 16;
currentSize = Math.max(12, Math.min(30, currentSize));
// 更新输入框值
this.fontSizeInput.value = currentSize.toString();
// 更新主题管理器中的字体大小
this.themeManager.setFontSize(currentSize);
// 更新预览
this.updatePreview();
};
// 获取按钮元素
const decreaseButton = this.fontSizeInput.parentElement?.querySelector('.book-smith-decrease-button');
const increaseButton = this.fontSizeInput.parentElement?.querySelector('.book-smith-increase-button');
// 添加减小字号事件
decreaseButton?.addEventListener('click', () => {
if (!this.fontSizeInput) return;
const currentSize = parseInt(this.fontSizeInput.value) || 16;
if (currentSize > 12) {
this.fontSizeInput.value = (currentSize - 1).toString();
updateFontSize();
}
});
// 添加增大字号事件
increaseButton?.addEventListener('click', () => {
if (!this.fontSizeInput) return;
const currentSize = parseInt(this.fontSizeInput.value) || 16;
if (currentSize < 30) {
this.fontSizeInput.value = (currentSize + 1).toString();
updateFontSize();
}
});
// 添加输入框变化事件
this.fontSizeInput.addEventListener('change', updateFontSize);
this.fontSizeInput.addEventListener('input', updateFontSize);
}
// 获取选项方法
private async getThemeOptions() {
const themes = this.themeManager.getVisibleThemes();
return themes.map(theme => ({
value: theme.id,
text: theme.name
}));
}
private async getTemplateOptions() {
const templates = this.imgTemplateManager.getVisibleTemplates();
return templates.map(template => ({
value: template.id,
text: template.name
}));
}
// 开本大小选择器
private createBookSizeSelector(parent: HTMLElement) {
const bookSizeOptions = [
{ value: 'a4', text: 'A4' },
{ value: 'a5', text: 'A5' },
{ value: 'b5', text: 'B5' },
{ value: '16k', text: '16K' },
{ value: 'custom', text: i18n.t('CUSTOM_SIZE') || '自定义' }
];
this.customBookSizeSelect = this.createCustomSelect(
parent,
'book-smith-book-size-select',
bookSizeOptions
);
this.customBookSizeSelect.id = 'book-size-select';
}
// 初始化开本大小选择器
private initializeBookSizeSelect() {
if (!this.customBookSizeSelect) return;
// 添加事件监听
this.customBookSizeSelect.querySelector('.book-smith-select')?.addEventListener('change', () => {
this.updatePreview();
});
}
// 获取排版设置
private getTypographySettings(): TypographySettings {
// 获取封面开关状态
const coverToggle = this.parentEl.querySelector('.cover-toggle-input') as HTMLInputElement;
const showCover = coverToggle ? coverToggle.checked : true;
return {
fontFamily: (this.customFontSelect?.querySelector('.book-smith-select') as HTMLElement)?.getAttribute('data-value') || 'default',
fontSize: this.fontSizeInput?.value || '16',
lineHeight: 'normal', // 保留默认值
margin: '0', // 保留默认值
templateId: (this.customTemplateSelect?.querySelector('.book-smith-select') as HTMLElement)?.getAttribute('data-value') || 'notes',
themeId: (this.customThemeSelect?.querySelector('.book-smith-select') as HTMLElement)?.getAttribute('data-value') || 'default',
coverSettings: this.coverSettings || undefined,
showCover: showCover,
bookSize: (this.customBookSizeSelect?.querySelector('.book-smith-select') as HTMLElement)?.getAttribute('data-value') || 'a4'
};
}
// 预览相关方法
// 在 updatePreview 方法中添加分页逻辑
private async updatePreview() {
if (!this.selectedBook) return;
// 获取封面开关状态
const coverToggle = this.parentEl.querySelector('.cover-toggle-input') as HTMLInputElement;
const showCover = coverToggle ? coverToggle.checked : true;
// 获取排版设置
const settings = this.getTypographySettings();
// 更新预览样式
this.updatePreviewStyle(settings);
// 分离封面和内容的显示逻辑
if (this.coverPreviewElement) {
this.coverPreviewElement.style.display = showCover ? 'block' : 'none';
if (showCover) {
this.updateCoverPreview();
}
}
if (this.previewElement) {
this.previewElement.style.display = 'block';
// 清空预览元素
this.previewElement.empty();
// 渲染内容(内部会应用主题和模板)
await this.renderPaginatedContent(settings);
}
}
// 修改 renderPaginatedContent 方法,确保一致的应用顺序
private async renderPaginatedContent(settings: TypographySettings) {
if (!this.previewElement || !this.tempRenderContainer) return;
// 清空临时容器
this.tempRenderContainer.empty();
// 1. 先渲染所有内容到临时容器
await this.renderAllContent(this.tempRenderContainer);
// 2. 提取内容块,转换为支持文字级分页的 IBlock 对象
const blocks = extractBlocks(this.tempRenderContainer);
// 3. 创建分页内容容器
const contentContainer = this.previewElement.createDiv({ cls: 'typography-content-pages' });
// 4. 创建分页引擎并应用分页
const engine = new PaginatedEngine(contentContainer);
engine.setOptions({
bookSize: settings.bookSize
});
// 执行分页
engine.paginate(blocks);
// 添加页码标记,修改格式为三位数
engine.addPageMarkers(" {page} ");
// 5. 生成分页目录
const tocPages = generatePaginatedTOC(contentContainer, settings.bookSize);
// 6. 将目录页添加到内容前面
const tocContainer = this.previewElement.createDiv({ cls: 'typography-toc-pages' });
tocPages.forEach(tocPage => {
tocContainer.appendChild(tocPage);
});
// 7.将目录容器移到内容容器前面
this.previewElement.insertBefore(tocContainer, contentContainer);
// 8. 在分页完成后应用主题和模板
if (this.currentTemplate) {
this.currentTemplate.render(this.previewElement);
}
this.themeManager.applyTheme(this.previewElement);
}
// 渲染所有内容到指定容器
private async renderAllContent(container: HTMLElement) {
if (!this.selectedBook) return;
// 渲染章节内容
if (this.selectedBook.structure && this.selectedBook.structure.tree) {
await this.renderChapters(this.selectedBook.structure.tree, container);
}
}
// 递归渲染章节内容(不分页,只渲染到临时容器)
private async renderChapters(chapters: ChapterNode[], container: HTMLElement, level: number = 1) {
if (!chapters || chapters.length === 0) return;
for (const chapter of chapters) {
// 跳过被排除的章节
if (chapter.exclude) continue;
// 创建标题元素
const heading = document.createElement(`h${Math.min(level, 6)}`);
heading.textContent = chapter.title;
heading.dataset.actualLevel = level.toString(); // 添加自定义属性记录实际层级
container.appendChild(heading);
// 如果是文件节点,渲染文件内容
if (chapter.type === 'file' && chapter.path) {
try {
// 构建文件路径
const filePath = `${this.rootPath}/${this.selectedBook?.basic.title}/${chapter.path}`;
const file = this.app.vault.getAbstractFileByPath(filePath);
if (file instanceof TFile) {
// 创建内容容器
const contentEl = container.createDiv({ cls: 'chapter-content' });
// 使用 cachedRead 获取文件内容
const content = await this.app.vault.cachedRead(file);
// 使用 MarkdownRenderer 渲染内容
await MarkdownRenderer.render(
this.app,
content,
contentEl,
file.path,
this.plugin
);
}
} catch (error) {
console.error(`无法读取文件 ${chapter.path}:`, error);
container.createEl('p', { text: `*无法读取此章节内容*`, cls: 'error-message' });
}
}
// 递归处理子章节
if (chapter.children && chapter.children.length > 0) {
await this.renderChapters(chapter.children, container, level + 1);
}
}
}
private updatePreviewStyle(settings: TypographySettings) {
if (!this.previewElement) return;
// 重置样式
this.previewElement.className = 'typography-preview';
// 应用字体
this.previewElement.classList.add(`font-${settings.fontFamily}`);
// 应用字号
this.previewElement.style.fontSize = `${settings.fontSize}px`;
// 应用行间距
this.previewElement.classList.add(`line-height-${settings.lineHeight}`);
// 应用页边距
this.previewElement.classList.add(`margin-${settings.margin}`);
// 应用开本大小
// this.previewElement.classList.add(`book-size-${settings.bookSize}`);
// 如果封面预览元素存在,也应用开本大小
if (this.coverPreviewElement) {
this.coverPreviewElement.className = 'typography-cover-preview';
this.coverPreviewElement.classList.add(`book-size-${settings.bookSize}`);
}
}
// 底部按钮
private createButtons(container: HTMLElement) {
// 创建按钮容器
const buttonsContainer = container.createDiv({ cls: 'typography-buttons-container' });
// 第一行按钮组 - 封面相关
const buttonsGroup = buttonsContainer.createDiv({ cls: 'typography-buttons-group cover-buttons-group' });
// 添加封面设计开关
this.createCoverToggle(buttonsGroup);
// 导出按钮
const exportBtn = buttonsGroup.createEl('button', {
text: i18n.t('EXPORT') || '导出',
cls: 'typography-btn export-btn'
});
exportBtn.addEventListener('click', () => this.exportWithTypography());
}
// 导出功能
private async exportWithTypography() {
try {
if (!this.selectedBook) {
throw new Error('未选择书籍');
}
// 使用新的导出模态框
const exportModal = new ExportModal(
this.parentEl,
this.exportService,
(format) => this.executeExport(format)
);
exportModal.open();
} catch (error) {
console.error('导出错误:', error);
new Notice(`导出失败: ${error instanceof Error ? error.message : String(error)}`);
}
}
// 执行导出
private async executeExport(format: string) {
try {
if (!this.selectedBook) {
throw new Error('未选择书籍');
}
let htmlContent: string | undefined;
let useTypography = false;
// 对于非 txt 格式,获取排版后的 HTML 内容
if (format !== 'txt') {
useTypography = true;
// 获取当前排版设置
const settings = this.getTypographySettings();
// 创建临时容器,用于获取完整的 HTML 内容
const tempContainer = document.createElement('div');
tempContainer.className = 'typography-export-container';
// 复制预览元素的内容到临时容器
if (this.previewElement) {
tempContainer.innerHTML = this.previewElement.innerHTML;
// 应用样式
tempContainer.style.fontSize = `${settings.fontSize}px`;
tempContainer.classList.add(`font-${settings.fontFamily}`);
// 获取 HTML 内容(只获取内部 HTML不包括外层容器
htmlContent = tempContainer.innerHTML;
}
}
// 使用统一的 exportBook 方法进行导出
const content = await this.exportService.exportBook(format, this.selectedBook, {
useTypography,
htmlContent
});
// 处理下载或打印
if (format === 'pdf' && content.content === 'print-success') {
// PDF 已通过 Printd 直接打印,不需要下载
new Notice(`PDF 打印成功!`);
} else {
// 其他格式需要下载
const blob = format === 'txt'
? new Blob([content.content], { type: 'text/plain' })
: this.dataURLToBlob(content.content);
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = content.fileName;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
new Notice(`导出成功!`);
}
} catch (error) {
console.error('导出错误:', error);
new Notice(`导出失败: ${error instanceof Error ? error.message : String(error)}`);
}
}
// 将 Data URL 转换为 Blob
private dataURLToBlob(dataURL: string): Blob {
const parts = dataURL.split(';base64,');
const contentType = parts[0].split(':')[1];
const raw = window.atob(parts[1]);
const rawLength = raw.length;
const uInt8Array = new Uint8Array(rawLength);
for (let i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], { type: contentType });
}
// 添加封面设计开关
private createCoverToggle(parent: HTMLElement) {
const coverToggleContainer = parent.createDiv({ cls: 'cover-toggle-container' });
coverToggleContainer.createEl('span', {
text: i18n.t('SHOW_COVER') || '显示封面',
cls: 'cover-toggle-label'
});
const coverToggle = coverToggleContainer.createEl('input', {
type: 'checkbox',
cls: 'cover-toggle-input'
}) as HTMLInputElement;
coverToggle.checked = false; // 默认显示封面
coverToggle.addEventListener('change', () => {
this.updatePreview();
});
// 添加封面设计按钮
const coverDesignBtn = coverToggleContainer.createEl('button', {
text: i18n.t('DESIGN_COVER') || '设计封面',
cls: 'cover-design-btn'
});
coverDesignBtn.addEventListener('click', () => this.openCoverDesigner());
}
// 打开封面设计器
private openCoverDesigner() {
if (!this.selectedBook) {
new Notice(i18n.t('SELECT_BOOK_FIRST') || '请先选择一本书籍');
return;
}
new CoverSettingModal(
this.app,
(settings) => {
this.coverSettings = settings;
this.updatePreview();
},
this.parentEl,
this.coverManager,
this.coverSettings || undefined,
this.selectedBook.basic.title,
this.selectedBook.basic.author
).open();
}
// 添加封面预览更新方法
private updateCoverPreview() {
if (!this.coverPreviewElement || !this.selectedBook) return;
// 清除现有内容
this.coverPreviewElement.empty();
// 应用封面样式
if (this.coverSettings) {
const contentContainer = this.coverManager.applyCoverStyles(this.coverPreviewElement, this.coverSettings);
// 添加标题和作者
if (contentContainer) {
// 添加书名
const titleEl = contentContainer.createEl('div', {
cls: 'cover-title',
text: this.selectedBook.basic.title
});
titleEl.setAttribute('style', this.coverSettings.titleStyle);
// 添加副标题
if (this.selectedBook.basic.subtitle) {
const subtitleEl = contentContainer.createEl('div', {
cls: 'cover-subtitle',
text: this.selectedBook.basic.subtitle
});
subtitleEl.setAttribute('style', 'font-size: 18px; color: #ffffff; margin-top: 10px; text-shadow: 0 1px 2px rgba(0,0,0,0.5);');
}
// 添加作者信息
if (this.selectedBook.basic.author && this.selectedBook.basic.author.length > 0) {
const authorEl = contentContainer.createEl('div', {
cls: 'cover-author',
text: this.selectedBook.basic.author.join(', ')
});
authorEl.setAttribute('style', this.coverSettings.authorStyle);
}
// 添加描述信息
if (this.selectedBook.basic.desc) {
const descriptionEl = contentContainer.createEl('div', {
cls: 'cover-description',
text: this.selectedBook.basic.desc
});
descriptionEl.setAttribute('style', 'font-size: 14px; color: #f0f0f0; margin-top: 20px; max-width: 80%; text-align: center; font-style: italic;');
}
}
} else {
// 使用默认封面样式
const defaultSettings = this.coverManager.getDefaultCoverSettings();
const contentContainer = this.coverManager.applyCoverStyles(this.coverPreviewElement, defaultSettings);
// 添加标题和作者
if (contentContainer) {
const titleEl = contentContainer.createEl('div', {
cls: 'cover-title',
text: this.selectedBook.basic.title
});
titleEl.setAttribute('style', defaultSettings.titleStyle);
// 添加副标题
if (this.selectedBook.basic.subtitle) {
const subtitleEl = contentContainer.createEl('div', {
cls: 'cover-subtitle',
text: this.selectedBook.basic.subtitle
});
subtitleEl.setAttribute('style', 'font-size: 18px; color: #ffffff; margin-top: 10px; text-shadow: 0 1px 2px rgba(0,0,0,0.5);');
}
if (this.selectedBook.basic.author && this.selectedBook.basic.author.length > 0) {
const authorEl = contentContainer.createEl('div', {
cls: 'cover-author',
text: this.selectedBook.basic.author.join(', ')
});
authorEl.setAttribute('style', defaultSettings.authorStyle);
}
// 添加描述信息
if (this.selectedBook.basic.desc) {
const descriptionEl = contentContainer.createEl('div', {
cls: 'cover-description',
text: this.selectedBook.basic.desc
});
descriptionEl.setAttribute('style', 'font-size: 14px; color: #f0f0f0; margin-top: 20px; max-width: 80%; text-align: center; font-style: italic;');
}
}
}
}
// 移除视图
remove() {
this.container.remove();
}
}