From 3eaa0a1017749e4adbd02a48851a2c4739a7de8f Mon Sep 17 00:00:00 2001 From: Yeban8090 Date: Wed, 11 Jun 2025 01:08:18 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E7=89=88=E6=9C=AC=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/TypographyView.ts | 149 ++++++++----- src/i18n/interfaces/modals/toolbarModals.ts | 5 +- src/i18n/locales/en.ts | 4 + src/i18n/locales/zh-CN.ts | 4 + src/services/ImgTemplateManager.ts | 93 ++++++++- src/services/ThemeManager.ts | 92 +------- src/styles/components/cover-design.css | 4 +- src/styles/components/paginated-engine.css | 30 ++- src/styles/components/typography-view.css | 144 ++++++++++--- src/styles/templates/templates.css | 98 +++++++++ src/styles/themes/themes.css | 98 +++++++++ src/utils/PaginatedEngine.ts | 220 +++++++------------- 12 files changed, 620 insertions(+), 321 deletions(-) create mode 100644 src/styles/templates/templates.css create mode 100644 src/styles/themes/themes.css diff --git a/src/components/TypographyView.ts b/src/components/TypographyView.ts index 9a71b95..dcb36fc 100644 --- a/src/components/TypographyView.ts +++ b/src/components/TypographyView.ts @@ -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' }); diff --git a/src/i18n/interfaces/modals/toolbarModals.ts b/src/i18n/interfaces/modals/toolbarModals.ts index 3ed68f1..a7b78e7 100644 --- a/src/i18n/interfaces/modals/toolbarModals.ts +++ b/src/i18n/interfaces/modals/toolbarModals.ts @@ -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; diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 66403f5..4c2fb77 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -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', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index aaf5cbc..b02bff9 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -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: '导出', diff --git a/src/services/ImgTemplateManager.ts b/src/services/ImgTemplateManager.ts index dbb64c7..1f9fae4 100644 --- a/src/services/ImgTemplateManager.ts +++ b/src/services/ImgTemplateManager.ts @@ -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 = `
备忘录
${new Date().toLocaleDateString()}
`; + + // 将页眉插入到页面内容之前 + 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 = `
${index + 1}
`; + 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 = `
专题
`; + + // 将页眉插入到页面内容之前 + if (page.firstChild) { + page.insertBefore(header, page.firstChild); + } else { + page.appendChild(header); + } + + // 添加页脚 + const footer = document.createElement('div'); + footer.className = 'magazine-footer'; + footer.innerHTML = `
${index + 1}
`; + page.appendChild(footer); + }); + + // 设置封面样式 + const coverPage = element.querySelector('.page:first-child'); + if (coverPage) { + coverPage.classList.add('magazine-cover'); + } } } \ No newline at end of file diff --git a/src/services/ThemeManager.ts b/src/services/ThemeManager.ts index 67032fc..bc2bfe2 100644 --- a/src/services/ThemeManager.ts +++ b/src/services/ThemeManager.ts @@ -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`; } } \ No newline at end of file diff --git a/src/styles/components/cover-design.css b/src/styles/components/cover-design.css index e6fd334..be63374 100644 --- a/src/styles/components/cover-design.css +++ b/src/styles/components/cover-design.css @@ -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 { diff --git a/src/styles/components/paginated-engine.css b/src/styles/components/paginated-engine.css index 58cb15e..b54df73 100644 --- a/src/styles/components/paginated-engine.css +++ b/src/styles/components/paginated-engine.css @@ -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; } \ No newline at end of file diff --git a/src/styles/components/typography-view.css b/src/styles/components/typography-view.css index 0bd1744..2c825d0 100644 --- a/src/styles/components/typography-view.css +++ b/src/styles/components/typography-view.css @@ -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; diff --git a/src/styles/templates/templates.css b/src/styles/templates/templates.css new file mode 100644 index 0000000..0373808 --- /dev/null +++ b/src/styles/templates/templates.css @@ -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; +} \ No newline at end of file diff --git a/src/styles/themes/themes.css b/src/styles/themes/themes.css new file mode 100644 index 0000000..6465e44 --- /dev/null +++ b/src/styles/themes/themes.css @@ -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; +} + +/* 添加暗色主题的其他样式... */ \ No newline at end of file diff --git a/src/utils/PaginatedEngine.ts b/src/utils/PaginatedEngine.ts index 8518bc1..f77e4ed 100644 --- a/src/utils/PaginatedEngine.ts +++ b/src/utils/PaginatedEngine.ts @@ -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; - } } \ No newline at end of file