mirror of
https://github.com/yeban8090/book-smith.git
synced 2026-07-22 05:46:23 +00:00
第二版本优化
This commit is contained in:
parent
af7250bd5a
commit
3eaa0a1017
12 changed files with 620 additions and 321 deletions
|
|
@ -8,7 +8,7 @@ import { ImgTemplateManager, ImgTemplate } from '../services/ImgTemplateManager'
|
|||
import { ThemeManager, Theme } from '../services/ThemeManager';
|
||||
import { CoverManager, CoverSettings } from '../services/CoverManager';
|
||||
import { CoverSettingModal } from '../modals/CoverSettingModal';
|
||||
import { PaginatedEngine, IBlock, extractBlocks, generatePaginatedTOC } from '../utils/PaginatedEngine';
|
||||
import { PaginatedEngine, extractBlocks, generatePaginatedTOC } from '../utils/PaginatedEngine';
|
||||
interface TypographySettings {
|
||||
fontFamily: string;
|
||||
fontSize: string;
|
||||
|
|
@ -128,8 +128,6 @@ export class TypographyView {
|
|||
}
|
||||
|
||||
private createPreviewArea(container: HTMLElement) {
|
||||
container.createEl('h2', { text: i18n.t('PREVIEW') });
|
||||
|
||||
// 创建一个包含封面和内容的容器
|
||||
const previewContainer = container.createDiv({ cls: 'typography-preview-container' });
|
||||
|
||||
|
|
@ -144,25 +142,29 @@ export class TypographyView {
|
|||
|
||||
// 按照要求的顺序创建顶部选择器:书籍、模板、主题、字体、字号大小
|
||||
private createTopSelectors(container: HTMLElement) {
|
||||
const selectorsContainer = container.createDiv({ cls: 'typography-selectors' });
|
||||
// 创建第一行选择器容器
|
||||
const selectorsRow1 = container.createDiv({ cls: 'typography-selectors-row' });
|
||||
|
||||
// 1. 创建书籍选择器
|
||||
this.createBookSelector(selectorsContainer);
|
||||
this.createBookSelector(selectorsRow1);
|
||||
|
||||
// 2. 创建模板选择器
|
||||
this.createTemplateSelector(selectorsContainer);
|
||||
this.createTemplateSelector(selectorsRow1);
|
||||
|
||||
// 3. 创建主题选择器
|
||||
this.createThemeSelector(selectorsContainer);
|
||||
this.createThemeSelector(selectorsRow1);
|
||||
|
||||
// 创建第二行选择器容器
|
||||
const selectorsRow2 = container.createDiv({ cls: 'typography-selectors-row' });
|
||||
|
||||
// 4. 创建字体选择器
|
||||
this.createFontSelector(selectorsContainer);
|
||||
this.createFontSelector(selectorsRow2);
|
||||
|
||||
// 5. 创建字号大小控制
|
||||
this.createFontSizeControls(selectorsContainer);
|
||||
this.createFontSizeControls(selectorsRow2);
|
||||
|
||||
// 6. 创建开本大小选择器
|
||||
this.createBookSizeSelector(selectorsContainer);
|
||||
this.createBookSizeSelector(selectorsRow2);
|
||||
}
|
||||
|
||||
// 自定义选择器通用方法
|
||||
|
|
@ -416,9 +418,13 @@ export class TypographyView {
|
|||
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') || '等宽字体' }
|
||||
{ value: 'monospace', text: i18n.t('MONOSPACE_FONT') || '等宽字体' },
|
||||
];
|
||||
|
||||
this.customFontSelect = this.createCustomSelect(
|
||||
|
|
@ -443,31 +449,60 @@ export class TypographyView {
|
|||
private createFontSizeControls(parent: HTMLElement) {
|
||||
const fontSizeGroup = parent.createEl('div', { cls: 'red-font-size-group' });
|
||||
|
||||
|
||||
// 添加减小按钮
|
||||
const decreaseButton = fontSizeGroup.createEl('button', {
|
||||
cls: 'red-font-size-button red-decrease-button',
|
||||
text: '-'
|
||||
});
|
||||
|
||||
this.fontSizeInput = fontSizeGroup.createEl('input', {
|
||||
cls: 'red-font-size-input',
|
||||
type: 'text',
|
||||
value: '16',
|
||||
attr: {
|
||||
style: 'border: none; outline: none; background: transparent;'
|
||||
style: 'border: none; outline: none; background: transparent;',
|
||||
min: '12',
|
||||
max: '30'
|
||||
}
|
||||
}) as HTMLInputElement;
|
||||
|
||||
|
||||
// 添加增大按钮
|
||||
const increaseButton = fontSizeGroup.createEl('button', {
|
||||
cls: 'red-font-size-button red-increase-button',
|
||||
text: '+'
|
||||
});
|
||||
|
||||
// 添加单位标签
|
||||
fontSizeGroup.createEl('span', {
|
||||
cls: 'red-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('button:first-child');
|
||||
const increaseButton = this.fontSizeInput.parentElement?.querySelector('button:last-child');
|
||||
|
||||
const decreaseButton = this.fontSizeInput.parentElement?.querySelector('.red-decrease-button');
|
||||
const increaseButton = this.fontSizeInput.parentElement?.querySelector('.red-increase-button');
|
||||
|
||||
// 添加减小字号事件
|
||||
decreaseButton?.addEventListener('click', () => {
|
||||
if (!this.fontSizeInput) return;
|
||||
|
|
@ -477,7 +512,7 @@ export class TypographyView {
|
|||
updateFontSize();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 添加增大字号事件
|
||||
increaseButton?.addEventListener('click', () => {
|
||||
if (!this.fontSizeInput) return;
|
||||
|
|
@ -487,7 +522,7 @@ export class TypographyView {
|
|||
updateFontSize();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 添加输入框变化事件
|
||||
this.fontSizeInput.addEventListener('change', updateFontSize);
|
||||
this.fontSizeInput.addEventListener('input', updateFontSize);
|
||||
|
|
@ -586,60 +621,56 @@ export class TypographyView {
|
|||
// 清空预览元素
|
||||
this.previewElement.empty();
|
||||
|
||||
// 应用模板
|
||||
if (this.currentTemplate) {
|
||||
this.currentTemplate.render(this.previewElement);
|
||||
}
|
||||
|
||||
// 应用主题
|
||||
this.themeManager.applyTheme(this.previewElement);
|
||||
|
||||
// 渲染内容
|
||||
// 渲染内容(内部会应用主题和模板)
|
||||
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
|
||||
});
|
||||
|
||||
|
||||
// 执行分页
|
||||
const pageCount = engine.paginate(blocks);
|
||||
|
||||
engine.paginate(blocks);
|
||||
|
||||
// 添加页码标记,修改格式为三位数
|
||||
engine.addPageMarkers(" {page} ");
|
||||
|
||||
// 5. 生成分页目录
|
||||
const tocPages = generatePaginatedTOC(this.previewElement, contentContainer, settings.bookSize);
|
||||
|
||||
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);
|
||||
|
||||
// 7. 添加页码信息
|
||||
const pageInfo = this.previewElement.createDiv({ cls: 'page-info' });
|
||||
pageInfo.textContent = `共 ${pageCount + tocPages.length} 页(目录 ${tocPages.length} 页,正文 ${pageCount} 页)`;
|
||||
|
||||
// 8. 在分页完成后应用主题和模板
|
||||
if (this.currentTemplate) {
|
||||
this.currentTemplate.render(this.previewElement);
|
||||
}
|
||||
this.themeManager.applyTheme(this.previewElement);
|
||||
}
|
||||
|
||||
// 渲染所有内容到指定容器
|
||||
|
|
@ -732,13 +763,19 @@ export class TypographyView {
|
|||
// 底部按钮
|
||||
private createButtons(container: HTMLElement) {
|
||||
// 创建按钮容器
|
||||
const buttonsContainer = container.createDiv({ cls: 'typography-buttons' });
|
||||
|
||||
const buttonsContainer = container.createDiv({ cls: 'typography-buttons-container' });
|
||||
|
||||
// 第一行按钮组 - 封面相关
|
||||
const coverButtonsGroup = buttonsContainer.createDiv({ cls: 'typography-buttons-group cover-buttons-group' });
|
||||
|
||||
// 添加封面设计开关
|
||||
this.createCoverToggle(buttonsContainer);
|
||||
|
||||
this.createCoverToggle(coverButtonsGroup);
|
||||
|
||||
// 第二行按钮组 - 操作按钮
|
||||
const actionButtonsGroup = buttonsContainer.createDiv({ cls: 'typography-buttons-group action-buttons-group' });
|
||||
|
||||
// 应用按钮
|
||||
const applyBtn = buttonsContainer.createEl('button', {
|
||||
const applyBtn = actionButtonsGroup.createEl('button', {
|
||||
text: i18n.t('APPLY') || '应用',
|
||||
cls: 'typography-btn apply-btn'
|
||||
});
|
||||
|
|
@ -746,9 +783,9 @@ export class TypographyView {
|
|||
// 应用排版设置
|
||||
new Notice(i18n.t('TYPOGRAPHY_APPLIED') || '排版设置已应用');
|
||||
});
|
||||
|
||||
|
||||
// 导出按钮
|
||||
const exportBtn = buttonsContainer.createEl('button', {
|
||||
const exportBtn = actionButtonsGroup.createEl('button', {
|
||||
text: i18n.t('EXPORT') || '导出',
|
||||
cls: 'typography-btn export-btn'
|
||||
});
|
||||
|
|
|
|||
|
|
@ -61,6 +61,10 @@ export interface ToolbarModalTranslation {
|
|||
SERIF_FONT: string;
|
||||
SANS_SERIF_FONT: string;
|
||||
MONOSPACE_FONT: string;
|
||||
SONGTI_FONT: string;
|
||||
HEITI_FONT: string;
|
||||
KAITI_FONT: string;
|
||||
FANGSONG_FONT: string;
|
||||
APPLY: string;
|
||||
TYPOGRAPHY_APPLIED: string;
|
||||
EXPORT: string;
|
||||
|
|
@ -69,7 +73,6 @@ export interface ToolbarModalTranslation {
|
|||
EXPORT_FAILED: string;
|
||||
FORMAT: string;
|
||||
CUSTOM_SIZE: string;
|
||||
|
||||
// CoverModal
|
||||
DESIGN_COVER: string;
|
||||
SHOW_COVER: string;
|
||||
|
|
|
|||
|
|
@ -383,6 +383,10 @@ const toolbarModalTranslation: ToolbarModalTranslation = {
|
|||
SERIF_FONT: 'Serif font',
|
||||
SANS_SERIF_FONT: 'Sans-serif font',
|
||||
MONOSPACE_FONT: 'Monospace font',
|
||||
SONGTI_FONT: 'Songti font',
|
||||
HEITI_FONT: 'Heiti font',
|
||||
KAITI_FONT: 'Kaiti font',
|
||||
FANGSONG_FONT: 'Fangsong font',
|
||||
APPLY: 'Apply',
|
||||
TYPOGRAPHY_APPLIED: 'Typography applied',
|
||||
EXPORT: 'Export',
|
||||
|
|
|
|||
|
|
@ -382,6 +382,10 @@ const toolbarModalTranslation: ToolbarModalTranslation = {
|
|||
SERIF_FONT: '衬线字体',
|
||||
SANS_SERIF_FONT: '无衬线字体',
|
||||
MONOSPACE_FONT: '等宽字体',
|
||||
SONGTI_FONT: '宋体',
|
||||
HEITI_FONT: '黑体',
|
||||
KAITI_FONT: '楷体',
|
||||
FANGSONG_FONT: '仿宋',
|
||||
APPLY: '应用',
|
||||
TYPOGRAPHY_APPLIED: '排版设置已应用',
|
||||
EXPORT: '导出',
|
||||
|
|
|
|||
|
|
@ -86,7 +86,20 @@ class DefaultTemplate implements ImgTemplate {
|
|||
constructor() {}
|
||||
|
||||
render(element: HTMLElement, settings?: any) {
|
||||
// 实现默认模板的渲染逻辑
|
||||
// 添加模板特定的类名
|
||||
element.classList.add('template-default');
|
||||
|
||||
// 设置基本布局
|
||||
const contentPages = element.querySelector('.typography-content-pages');
|
||||
if (contentPages) {
|
||||
contentPages.classList.add('default-content-layout');
|
||||
}
|
||||
|
||||
// 设置页面边距和布局
|
||||
const pages = element.querySelectorAll('.page');
|
||||
pages.forEach(page => {
|
||||
page.classList.add('default-page-layout');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +117,27 @@ class NotesTemplate implements ImgTemplate {
|
|||
constructor() {}
|
||||
|
||||
render(element: HTMLElement, settings?: any) {
|
||||
// 实现备忘录模板的渲染逻辑
|
||||
// 添加模板特定的类名
|
||||
element.classList.add('template-notes');
|
||||
|
||||
// 设置备忘录风格的页眉
|
||||
const pages = element.querySelectorAll('.page');
|
||||
pages.forEach((page, index) => {
|
||||
// 添加页眉
|
||||
const header = document.createElement('div');
|
||||
header.className = 'notes-header';
|
||||
header.innerHTML = `<div class="notes-title">备忘录</div><div class="notes-date">${new Date().toLocaleDateString()}</div>`;
|
||||
|
||||
// 将页眉插入到页面内容之前
|
||||
if (page.firstChild) {
|
||||
page.insertBefore(header, page.firstChild);
|
||||
} else {
|
||||
page.appendChild(header);
|
||||
}
|
||||
|
||||
// 添加备忘录风格的线条背景
|
||||
page.classList.add('notes-page-style');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +155,27 @@ class BookTemplate implements ImgTemplate {
|
|||
constructor() {}
|
||||
|
||||
render(element: HTMLElement, settings?: any) {
|
||||
// 实现书籍模板的渲染逻辑
|
||||
// 添加模板特定的类名
|
||||
element.classList.add('template-book');
|
||||
|
||||
// 设置书籍风格的页面
|
||||
const pages = element.querySelectorAll('.page');
|
||||
pages.forEach((page, index) => {
|
||||
// 添加书籍风格的页面样式
|
||||
page.classList.add('book-page-style');
|
||||
|
||||
// 添加页脚(页码)
|
||||
const footer = document.createElement('div');
|
||||
footer.className = 'book-footer';
|
||||
footer.innerHTML = `<div class="book-page-number">${index + 1}</div>`;
|
||||
page.appendChild(footer);
|
||||
});
|
||||
|
||||
// 设置封面样式
|
||||
const coverPage = element.querySelector('.page:first-child');
|
||||
if (coverPage) {
|
||||
coverPage.classList.add('book-cover');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -140,6 +193,38 @@ class MagazineTemplate implements ImgTemplate {
|
|||
constructor() {}
|
||||
|
||||
render(element: HTMLElement, settings?: any) {
|
||||
// 实现杂志模板的渲染逻辑
|
||||
// 添加模板特定的类名
|
||||
element.classList.add('template-magazine');
|
||||
|
||||
// 设置杂志风格的页面
|
||||
const pages = element.querySelectorAll('.page');
|
||||
pages.forEach((page, index) => {
|
||||
// 添加杂志风格的页面样式
|
||||
page.classList.add('magazine-page-style');
|
||||
|
||||
// 添加页眉
|
||||
const header = document.createElement('div');
|
||||
header.className = 'magazine-header';
|
||||
header.innerHTML = `<div class="magazine-section">专题</div>`;
|
||||
|
||||
// 将页眉插入到页面内容之前
|
||||
if (page.firstChild) {
|
||||
page.insertBefore(header, page.firstChild);
|
||||
} else {
|
||||
page.appendChild(header);
|
||||
}
|
||||
|
||||
// 添加页脚
|
||||
const footer = document.createElement('div');
|
||||
footer.className = 'magazine-footer';
|
||||
footer.innerHTML = `<div class="magazine-page-number">${index + 1}</div>`;
|
||||
page.appendChild(footer);
|
||||
});
|
||||
|
||||
// 设置封面样式
|
||||
const coverPage = element.querySelector('.page:first-child');
|
||||
if (coverPage) {
|
||||
coverPage.classList.add('magazine-cover');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -228,95 +228,19 @@ export class ThemeManager {
|
|||
}
|
||||
|
||||
// 应用主题到元素
|
||||
// 修改 applyTheme 方法,使用 CSS 类而不是内联样式
|
||||
applyTheme(element: HTMLElement, themeId?: string): void {
|
||||
const theme = themeId ? this.getTheme(themeId) : this.getCurrentTheme();
|
||||
if (!theme) return;
|
||||
|
||||
// 应用容器样式
|
||||
element.setAttribute('style', theme.styles.container);
|
||||
// 移除所有主题相关的类
|
||||
const themeClasses = Array.from(element.classList).filter(cls => cls.startsWith('theme-'));
|
||||
themeClasses.forEach(cls => element.classList.remove(cls));
|
||||
|
||||
// 应用标题样式
|
||||
element.querySelectorAll('h1').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.title.h1);
|
||||
});
|
||||
// 添加当前主题的类
|
||||
element.classList.add(`theme-${theme.id}`);
|
||||
|
||||
element.querySelectorAll('h2').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.title.h2);
|
||||
});
|
||||
|
||||
element.querySelectorAll('h3').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.title.h3);
|
||||
});
|
||||
|
||||
// 应用段落样式
|
||||
element.querySelectorAll('p').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.paragraph);
|
||||
});
|
||||
|
||||
// 应用引用样式
|
||||
element.querySelectorAll('blockquote').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.quote);
|
||||
});
|
||||
|
||||
// 应用列表样式
|
||||
element.querySelectorAll('ul').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.list.ul);
|
||||
});
|
||||
|
||||
element.querySelectorAll('ol').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.list.ol);
|
||||
});
|
||||
|
||||
element.querySelectorAll('li').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.list.li);
|
||||
});
|
||||
|
||||
// 应用强调样式
|
||||
element.querySelectorAll('strong').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.emphasis.strong);
|
||||
});
|
||||
|
||||
element.querySelectorAll('em').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.emphasis.em);
|
||||
});
|
||||
|
||||
element.querySelectorAll('del').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.emphasis.del);
|
||||
});
|
||||
|
||||
// 应用代码样式
|
||||
element.querySelectorAll('pre code').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.code.block);
|
||||
});
|
||||
|
||||
element.querySelectorAll('code:not(pre code)').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.code.inline);
|
||||
});
|
||||
|
||||
// 应用表格样式
|
||||
element.querySelectorAll('table').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.table.container);
|
||||
});
|
||||
|
||||
element.querySelectorAll('th').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.table.header);
|
||||
});
|
||||
|
||||
element.querySelectorAll('td').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.table.cell);
|
||||
});
|
||||
|
||||
// 应用其他元素样式
|
||||
element.querySelectorAll('hr').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.hr);
|
||||
});
|
||||
|
||||
element.querySelectorAll('img').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.image);
|
||||
});
|
||||
|
||||
element.querySelectorAll('a').forEach(el => {
|
||||
el.setAttribute('style', theme.styles.link);
|
||||
});
|
||||
// 设置字体大小
|
||||
element.style.fontSize = `${this.currentFontSize}px`;
|
||||
}
|
||||
}
|
||||
|
|
@ -99,7 +99,9 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 0; /* 移除底部边距 */
|
||||
justify-content: center; /* 居中显示 */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cover-toggle-label {
|
||||
|
|
|
|||
|
|
@ -22,33 +22,35 @@
|
|||
width: 210mm;
|
||||
min-height: 297mm;
|
||||
max-height: 297mm;
|
||||
padding-bottom:10px
|
||||
padding: 15mm 10mm 10mm 10mm;
|
||||
}
|
||||
|
||||
.book-size-a5 {
|
||||
width: 148mm;
|
||||
min-height: 210mm;
|
||||
max-height: 210mm;
|
||||
padding: 15mm 10mm 10mm 10mm;
|
||||
}
|
||||
|
||||
.book-size-b5 {
|
||||
width: 176mm;
|
||||
min-height: 250mm;
|
||||
max-height: 250mm;
|
||||
padding: 15mm 10mm 10mm 10mm;
|
||||
}
|
||||
|
||||
.book-size-16k {
|
||||
width: 184mm;
|
||||
min-height: 260mm;
|
||||
max-height: 260mm;
|
||||
padding: 18mm;
|
||||
padding: 15mm 10mm 10mm 10mm;
|
||||
}
|
||||
|
||||
.book-size-custom {
|
||||
width: 180mm;
|
||||
min-height: 240mm;
|
||||
max-height: 240mm;
|
||||
padding: 15mm;
|
||||
padding: 15mm 10mm 10mm 10mm;
|
||||
}
|
||||
|
||||
/* 目录样式 */
|
||||
|
|
@ -90,6 +92,8 @@
|
|||
|
||||
.toc-page-ref {
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 200;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
|
|
@ -143,16 +147,32 @@
|
|||
color: #666;
|
||||
}
|
||||
|
||||
/* 新增:页码标记内容布局 */
|
||||
.marker-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
/* 页码标记样式 */
|
||||
.page-marker {
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
bottom: 10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
border-top: 1px solid #eee;
|
||||
padding-top: 5px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* 新增:BookSmith标识样式 */
|
||||
.booksmith-logo {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
@ -47,19 +47,48 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部选择器面板样式 */
|
||||
/* 顶部选择器面板样式 - 防止换行 */
|
||||
.typography-top-panel {
|
||||
padding: 16px;
|
||||
max-width: 686px;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
background-color: var(--background-secondary-alt);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* 选择器容器样式 */
|
||||
.typography-selectors {
|
||||
/* 选择器行容器样式 */
|
||||
.typography-selectors-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap; /* 防止换行 */
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: center; /* 居中排列 */
|
||||
min-width: min-content; /* 确保内容不会被压缩 */
|
||||
max-width: 1200px; /* 设置最大宽度 */
|
||||
margin: 0 auto; /* 居中显示 */
|
||||
}
|
||||
|
||||
/* 移除原有的选择器容器样式 */
|
||||
/* .typography-selectors {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
min-width: min-content;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
} */
|
||||
|
||||
/* 自定义选择器容器样式 */
|
||||
.red-select-container {
|
||||
position: relative;
|
||||
flex: 0 0 auto; /* 防止压缩 */
|
||||
min-width: 120px; /* 最小宽度 */
|
||||
max-width: 200px; /* 最大宽度 */
|
||||
|
||||
}
|
||||
|
||||
/* 表单字段样式 */
|
||||
|
|
@ -126,13 +155,52 @@
|
|||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 预览面板样式 */
|
||||
/* 预览面板样式 - 添加滚动条支持 */
|
||||
.typography-preview-panel {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center; /* 确保内容居中 */
|
||||
}
|
||||
|
||||
/* 预览容器样式 - 添加居中和缩放支持 */
|
||||
.typography-preview-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* 封面和内容容器样式 */
|
||||
.typography-cover-container,
|
||||
.typography-content-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 预览内容区域 - 确保缩放时的行为正确 */
|
||||
.typography-preview {
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
color: black;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
||||
overflow: auto;
|
||||
margin: 0 auto;
|
||||
min-width: 650px;
|
||||
transform-origin: center top; /* 确保缩放以中心为基准 */
|
||||
}
|
||||
|
||||
/* 书页样式优化 */
|
||||
.book-page {
|
||||
margin: 0 auto; /* 确保居中 */
|
||||
max-width: 100%; /* 限制最大宽度 */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 预览标题 */
|
||||
|
|
@ -144,19 +212,6 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
/* 预览内容区域 */
|
||||
.typography-preview {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
color: black;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
||||
overflow-y: auto;
|
||||
margin: 0 auto;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
/* 底部按钮面板样式 */
|
||||
.typography-bottom-panel {
|
||||
padding: 16px;
|
||||
|
|
@ -164,13 +219,35 @@
|
|||
background-color: var(--background-secondary-alt);
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.typography-buttons {
|
||||
/* 按钮容器样式 */
|
||||
.typography-buttons-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 按钮组样式 */
|
||||
.typography-buttons-group {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 封面按钮组样式 */
|
||||
.cover-buttons-group {
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
/* 操作按钮组样式 */
|
||||
.action-buttons-group {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
/* 原有的按钮样式保持不变 */
|
||||
.apply-btn, .export-btn {
|
||||
padding: 10px 16px;
|
||||
border-radius: 6px;
|
||||
|
|
@ -210,7 +287,7 @@
|
|||
100% { background-color: var(--background-primary); }
|
||||
}
|
||||
|
||||
/* 保留原有的字体、字号、行高和页边距样式 */
|
||||
/* 字体样式 */
|
||||
.font-default {
|
||||
font-family: var(--default-font);
|
||||
}
|
||||
|
|
@ -227,6 +304,22 @@
|
|||
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
|
||||
}
|
||||
|
||||
.font-songti {
|
||||
font-family: SimSun, "宋体", STSong, "华文宋体", serif;
|
||||
}
|
||||
|
||||
.font-heiti {
|
||||
font-family: SimHei, "黑体", STHeiti, "华文黑体", sans-serif;
|
||||
}
|
||||
|
||||
.font-kaiti {
|
||||
font-family: KaiTi, "楷体", STKaiti, "华文楷体", serif;
|
||||
}
|
||||
|
||||
.font-fangsong {
|
||||
font-family: FangSong, "仿宋", STFangsong, "华文仿宋", serif;
|
||||
}
|
||||
|
||||
/* 字号预览样式 */
|
||||
.font-size-small {
|
||||
font-size: 14px;
|
||||
|
|
@ -359,13 +452,6 @@
|
|||
transition: background-color 0.5s ease;
|
||||
}
|
||||
|
||||
/* ===== 下拉选择器 ===== */
|
||||
.red-select-container {
|
||||
position: relative;
|
||||
max-width: 200px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.red-select {
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
|
|
@ -450,6 +536,8 @@
|
|||
|
||||
/* ===== 字号调整组件 ===== */
|
||||
.red-font-size-group {
|
||||
min-width: 36px;
|
||||
max-width: 120px;
|
||||
height: 36px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
|
|
|||
98
src/styles/templates/templates.css
Normal file
98
src/styles/templates/templates.css
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/* 默认模板样式 */
|
||||
.template-default .page {
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 备忘录模板样式 */
|
||||
.template-notes .page {
|
||||
background-color: #fffdf7;
|
||||
background-image: linear-gradient(#eee 1px, transparent 1px);
|
||||
background-size: 100% 24px;
|
||||
padding: 30px 20px;
|
||||
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.notes-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.notes-title {
|
||||
font-weight: bold;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.notes-date {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
/* 书籍模板样式 */
|
||||
.template-book .page {
|
||||
padding: 30px;
|
||||
background-color: #fffef5;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.book-page-style {
|
||||
position: relative;
|
||||
min-height: 800px;
|
||||
}
|
||||
|
||||
.book-footer {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.book-cover {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 杂志模板样式 */
|
||||
.template-magazine .page {
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.magazine-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid #333;
|
||||
}
|
||||
|
||||
.magazine-section {
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.magazine-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 20px;
|
||||
margin-top: 30px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.magazine-cover {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
98
src/styles/themes/themes.css
Normal file
98
src/styles/themes/themes.css
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/* 默认主题 */
|
||||
.theme-default {
|
||||
background-color: white;
|
||||
color: #333;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.theme-default h1 {
|
||||
font-size: 2em;
|
||||
margin-bottom: 0.5em;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.theme-default h2 {
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.theme-default h3 {
|
||||
font-size: 1.2em;
|
||||
margin-bottom: 0.5em;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.theme-default p {
|
||||
margin-bottom: 1em;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.theme-default blockquote {
|
||||
border-left: 4px solid #ddd;
|
||||
padding-left: 1em;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.theme-default ul {
|
||||
list-style-type: disc;
|
||||
padding-left: 2em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.theme-default ol {
|
||||
list-style-type: decimal;
|
||||
padding-left: 2em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.theme-default li {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.theme-default strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.theme-default em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.theme-default del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.theme-default pre code {
|
||||
background-color: #f5f5f5;
|
||||
padding: 1em;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.theme-default code:not(pre code) {
|
||||
background-color: #f5f5f5;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* 亮色主题 */
|
||||
.theme-light {
|
||||
background-color: #f9f9f9;
|
||||
color: #333;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* 添加亮色主题的其他样式... */
|
||||
|
||||
/* 暗色主题 */
|
||||
.theme-dark {
|
||||
background-color: #222;
|
||||
color: #eee;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* 添加暗色主题的其他样式... */
|
||||
|
|
@ -47,7 +47,7 @@ export class TextBlock implements IBlock {
|
|||
const head = this.content.slice(0, splitPoint).trim();
|
||||
const tail = this.content.slice(splitPoint).trim();
|
||||
|
||||
if (tail.length < minLength * 0.6) return null;
|
||||
if (tail.length < minLength) return null;
|
||||
|
||||
this.content = head;
|
||||
return new TextBlock(this.type, tail);
|
||||
|
|
@ -151,18 +151,7 @@ export class Page {
|
|||
return this.blocks.pop() || null;
|
||||
}
|
||||
isOverflow(maxHeight: number): boolean {
|
||||
return this.element.scrollHeight > maxHeight;
|
||||
}
|
||||
setPageNumber(number: number): void {
|
||||
const pageNumberElement = this.element.querySelector('.page-number');
|
||||
if (pageNumberElement) {
|
||||
pageNumberElement.textContent = number.toString();
|
||||
(pageNumberElement as HTMLElement).style.position = 'absolute';
|
||||
(pageNumberElement as HTMLElement).style.bottom = '10px';
|
||||
(pageNumberElement as HTMLElement).style.left = '0';
|
||||
(pageNumberElement as HTMLElement).style.right = '0';
|
||||
(pageNumberElement as HTMLElement).style.textAlign = 'center';
|
||||
}
|
||||
return this.element.scrollHeight > maxHeight + 20;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -260,7 +249,6 @@ export class PaginatedEngine {
|
|||
unpaginatedBlocks.shift();
|
||||
}
|
||||
}
|
||||
this.pages.forEach((page, idx) => page.setPageNumber(idx + 1));
|
||||
return this.pages.length;
|
||||
}
|
||||
|
||||
|
|
@ -275,7 +263,26 @@ export class PaginatedEngine {
|
|||
this.pages.forEach((page, idx) => {
|
||||
const marker = document.createElement('div');
|
||||
marker.classList.add('page-marker');
|
||||
marker.textContent = format.replace("{page}", (idx + 1).toString());
|
||||
|
||||
// 将页码格式化为三位数
|
||||
const pageNum = (idx + 1).toString().padStart(3, '0');
|
||||
|
||||
// 创建包含BookSmith标识和页码的内容
|
||||
const markerContent = document.createElement('div');
|
||||
markerContent.classList.add('marker-content');
|
||||
|
||||
// 添加BookSmith标识
|
||||
const bookSmithLogo = document.createElement('span');
|
||||
bookSmithLogo.classList.add('booksmith-logo');
|
||||
bookSmithLogo.textContent = 'BookSmith';
|
||||
markerContent.appendChild(bookSmithLogo);
|
||||
|
||||
// 添加页码
|
||||
const pageMarker = document.createElement('span');
|
||||
pageMarker.textContent = format.replace("{page}", pageNum);
|
||||
markerContent.appendChild(pageMarker);
|
||||
|
||||
marker.appendChild(markerContent);
|
||||
page.element.appendChild(marker);
|
||||
});
|
||||
}
|
||||
|
|
@ -296,43 +303,30 @@ export function createNewPage(bookSize: string = 'a4'): HTMLElement {
|
|||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成分页目录
|
||||
*/
|
||||
export function generatePaginatedTOC(container: HTMLElement, contentContainer: HTMLElement, bookSize: string = 'a4'): HTMLElement[] {
|
||||
// 创建目录容器
|
||||
const tocContainer = document.createElement('div');
|
||||
export function generatePaginatedTOC(contentContainer: HTMLElement, bookSize: string = 'a4'): HTMLElement[] {
|
||||
const pageHeightMap = {
|
||||
'a4': 1123, 'a5': 794, 'b5': 945, '16k': 983, 'custom': 907
|
||||
};
|
||||
const maxHeight = pageHeightMap[bookSize as keyof typeof pageHeightMap] || 1000;
|
||||
|
||||
const tocTitle = document.createElement('h2');
|
||||
tocTitle.textContent = '目录';
|
||||
tocContainer.appendChild(tocTitle);
|
||||
|
||||
const tocList = document.createElement('ul');
|
||||
tocList.classList.add('toc-list');
|
||||
tocContainer.appendChild(tocList);
|
||||
|
||||
// 查找所有标题元素和它们所在的页面
|
||||
const headingsWithPages: Array<{
|
||||
level: number;
|
||||
text: string;
|
||||
id: string;
|
||||
pageNumber: number;
|
||||
}> = [];
|
||||
const pages = Array.from(contentContainer.querySelectorAll('.book-page'));
|
||||
|
||||
// 提取标题与对应页码
|
||||
const pages = Array.from(contentContainer.querySelectorAll('.book-page'));
|
||||
pages.forEach((page, pageIndex) => {
|
||||
const headings = Array.from(page.querySelectorAll('h1, h2, h3, h4, h5, h6'));
|
||||
const headings = Array.from(page.querySelectorAll('h1, h2, h3'));
|
||||
headings.forEach((heading, index) => {
|
||||
// 优先使用自定义属性中的实际层级,如果没有则使用标签名中的数字
|
||||
const level = (heading as HTMLElement).dataset.actualLevel
|
||||
? parseInt((heading as HTMLElement).dataset.actualLevel || '1')
|
||||
: parseInt(heading.tagName.substring(1));
|
||||
const text = heading.textContent || '';
|
||||
const id = `heading-${pageIndex}-${index}`;
|
||||
|
||||
// 设置标题ID,用于锚点链接
|
||||
heading.id = id;
|
||||
|
||||
headingsWithPages.push({
|
||||
level,
|
||||
text,
|
||||
|
|
@ -342,133 +336,75 @@ export function generatePaginatedTOC(container: HTMLElement, contentContainer: H
|
|||
});
|
||||
});
|
||||
|
||||
// 为每个标题创建目录项
|
||||
headingsWithPages.forEach(({ level, text, id, pageNumber }) => {
|
||||
const tocPages: HTMLElement[] = [];
|
||||
|
||||
// 工具函数:创建一页目录结构
|
||||
const createTocPage = (): {
|
||||
page: HTMLElement;
|
||||
list: HTMLElement;
|
||||
} => {
|
||||
const tocPage = document.createElement('div');
|
||||
tocPage.classList.add('book-page', 'toc-page', `book-size-${bookSize}`);
|
||||
|
||||
const tocContainer = document.createElement('div');
|
||||
const tocTitle = document.createElement('h1');
|
||||
tocTitle.textContent = '目录';
|
||||
tocContainer.appendChild(tocTitle);
|
||||
|
||||
const tocList = document.createElement('div');
|
||||
tocList.classList.add('toc-list');
|
||||
tocContainer.appendChild(tocList);
|
||||
|
||||
tocPage.appendChild(tocContainer);
|
||||
document.body.appendChild(tocPage); // 必须临时挂载才能测量 scrollHeight
|
||||
|
||||
return { page: tocPage, list: tocList };
|
||||
};
|
||||
|
||||
let { page: currentPage, list: currentList } = createTocPage();
|
||||
tocPages.push(currentPage);
|
||||
|
||||
for (let i = 0; i < headingsWithPages.length; ) {
|
||||
const { level, text, id, pageNumber } = headingsWithPages[i];
|
||||
|
||||
// 构建目录项
|
||||
const listItem = document.createElement('li');
|
||||
listItem.classList.add(`toc-level-${level}`);
|
||||
|
||||
// 创建链接和页码容器
|
||||
const itemContent = document.createElement('div');
|
||||
itemContent.classList.add('toc-item-content');
|
||||
|
||||
// 添加链接
|
||||
const link = document.createElement('a');
|
||||
link.href = `#${id}`;
|
||||
link.textContent = text;
|
||||
itemContent.appendChild(link);
|
||||
|
||||
// 添加页码
|
||||
const pageRef = document.createElement('span');
|
||||
pageRef.classList.add('toc-page-ref');
|
||||
pageRef.textContent = pageNumber.toString();
|
||||
pageRef.textContent = pageNumber.toString().padStart(3, '0');
|
||||
itemContent.appendChild(pageRef);
|
||||
|
||||
listItem.appendChild(itemContent);
|
||||
tocList.appendChild(listItem);
|
||||
});
|
||||
currentList.appendChild(listItem);
|
||||
|
||||
// 对目录进行分页处理
|
||||
const tocPages = [];
|
||||
const pageHeight = getPageHeightByBookSize(bookSize);
|
||||
// 判断是否溢出
|
||||
if (currentPage.scrollHeight > maxHeight) {
|
||||
currentList.removeChild(listItem); // 撤回当前项
|
||||
|
||||
// 创建第一个目录页
|
||||
let currentTocPage = document.createElement('div');
|
||||
currentTocPage.classList.add('book-page', 'toc-page', `book-size-${bookSize}`);
|
||||
currentTocPage.appendChild(tocContainer.cloneNode(true));
|
||||
tocPages.push(currentTocPage);
|
||||
// 新建一页
|
||||
const next = createTocPage();
|
||||
currentPage = next.page;
|
||||
currentList = next.list;
|
||||
tocPages.push(currentPage);
|
||||
|
||||
// 检查目录是否需要分页
|
||||
if (currentTocPage.scrollHeight > pageHeight) {
|
||||
// 重新创建目录,按项目分页
|
||||
tocPages.length = 0;
|
||||
currentTocPage = document.createElement('div');
|
||||
currentTocPage.classList.add('book-page', 'toc-page', `book-size-${bookSize}`);
|
||||
continue; // 不推进 i,下次重新尝试这一条
|
||||
}
|
||||
|
||||
const newTocContainer = document.createElement('div');
|
||||
newTocContainer.classList.add('typography-toc');
|
||||
newTocContainer.classList.add(`book-size-${bookSize}`);
|
||||
|
||||
const newTocTitle = document.createElement('h2');
|
||||
newTocTitle.textContent = '目录';
|
||||
newTocContainer.appendChild(newTocTitle);
|
||||
|
||||
const newTocList = document.createElement('ul');
|
||||
newTocList.classList.add('toc-list');
|
||||
newTocContainer.appendChild(newTocList);
|
||||
|
||||
currentTocPage.appendChild(newTocContainer);
|
||||
tocPages.push(currentTocPage);
|
||||
|
||||
// 逐项添加目录项,检查是否需要创建新页
|
||||
headingsWithPages.forEach(({ level, text, id, pageNumber }) => {
|
||||
const listItem = document.createElement('li');
|
||||
listItem.classList.add(`toc-level-${level}`);
|
||||
|
||||
// 创建链接和页码容器
|
||||
const itemContent = document.createElement('div');
|
||||
itemContent.classList.add('toc-item-content');
|
||||
|
||||
// 添加链接
|
||||
const link = document.createElement('a');
|
||||
link.href = `#${id}`;
|
||||
link.textContent = text;
|
||||
itemContent.appendChild(link);
|
||||
|
||||
// 添加页码
|
||||
const pageRef = document.createElement('span');
|
||||
pageRef.classList.add('toc-page-ref');
|
||||
pageRef.textContent = pageNumber.toString();
|
||||
itemContent.appendChild(pageRef);
|
||||
|
||||
listItem.appendChild(itemContent);
|
||||
|
||||
// 添加到当前目录页
|
||||
const currentTocList = currentTocPage.querySelector('.toc-list');
|
||||
if (currentTocList) {
|
||||
currentTocList.appendChild(listItem);
|
||||
|
||||
// 检查是否溢出
|
||||
if (currentTocPage.scrollHeight > pageHeight) {
|
||||
// 移除刚添加的项
|
||||
currentTocList.removeChild(listItem);
|
||||
|
||||
// 创建新的目录页
|
||||
const nextTocPage = document.createElement('div');
|
||||
nextTocPage.classList.add('book-page', 'toc-page', `book-size-${bookSize}`);
|
||||
|
||||
const nextTocContainer = document.createElement('div');
|
||||
nextTocContainer.classList.add('typography-toc');
|
||||
nextTocContainer.classList.add(`book-size-${bookSize}`);
|
||||
|
||||
const nextTocTitle = document.createElement('h2');
|
||||
nextTocTitle.textContent = '目录';
|
||||
nextTocContainer.appendChild(nextTocTitle);
|
||||
|
||||
const nextTocList = document.createElement('ul');
|
||||
nextTocList.classList.add('toc-list');
|
||||
nextTocContainer.appendChild(nextTocList);
|
||||
|
||||
nextTocPage.appendChild(nextTocContainer);
|
||||
nextTocList.appendChild(listItem);
|
||||
|
||||
tocPages.push(nextTocPage);
|
||||
currentTocPage = nextTocPage;
|
||||
}
|
||||
}
|
||||
});
|
||||
i++; // 成功添加才推进
|
||||
}
|
||||
|
||||
// 清除挂载
|
||||
tocPages.forEach(p => document.body.contains(p) && p.remove());
|
||||
|
||||
return tocPages;
|
||||
}
|
||||
|
||||
// 辅助函数:根据开本大小获取页面高度
|
||||
function getPageHeightByBookSize(bookSize: string): number {
|
||||
switch (bookSize) {
|
||||
case 'a4': return 1000;
|
||||
case 'a5': return 800;
|
||||
case 'b5': return 900;
|
||||
case '16k': return 850;
|
||||
case 'custom': return 800;
|
||||
default: return 800;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue