第二版本优化

This commit is contained in:
Yeban8090 2025-06-11 01:08:18 +08:00
parent af7250bd5a
commit 3eaa0a1017
12 changed files with 620 additions and 321 deletions

View file

@ -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'
});

View file

@ -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;

View file

@ -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',

View file

@ -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: '导出',

View file

@ -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');
}
}
}

View file

@ -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`;
}
}

View file

@ -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 {

View file

@ -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;
}

View file

@ -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;

View 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;
}

View 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;
}
/* 添加暗色主题的其他样式... */

View file

@ -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;
}
}