From 70db8a3327b0d3a9b0f04e7494b3edbbac173b22 Mon Sep 17 00:00:00 2001 From: Yeban8090 Date: Mon, 21 Apr 2025 01:27:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=A8=A1=E6=9D=BF=E9=A2=84?= =?UTF-8?q?=E8=A7=88=E5=8F=8A=E8=83=8C=E6=99=AF=E8=87=AA=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 25 +- package.json | 5 +- src/backgroundManager.ts | 31 +- src/main.ts | 6 +- src/settings/CreateBackgroundModal.ts | 551 ++++++++++++++++++ src/settings/CreateTemplateModal.ts | 16 +- src/settings/MPSettingTab.ts | 494 +++++++++++----- src/settings/settings.ts | 78 +++ src/settings/templatePreviewModal.ts | 75 +++ src/styles/index.css | 4 +- src/styles/settings/background-modal.css | 71 +++ src/styles/settings/settings.css | 126 +++- .../settings/template-preview-modal.css | 83 +++ src/templateManager.ts | 4 +- src/utils/nanoid.ts | 1 + src/view.ts | 4 +- 16 files changed, 1395 insertions(+), 179 deletions(-) create mode 100644 src/settings/CreateBackgroundModal.ts create mode 100644 src/settings/templatePreviewModal.ts create mode 100644 src/styles/settings/background-modal.css create mode 100644 src/styles/settings/template-preview-modal.css create mode 100644 src/utils/nanoid.ts diff --git a/package-lock.json b/package-lock.json index 7f25dc9..38687f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,16 @@ { "name": "mp-preview", - "version": "1.0.1", + "version": "1.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mp-preview", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", + "dependencies": { + "nanoid": "^5.1.5" + }, "devDependencies": { "@types/node": "^16.11.6", "@typescript-eslint/eslint-plugin": "5.29.0", @@ -1856,6 +1859,24 @@ "dev": true, "license": "MIT" }, + "node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", diff --git a/package.json b/package.json index 73e62ba..f64a809 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mp-preview", - "version": "1.0.1", + "version": "1.0.2", "description": "一键将 Markdown 文档转换为微信公众号格式", "main": "main.js", "scripts": { @@ -23,5 +23,8 @@ "obsidian": "latest", "tslib": "2.4.0", "typescript": "4.7.4" + }, + "dependencies": { + "nanoid": "^5.1.5" } } diff --git a/src/backgroundManager.ts b/src/backgroundManager.ts index dbc71bc..3ae8f69 100644 --- a/src/backgroundManager.ts +++ b/src/backgroundManager.ts @@ -1,32 +1,41 @@ -import { backgrounds } from './backgrounds'; +import { SettingsManager } from "./settings/settings"; export interface Background { id: string; name: string; style: string; + isPreset?: boolean; + isVisible?: boolean; } export class BackgroundManager { - private backgrounds: Background[]; private currentBackground: Background | null = null; + private settingsManager: SettingsManager; - constructor() { - this.backgrounds = backgrounds.backgrounds; + constructor(settingsManager: SettingsManager) { + this.settingsManager = settingsManager; } - public getAllBackgrounds(): Background[] { - return this.backgrounds; - } - - public setBackground(id: string | null) { + public setBackground(id: string | null): boolean { if (!id) { this.currentBackground = null; - return; + return true; } - const background = this.backgrounds.find(bg => bg.id === id); + + const background = this.settingsManager.getBackground(id); if (background) { + // 检查背景是否可见 + if (background.isVisible === false) { + console.warn(`尝试设置不可见的背景: ${id}`); + return false; + } + this.currentBackground = background; + return true; } + + console.warn(`未找到背景: ${id}`); + return false; } public applyBackground(element: HTMLElement) { diff --git a/src/main.ts b/src/main.ts index e0b1ee5..a2ffaf7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,14 +7,14 @@ import { DonateManager } from './donateManager'; import { MPSettingTab } from './settings/MPSettingTab'; export default class MPPlugin extends Plugin { settingsManager: SettingsManager; - + templateManager: TemplateManager; async onload() { // 初始化设置管理器 this.settingsManager = new SettingsManager(this); await this.settingsManager.loadSettings(); // 初始化模板管理器 - const templateManager = new TemplateManager(this.app, this.settingsManager); + this.templateManager = new TemplateManager(this.app, this.settingsManager); // 初始化转换器 MPConverter.initialize(this.app); @@ -24,7 +24,7 @@ export default class MPPlugin extends Plugin { // 注册视图 this.registerView( VIEW_TYPE_MP, - (leaf) => new MPView(leaf, templateManager, this.settingsManager) + (leaf) => new MPView(leaf, this.templateManager, this.settingsManager) ); // 自动打开视图但不聚焦 diff --git a/src/settings/CreateBackgroundModal.ts b/src/settings/CreateBackgroundModal.ts new file mode 100644 index 0000000..f3257ea --- /dev/null +++ b/src/settings/CreateBackgroundModal.ts @@ -0,0 +1,551 @@ +import { App, Modal, Setting, Notice } from 'obsidian'; +import { Background } from '../backgroundManager'; +import { nanoid } from '../utils/nanoid'; + +interface CssTemplate { + name: string; + style: string; + settings: string[]; +} + +export class CreateBackgroundModal extends Modal { + private onSubmit: (background: Background) => void; + private background: Background; + private isEditing: boolean; + + // 内部使用的背景类型 + private backgroundType: 'color' | 'css' = 'color'; + private backgroundColor: string = '#f5f5f5'; + private backgroundCssStyle: string = ''; + private cssTemplateType: string = 'custom'; // 新增:CSS模板类型 + // 新增属性 + private patternColor: string = 'rgba(50, 0, 0, 0.03)'; + private patternSize: number = 20; + + // 根据模板类型更新设置项的可见性 + private updateSettingsVisibility(cssSection: HTMLElement, templateType: string) { + const template = this.cssTemplates[templateType]; + if (!template) return; + + // 获取所有可能的设置项容器 + const colorSettingContainer = cssSection.querySelector('.pattern-color-setting'); + const sizeSettingContainer = cssSection.querySelector('.pattern-size-setting'); + const customCssContainer = cssSection.querySelector('.custom-css-container'); + + // 根据模板需要的设置项显示或隐藏 + if (colorSettingContainer) { + colorSettingContainer.toggleClass('is-hidden', !template.settings.includes('color')); + } + + if (sizeSettingContainer) { + sizeSettingContainer.toggleClass('is-hidden', !template.settings.includes('size')); + } + + if (customCssContainer) { + customCssContainer.toggleClass('is-hidden', !template.settings.includes('custom')); + + // 如果显示自定义CSS,同时显示文本区域 + const customCssTextArea = cssSection.querySelector('.custom-css-textarea'); + if (customCssTextArea && template.settings.includes('custom')) { + customCssTextArea.toggleClass('is-hidden', false); + } + } + } + + // 解析CSS样式到各个组件 + private parseCssToComponents() { + // 提取颜色 + const colorMatch = this.backgroundCssStyle.match(/rgba?\([^)]+\)/); + if (colorMatch) { + this.patternColor = colorMatch[0]; + } + + // 提取大小 + const sizeMatch = this.backgroundCssStyle.match(/background-size:\s*(\d+)px/); + if (sizeMatch && sizeMatch[1]) { + this.patternSize = parseInt(sizeMatch[1]); + } + } + + // 更新图案颜色(保持透明度不变) + private updatePatternColor(hexColor: string) { + const r = parseInt(hexColor.slice(1, 3), 16); + const g = parseInt(hexColor.slice(3, 5), 16); + const b = parseInt(hexColor.slice(5, 7), 16); + + // 提取当前透明度 + let alpha = 0.03; + const alphaMatch = this.patternColor.match(/rgba\([^,]+,[^,]+,[^,]+,([^)]+)\)/); + if (alphaMatch && alphaMatch[1]) { + alpha = parseFloat(alphaMatch[1]); + } + + this.patternColor = `rgba(${r}, ${g}, ${b}, ${alpha})`; + } + + // 更新图案透明度(保持颜色不变) + private updatePatternOpacity(opacity: number) { + const colorMatch = this.patternColor.match(/rgba\(([^,]+),([^,]+),([^,]+),[^)]+\)/); + if (colorMatch) { + const [_, r, g, b] = colorMatch; + this.patternColor = `rgba(${r}, ${g}, ${b}, ${opacity})`; + } else { + // 如果之前不是rgba格式,转换为rgba + const r = parseInt(this.patternColor.slice(1, 3), 16); + const g = parseInt(this.patternColor.slice(3, 5), 16); + const b = parseInt(this.patternColor.slice(5, 7), 16); + this.patternColor = `rgba(${r}, ${g}, ${b}, ${opacity})`; + } + } + + // 根据当前组件设置更新CSS样式 + private updateCssStyle() { + if (this.cssTemplateType === 'custom') { + return; // 自定义模式下不自动更新CSS + } + + const template = this.cssTemplates[this.cssTemplateType]; + if (!template) return; + + // 替换模板中的颜色和大小 + let style = template.style; + + // 替换所有颜色 + style = style.replace(/rgba\([^)]+\)/g, this.patternColor); + + // 替换所有大小 + style = style.replace(/(\d+)px/g, `${this.patternSize}px`); + + this.backgroundCssStyle = style; + } + // 预设的CSS模板 + private cssTemplates: Record = { + custom: { + name: '自定义', + style: '', + settings: ['custom'] + }, + grid: { + name: '网格', + style: 'background-image: linear-gradient(90deg, rgba(50, 0, 0, 0.03) 2%, transparent 2%), linear-gradient(360deg, rgba(50, 0, 0, 0.03) 2%, transparent 2%); background-size: 20px 20px;', + settings: ['color', 'size'] + }, + diagonalStripes: { + name: '对角条纹', + style: 'background-image: linear-gradient(135deg, rgba(50, 0, 0, 0.05) 25%, transparent 25%, transparent 50%, rgba(50, 0, 0, 0.05) 50%, rgba(50, 0, 0, 0.05) 75%, transparent 75%, transparent); background-size: 20px 20px;', + settings: ['color', 'size'] + }, + polkaDots: { + name: '波尔卡圆点', + style: 'background-image: radial-gradient(circle, rgba(50, 0, 0, 0.05) 10%, transparent 10%); background-size: 20px 20px;', + settings: ['color', 'size'] + }, + zigzag: { + name: '锯齿形', + style: 'background-image: linear-gradient(135deg, rgba(50, 0, 0, 0.05) 25%, transparent 25%, transparent 50%, rgba(50, 0, 0, 0.05) 50%, rgba(50, 0, 0, 0.05) 75%, transparent 75%, transparent); background-size: 20px 20px; background-position: 0 0, 10px 10px;', + settings: ['color', 'size'] + }, + honeycomb: { + name: '蜂窝', + style: 'background-image: linear-gradient(30deg, rgba(50, 0, 0, 0.05) 12%, transparent 12%, transparent 50%, rgba(50, 0, 0, 0.05) 50%, rgba(50, 0, 0, 0.05) 62%, transparent 62%, transparent); background-size: 20px 35px;', + settings: ['color', 'size'] + }, + wave: { + name: '波浪', + style: 'background-image: linear-gradient(45deg, rgba(50, 0, 0, 0.04) 12%, transparent 12%, transparent 88%, rgba(50, 0, 0, 0.04) 88%), linear-gradient(135deg, rgba(50, 0, 0, 0.04) 12%, transparent 12%, transparent 88%, rgba(50, 0, 0, 0.04) 88%); background-size: 30px 30px; background-position: 0 0, 0 0, 15px 15px, 15px 15px;', + settings: ['color', 'size'] + }, + checkerboard: { + name: '棋盘', + style: 'background-image: linear-gradient(45deg, rgba(50, 0, 0, 0.04) 25%, transparent 25%), linear-gradient(-45deg, rgba(50, 0, 0, 0.04) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, rgba(50, 0, 0, 0.04) 75%), linear-gradient(-45deg, transparent 75%, rgba(50, 0, 0, 0.04) 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0px;', + settings: ['color', 'size'] + } + }; + + // 从 style 字符串中解析出背景类型和相关属性 + private parseStyleToProperties(style: string) { + if (style.includes('background-color:')) { + this.backgroundType = 'color'; + const colorMatch = style.match(/background-color: (#[a-fA-F0-9]+)/); + if (colorMatch && colorMatch[1]) { + this.backgroundColor = colorMatch[1]; + } + } else { + this.backgroundType = 'css'; + // 移除基本样式,保留自定义 CSS + this.backgroundCssStyle = style.replace(/box-sizing: border-box; margin: 0; padding: 0;/, '').trim(); + + // 尝试匹配预设模板 + let matched = false; + for (const [key, template] of Object.entries(this.cssTemplates)) { + if (key === 'custom') continue; + + if (this.backgroundCssStyle === template.style) { + this.cssTemplateType = key; + matched = true; + break; + } + } + + if (!matched) { + this.cssTemplateType = 'custom'; + } + } + } + + constructor( + app: App, + onSubmit: (background: Background) => void, + background?: Background + ) { + super(app); + this.onSubmit = onSubmit; + this.isEditing = !!background; + + if (background) { + this.background = { ...background }; + // 从 style 中解析出类型和相关属性 + this.parseStyleToProperties(background.style); + } else { + this.background = { + id: nanoid(), + name: '', + style: 'background-color: #f5f5f5;' + }; + this.backgroundType = 'color'; + this.backgroundColor = '#f5f5f5'; + } + } + + onOpen() { + const { contentEl } = this; + contentEl.empty(); + contentEl.addClass('mp-background-modal'); + + // 标题 + contentEl.createEl('h2', { text: this.isEditing ? '编辑背景' : '创建新背景' }); + + // 基本信息 + const basicSection = contentEl.createDiv('background-basic-section'); + + // 背景名称 + new Setting(basicSection) + .setName('背景名称') + .setDesc('输入背景的名称') + .addText(text => { + text.setValue(this.background.name || '') + .onChange(value => { + this.background.name = value; + }); + }); + + // 背景类型 + new Setting(basicSection) + .setName('背景类型') + .setDesc('选择背景的类型') + .addDropdown(dropdown => { + dropdown + .addOption('color', '纯色背景') + .addOption('css', 'CSS背景图案') + .setValue(this.backgroundType) + .onChange(value => { + this.backgroundType = value as 'color' | 'css'; + this.updateTypeSpecificSettings(); + }); + }); + + // 类型特定设置容器 + const typeSpecificSection = contentEl.createDiv('background-type-specific-section'); + + // 纯色背景设置 + const colorSection = typeSpecificSection.createDiv('background-color-section'); + new Setting(colorSection) + .setName('背景颜色') + .setDesc('选择背景的颜色') + .addColorPicker(color => { + color.setValue(this.backgroundColor) + .onChange(value => { + this.backgroundColor = value; + this.updatePreview(); + }); + }); + + // CSS背景图案设置 + const cssSection = typeSpecificSection.createDiv('background-css-section'); + + // 添加模板选择下拉框 + new Setting(cssSection) + .setName('背景模板') + .setDesc('选择预设的背景模板') + .addDropdown(dropdown => { + for (const [key, template] of Object.entries(this.cssTemplates)) { + dropdown.addOption(key, template.name); + } + dropdown.setValue(this.cssTemplateType) + .onChange(value => { + this.cssTemplateType = value; + if (value !== 'custom') { + this.backgroundCssStyle = this.cssTemplates[value].style; + + // 解析CSS样式到各个组件 + this.parseCssToComponents(); + + // 更新UI控件以反映当前模板的设置 + const colorPicker = cssSection.querySelector('input[type="color"]'); + const opacitySlider = cssSection.querySelector('.slider'); + const sizeSlider = cssSection.querySelectorAll('.slider')[1]; + + if (colorPicker) { + // 从rgba提取十六进制颜色 + const rgbaMatch = this.patternColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); + if (rgbaMatch) { + const [_, r, g, b] = rgbaMatch; + const hexColor = '#' + + parseInt(r).toString(16).padStart(2, '0') + + parseInt(g).toString(16).padStart(2, '0') + + parseInt(b).toString(16).padStart(2, '0'); + (colorPicker as HTMLInputElement).value = hexColor; + } + } + + if (opacitySlider) { + const opacityMatch = this.patternColor.match(/rgba\([^,]+,[^,]+,[^,]+,([^)]+)\)/); + if (opacityMatch && opacityMatch[1]) { + const opacity = parseFloat(opacityMatch[1]) * 100; + // 使用类型断言来访问__component__属性 + const sliderComponent = (opacitySlider.parentElement as any).__component__; + if (sliderComponent && typeof sliderComponent.setValue === 'function') { + sliderComponent.setValue(opacity); + } + } + } + + if (sizeSlider) { + // 使用类型断言来访问__component__属性 + const sliderComponent = (sizeSlider.parentElement as any).__component__; + if (sliderComponent && typeof sliderComponent.setValue === 'function') { + sliderComponent.setValue(this.patternSize); + } + } + } + + // 更新自定义CSS区域的可见性 + const customCssContainer = cssSection.querySelector('.custom-css-container'); + const customCssTextArea = cssSection.querySelector('.custom-css-textarea'); + if (customCssContainer && customCssTextArea) { + const isCustom = value === 'custom'; + customCssTextArea.toggleClass('is-hidden', !isCustom); + } + + // 更新CSS设置项的可见性 - 移动到这里更合适 + this.updateSettingsVisibility(cssSection, value); + + this.updatePreview(); + }); + }); + + // 背景颜色设置 + const colorSettingContainer = cssSection.createDiv('pattern-color-setting'); + new Setting(colorSettingContainer) + .setName('图案颜色') + .setDesc('设置背景图案的颜色') + .addColorPicker(color => { + // 从rgba提取十六进制颜色 + const rgbaMatch = this.patternColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); + let hexColor = '#320000'; // 默认颜色 + + if (rgbaMatch) { + const [_, r, g, b] = rgbaMatch; + hexColor = '#' + + parseInt(r).toString(16).padStart(2, '0') + + parseInt(g).toString(16).padStart(2, '0') + + parseInt(b).toString(16).padStart(2, '0'); + } + + color.setValue(hexColor) + .onChange(value => { + // 更新颜色但保持透明度 + this.updatePatternColor(value); + this.updateCssStyle(); + this.updatePreview(); + }); + }) + .addSlider(slider => { + // 提取当前透明度 + let opacity = 3; // 默认透明度3% + const alphaMatch = this.patternColor.match(/rgba\([^,]+,[^,]+,[^,]+,([^)]+)\)/); + if (alphaMatch && alphaMatch[1]) { + opacity = parseFloat(alphaMatch[1]) * 100; + } + + slider.setLimits(0, 100, 1) + .setValue(opacity) + .setDynamicTooltip() + .onChange(value => { + // 更新透明度但保持颜色 + this.updatePatternOpacity(value / 100); + this.updateCssStyle(); + this.updatePreview(); + }); + }); + + // 图案大小设置 + const sizeSettingContainer = cssSection.createDiv('pattern-size-setting'); + new Setting(sizeSettingContainer) + .setName('图案大小') + .setDesc('设置背景图案的大小') + .addSlider(slider => { + slider.setLimits(5, 50, 1) + .setValue(this.patternSize) // 使用当前模板的大小 + .setDynamicTooltip() + .onChange(value => { + this.patternSize = value; + this.updateCssStyle(); + this.updatePreview(); + }); + }); + + // 移除自定义CSS切换按钮,直接创建文本区域容器 + const customCssContainer = cssSection.createDiv('custom-css-container'); + const customCssTextArea = customCssContainer.createDiv('custom-css-textarea'); + customCssTextArea.toggleClass('is-hidden', this.cssTemplateType !== 'custom'); + + new Setting(customCssTextArea) + .setName('CSS代码') + .setDesc('直接输入CSS样式代码') + .addTextArea(text => { + text.setValue(this.backgroundCssStyle) + .setPlaceholder(`/* CSS背景样式示例 */ +/* 渐变背景 */ +background: linear-gradient(45deg, #ff9a9e 0%, #fad0c4 99%, #fad0c4 100%); + +/* 条纹背景 */ +background-image: linear-gradient(90deg, rgba(50, 0, 0, 0.05) 50%, transparent 50%); +background-size: 10px 10px; + +/* 网格背景 */ +background-image: + linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px), + linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px); +background-size: 20px 20px;`) + .onChange(value => { + this.backgroundCssStyle = value; + this.updatePreview(); + }); + text.inputEl.rows = 10; + text.inputEl.cols = 50; + }); + // 背景预览 + const previewSection = contentEl.createDiv('background-preview-section'); + previewSection.createEl('h3', { text: '预览' }); + const previewEl = previewSection.createDiv('background-preview'); + previewEl.createSpan({ text: '预览效果' }); + this.updatePreview(previewEl); + + // 按钮区域 + const buttonSection = contentEl.createDiv('background-button-section'); + new Setting(buttonSection) + .addButton(btn => { + btn.setButtonText('取消') + .onClick(() => { + this.close(); + }); + }) + .addButton(btn => { + btn.setButtonText(this.isEditing ? '保存' : '创建') + .setCta() + .onClick(() => { + if (!this.validateForm()) { + return; + } + this.generateStyle(); + this.onSubmit(this.background); + this.close(); + }); + }); + + // 初始化类型特定设置 + this.updateTypeSpecificSettings(); + } + + private validateForm(): boolean { + if (!this.background.name) { + new Notice('请输入背景名称'); + return false; + } + + switch (this.backgroundType) { + case 'color': + if (!this.backgroundColor) { + new Notice('请选择背景颜色'); + return false; + } + break; + case 'css': + if (!this.backgroundCssStyle) { + new Notice('请输入CSS样式'); + return false; + } + break; + } + + return true; + } + + private updateTypeSpecificSettings() { + const colorSection = this.contentEl.querySelector('.background-color-section'); + const cssSection = this.contentEl.querySelector('.background-css-section'); + + if (colorSection && cssSection) { + colorSection.toggleClass('is-hidden', this.backgroundType !== 'color'); + cssSection.toggleClass('is-hidden', this.backgroundType !== 'css'); + + // 如果切换到CSS背景类型,根据当前模板更新设置项可见性 + if (this.backgroundType === 'css') { + this.updateSettingsVisibility(cssSection as HTMLElement, this.cssTemplateType); + } + } + + const previewEl = this.contentEl.querySelector('.background-preview'); + if (previewEl) { + this.updatePreview(previewEl as HTMLElement); + } + } + + private updatePreview(previewEl?: HTMLElement) { + const el = previewEl || this.contentEl.querySelector('.background-preview'); + if (!el) return; + + let style = ''; + switch (this.backgroundType) { + case 'color': + style = `background-color: ${this.backgroundColor};`; + break; + case 'css': + style = this.backgroundCssStyle; + break; + } + + el.setAttribute('style', style); + } + + private generateStyle() { + let style = 'box-sizing: border-box; margin: 0; padding: 0; '; + + switch (this.backgroundType) { + case 'color': + style += `background-color: ${this.backgroundColor};`; + break; + case 'css': + style += this.backgroundCssStyle; + break; + } + + this.background.style = style; + } + + onClose() { + const { contentEl } = this; + contentEl.empty(); + } +} \ No newline at end of file diff --git a/src/settings/CreateTemplateModal.ts b/src/settings/CreateTemplateModal.ts index eb4c22f..0a09f73 100644 --- a/src/settings/CreateTemplateModal.ts +++ b/src/settings/CreateTemplateModal.ts @@ -1,6 +1,7 @@ import { App, Modal, Setting, Notice, setIcon, ColorComponent } from 'obsidian'; import MPPlugin from '../main'; import { Template } from '../templateManager'; +import { TemplatePreviewModal } from './templatePreviewModal'; export class CreateTemplateModal extends Modal { private template: Template; @@ -180,6 +181,16 @@ export class CreateTemplateModal extends Modal { // 保存和取消按钮 const buttonContainer = contentEl.createDiv('modal-button-container'); new Setting(buttonContainer) + .addButton(btn => btn + .setButtonText('预览') + .onClick(() => { + // 打开预览模式 + const previewModal = new TemplatePreviewModal(this.app, this.template, this.plugin.templateManager); + previewModal.open(); + })) + .addButton(btn => btn + .setButtonText('取消') + .onClick(() => this.close())) .addButton(btn => btn .setButtonText('保存') .setCta() @@ -187,10 +198,7 @@ export class CreateTemplateModal extends Modal { if (await this.validateAndSubmit()) { this.close(); } - })) - .addButton(btn => btn - .setButtonText('取消') - .onClick(() => this.close())); + })); this.nameInput.addEventListener('keydown', async (e) => { if (e.key === 'Enter' && !e.shiftKey) { diff --git a/src/settings/MPSettingTab.ts b/src/settings/MPSettingTab.ts index 9e25983..901f830 100644 --- a/src/settings/MPSettingTab.ts +++ b/src/settings/MPSettingTab.ts @@ -2,8 +2,9 @@ import { App, PluginSettingTab, Setting, setIcon, Notice } from 'obsidian'; import MPPlugin from '../main'; // 修改插件名以匹配类名 import { CreateTemplateModal } from './CreateTemplateModal'; import { CreateFontModal } from './CreateFontModal'; -import { ConfirmModal } from './ConfirmModal'; // 添加确认模态框导入 - +import { CreateBackgroundModal } from './CreateBackgroundModal'; // 添加导入 +import { ConfirmModal } from './ConfirmModal'; +import { TemplatePreviewModal } from './templatePreviewModal'; // 添加导入 export class MPSettingTab extends PluginSettingTab { plugin: MPPlugin; // 修改插件类型以匹配类名 private expandedSections: Set = new Set(); @@ -16,15 +17,15 @@ export class MPSettingTab extends PluginSettingTab { private createSection(containerEl: HTMLElement, title: string, renderContent: (contentEl: HTMLElement) => void) { const section = containerEl.createDiv('settings-section'); const header = section.createDiv('settings-section-header'); - + const toggle = header.createSpan('settings-section-toggle'); setIcon(toggle, 'chevron-right'); - + header.createEl('h4', { text: title }); - + const content = section.createDiv('settings-section-content'); renderContent(content); - + header.addEventListener('click', () => { const isExpanded = !section.hasClass('is-expanded'); section.toggleClass('is-expanded', isExpanded); @@ -35,13 +36,13 @@ export class MPSettingTab extends PluginSettingTab { this.expandedSections.delete(title); } }); - + if (this.expandedSections.has(title) || (!containerEl.querySelector('.settings-section'))) { section.addClass('is-expanded'); setIcon(toggle, 'chevron-down'); this.expandedSections.add(title); } - + return section; } @@ -54,9 +55,206 @@ export class MPSettingTab extends PluginSettingTab { this.createSection(containerEl, '基本设置', el => this.renderBasicSettings(el)); this.createSection(containerEl, '模板设置', el => this.renderTemplateSettings(el)); + this.createSection(containerEl, '背景设置', el => this.renderBackgroundSettings(el)); } - private renderTemplateSettings(containerEl: HTMLElement): void { + private renderBasicSettings(containerEl: HTMLElement): void { + // 字体管理区域 + const fontSection = containerEl.createDiv('mp-settings-subsection'); + const fontHeader = fontSection.createDiv('mp-settings-subsection-header'); + const fontToggle = fontHeader.createSpan('mp-settings-subsection-toggle'); + setIcon(fontToggle, 'chevron-right'); + + fontHeader.createEl('h3', { text: '字体管理' }); + + const fontContent = fontSection.createDiv('mp-settings-subsection-content'); + + // 折叠/展开逻辑 + fontHeader.addEventListener('click', () => { + const isExpanded = !fontSection.hasClass('is-expanded'); + fontSection.toggleClass('is-expanded', isExpanded); + setIcon(fontToggle, isExpanded ? 'chevron-down' : 'chevron-right'); + }); + + // 字体列表 + const fontList = fontContent.createDiv('font-management'); + this.plugin.settingsManager.getFontOptions().forEach(font => { + const fontItem = fontList.createDiv('font-item'); + const setting = new Setting(fontItem) + .setName(font.label) + .setDesc(font.value); + + // 只为非预设字体添加编辑和删除按钮 + if (!font.isPreset) { + setting + .addExtraButton(btn => + btn.setIcon('pencil') + .setTooltip('编辑') + .onClick(() => { + new CreateFontModal( + this.app, + async (updatedFont) => { + await this.plugin.settingsManager.updateFont(font.value, updatedFont); + this.display(); + new Notice('请重启 Obsidian 或重新加载以使更改生效'); + }, + font + ).open(); + })) + .addExtraButton(btn => + btn.setIcon('trash') + .setTooltip('删除') + .onClick(() => { + // 新增确认模态框 + new ConfirmModal( + this.app, + '确认删除字体', + `确定要删除「${font.label}」字体配置吗?`, + async () => { + await this.plugin.settingsManager.removeFont(font.value); + this.display(); + new Notice('请重启 Obsidian 或重新加载以使更改生效'); + } + ).open(); + })); + } + }); + + // 添加新字体按钮 + new Setting(fontContent) + .addButton(btn => btn + .setButtonText('+ 添加字体') + .setCta() + .onClick(() => { + new CreateFontModal( + this.app, + async (newFont) => { + await this.plugin.settingsManager.addCustomFont(newFont); + this.display(); + new Notice('请重启 Obsidian 或重新加载以使更改生效'); + } + ).open(); + })); + } + + private renderTemplateSettings(containerEl: HTMLElement): void { + // 模板显示设置部分 - 从基本设置移动到这里 + const templateVisibilitySection = containerEl.createDiv('mp-settings-subsection'); + const templateVisibilityHeader = templateVisibilitySection.createDiv('mp-settings-subsection-header'); + + const templateVisibilityToggle = templateVisibilityHeader.createSpan('mp-settings-subsection-toggle'); + setIcon(templateVisibilityToggle, 'chevron-right'); + + templateVisibilityHeader.createEl('h3', { text: '模板显示设置' }); + + const templateVisibilityContent = templateVisibilitySection.createDiv('mp-settings-subsection-content'); + + // 折叠/展开逻辑 + templateVisibilityHeader.addEventListener('click', () => { + const isExpanded = !templateVisibilitySection.hasClass('is-expanded'); + templateVisibilitySection.toggleClass('is-expanded', isExpanded); + setIcon(templateVisibilityToggle, isExpanded ? 'chevron-down' : 'chevron-right'); + }); + + // 模板选择容器 + const templateSelectionContainer = templateVisibilityContent.createDiv('template-selection-container'); + + // 左侧:所有模板列表 + const allTemplatesContainer = templateSelectionContainer.createDiv('all-templates-container'); + allTemplatesContainer.createEl('h4', { text: '隐藏模板' }); + const allTemplatesList = allTemplatesContainer.createDiv('templates-list'); + + // 中间:控制按钮 + const controlButtonsContainer = templateSelectionContainer.createDiv('control-buttons-container'); + const addButton = controlButtonsContainer.createEl('button', { text: '>' }); + const removeButton = controlButtonsContainer.createEl('button', { text: '<' }); + + // 右侧:显示的模板列表 + const visibleTemplatesContainer = templateSelectionContainer.createDiv('visible-templates-container'); + visibleTemplatesContainer.createEl('h4', { text: '显示模板' }); + const visibleTemplatesList = visibleTemplatesContainer.createDiv('templates-list'); + + // 获取所有模板 + const allTemplates = this.plugin.settingsManager.getAllTemplates(); + + // 渲染模板列表 + const renderTemplateLists = () => { + // 清空列表 + allTemplatesList.empty(); + visibleTemplatesList.empty(); + + // 填充左侧列表(所有未显示的模板) + allTemplates + .filter(template => template.isVisible === false) + .forEach(template => { + const templateItem = allTemplatesList.createDiv('template-list-item'); + templateItem.textContent = template.name; + templateItem.dataset.templateId = template.id; + + // 点击选中/取消选中 + templateItem.addEventListener('click', () => { + templateItem.toggleClass('selected', !templateItem.hasClass('selected')); + }); + }); + + // 填充右侧列表(所有显示的模板) + allTemplates + .filter(template => template.isVisible !== false) // 默认显示 + .forEach(template => { + const templateItem = visibleTemplatesList.createDiv('template-list-item'); + templateItem.textContent = template.name; + templateItem.dataset.templateId = template.id; + + // 点击选中/取消选中 + templateItem.addEventListener('click', () => { + templateItem.toggleClass('selected', !templateItem.hasClass('selected')); + }); + }); + }; + + // 初始渲染 + renderTemplateLists(); + + // 添加按钮事件 + addButton.addEventListener('click', async () => { + const selectedItems = Array.from(allTemplatesList.querySelectorAll('.template-list-item.selected')); + if (selectedItems.length === 0) return; + + for (const item of selectedItems) { + const templateId = (item as HTMLElement).dataset.templateId; + if (!templateId) continue; + + const template = allTemplates.find(t => t.id === templateId); + if (template) { + template.isVisible = true; + await this.plugin.settingsManager.updateTemplate(templateId, template); + } + } + + renderTemplateLists(); + new Notice('请重启 Obsidian 或重新加载以使更改生效'); + }); + + // 移除按钮事件 + removeButton.addEventListener('click', async () => { + const selectedItems = Array.from(visibleTemplatesList.querySelectorAll('.template-list-item.selected')); + if (selectedItems.length === 0) return; + + for (const item of selectedItems) { + const templateId = (item as HTMLElement).dataset.templateId; + if (!templateId) continue; + + const template = allTemplates.find(t => t.id === templateId); + if (template) { + template.isVisible = false; + await this.plugin.settingsManager.updateTemplate(templateId, template); + } + } + + renderTemplateLists(); + new Notice('请重启 Obsidian 或重新加载以使更改生效'); + }); + // 模板管理区域 const templateList = containerEl.createDiv('template-management'); // 渲染自定义模板 @@ -69,6 +267,12 @@ export class MPSettingTab extends PluginSettingTab { .setName(template.name) .setDesc(template.description) .addExtraButton(btn => + btn.setIcon('eye') + .setTooltip('预览') + .onClick(() => { + new TemplatePreviewModal(this.app, template, this.plugin.templateManager).open(); // 修改为使用预览模态框 + })) + .addExtraButton(btn => btn.setIcon('pencil') .setTooltip('编辑') .onClick(() => { @@ -83,7 +287,7 @@ export class MPSettingTab extends PluginSettingTab { template ).open(); })) - .addExtraButton(btn => + .addExtraButton(btn => btn.setIcon('trash') .setTooltip('删除') .onClick(() => { @@ -100,7 +304,7 @@ export class MPSettingTab extends PluginSettingTab { ).open(); })); }); - + // 添加新模板按钮 new Setting(containerEl) .addButton(btn => btn @@ -119,198 +323,184 @@ export class MPSettingTab extends PluginSettingTab { })); } - private renderBasicSettings(containerEl: HTMLElement): void { - // 模板显示设置部分 - const templateVisibilitySection = containerEl.createDiv('mp-settings-subsection'); - const templateVisibilityHeader = templateVisibilitySection.createDiv('mp-settings-subsection-header'); - - const templateVisibilityToggle = templateVisibilityHeader.createSpan('mp-settings-subsection-toggle'); - setIcon(templateVisibilityToggle, 'chevron-right'); - - templateVisibilityHeader.createEl('h3', { text: '模板显示设置' }); - - const templateVisibilityContent = templateVisibilitySection.createDiv('mp-settings-subsection-content'); - + private renderBackgroundSettings(containerEl: HTMLElement): void { + // 背景显示设置部分 + const backgroundVisibilitySection = containerEl.createDiv('mp-settings-subsection'); + const backgroundVisibilityHeader = backgroundVisibilitySection.createDiv('mp-settings-subsection-header'); + + const backgroundVisibilityToggle = backgroundVisibilityHeader.createSpan('mp-settings-subsection-toggle'); + setIcon(backgroundVisibilityToggle, 'chevron-right'); + + backgroundVisibilityHeader.createEl('h3', { text: '背景显示设置' }); + + const backgroundVisibilityContent = backgroundVisibilitySection.createDiv('mp-settings-subsection-content'); + // 折叠/展开逻辑 - templateVisibilityHeader.addEventListener('click', () => { - const isExpanded = !templateVisibilitySection.hasClass('is-expanded'); - templateVisibilitySection.toggleClass('is-expanded', isExpanded); - setIcon(templateVisibilityToggle, isExpanded ? 'chevron-down' : 'chevron-right'); + backgroundVisibilityHeader.addEventListener('click', () => { + const isExpanded = !backgroundVisibilitySection.hasClass('is-expanded'); + backgroundVisibilitySection.toggleClass('is-expanded', isExpanded); + setIcon(backgroundVisibilityToggle, isExpanded ? 'chevron-down' : 'chevron-right'); }); - - // 模板选择容器 - const templateSelectionContainer = templateVisibilityContent.createDiv('template-selection-container'); - - // 左侧:所有模板列表 - const allTemplatesContainer = templateSelectionContainer.createDiv('all-templates-container'); - allTemplatesContainer.createEl('h4', { text: '隐藏模板' }); - const allTemplatesList = allTemplatesContainer.createDiv('templates-list'); - + + // 背景选择容器 + const backgroundSelectionContainer = backgroundVisibilityContent.createDiv('background-selection-container'); + + // 左侧:所有背景列表 + const allBackgroundsContainer = backgroundSelectionContainer.createDiv('all-backgrounds-container'); + allBackgroundsContainer.createEl('h4', { text: '隐藏背景' }); + const allBackgroundsList = allBackgroundsContainer.createDiv('backgrounds-list'); + // 中间:控制按钮 - const controlButtonsContainer = templateSelectionContainer.createDiv('control-buttons-container'); + const controlButtonsContainer = backgroundSelectionContainer.createDiv('control-buttons-container'); const addButton = controlButtonsContainer.createEl('button', { text: '>' }); const removeButton = controlButtonsContainer.createEl('button', { text: '<' }); - - // 右侧:显示的模板列表 - const visibleTemplatesContainer = templateSelectionContainer.createDiv('visible-templates-container'); - visibleTemplatesContainer.createEl('h4', { text: '显示模板' }); - const visibleTemplatesList = visibleTemplatesContainer.createDiv('templates-list'); - - // 获取所有模板 - const allTemplates = this.plugin.settingsManager.getAllTemplates(); - - // 渲染模板列表 - const renderTemplateLists = () => { + + // 右侧:显示的背景列表 + const visibleBackgroundsContainer = backgroundSelectionContainer.createDiv('visible-backgrounds-container'); + visibleBackgroundsContainer.createEl('h4', { text: '显示背景' }); + const visibleBackgroundsList = visibleBackgroundsContainer.createDiv('backgrounds-list'); + + // 获取所有背景 + const allBackgrounds = this.plugin.settingsManager.getAllBackgrounds(); + + // 渲染背景列表 + const renderBackgroundLists = () => { // 清空列表 - allTemplatesList.empty(); - visibleTemplatesList.empty(); - - // 填充左侧列表(所有未显示的模板) - allTemplates - .filter(template => template.isVisible === false) - .forEach(template => { - const templateItem = allTemplatesList.createDiv('template-list-item'); - templateItem.textContent = template.name; - templateItem.dataset.templateId = template.id; - + allBackgroundsList.empty(); + visibleBackgroundsList.empty(); + + // 填充左侧列表(所有未显示的背景) + allBackgrounds + .filter(background => background.isVisible === false) + .forEach(background => { + const backgroundItem = allBackgroundsList.createDiv('background-list-item'); + backgroundItem.textContent = background.name; + backgroundItem.dataset.backgroundId = background.id; + // 点击选中/取消选中 - templateItem.addEventListener('click', () => { - templateItem.toggleClass('selected', !templateItem.hasClass('selected')); + backgroundItem.addEventListener('click', () => { + backgroundItem.toggleClass('selected', !backgroundItem.hasClass('selected')); }); }); - - // 填充右侧列表(所有显示的模板) - allTemplates - .filter(template => template.isVisible !== false) // 默认显示 - .forEach(template => { - const templateItem = visibleTemplatesList.createDiv('template-list-item'); - templateItem.textContent = template.name; - templateItem.dataset.templateId = template.id; - + + // 填充右侧列表(所有显示的背景) + allBackgrounds + .filter(background => background.isVisible !== false) // 默认显示 + .forEach(background => { + const backgroundItem = visibleBackgroundsList.createDiv('background-list-item'); + backgroundItem.textContent = background.name; + backgroundItem.dataset.backgroundId = background.id; + // 点击选中/取消选中 - templateItem.addEventListener('click', () => { - templateItem.toggleClass('selected', !templateItem.hasClass('selected')); + backgroundItem.addEventListener('click', () => { + backgroundItem.toggleClass('selected', !backgroundItem.hasClass('selected')); }); }); }; - + // 初始渲染 - renderTemplateLists(); - + renderBackgroundLists(); + // 添加按钮事件 addButton.addEventListener('click', async () => { - const selectedItems = Array.from(allTemplatesList.querySelectorAll('.template-list-item.selected')); + const selectedItems = Array.from(allBackgroundsList.querySelectorAll('.background-list-item.selected')); if (selectedItems.length === 0) return; - + for (const item of selectedItems) { - const templateId = (item as HTMLElement).dataset.templateId; - if (!templateId) continue; - - const template = allTemplates.find(t => t.id === templateId); - if (template) { - template.isVisible = true; - await this.plugin.settingsManager.updateTemplate(templateId, template); + const backgroundId = (item as HTMLElement).dataset.backgroundId; + if (!backgroundId) continue; + + const background = allBackgrounds.find(b => b.id === backgroundId); + if (background) { + background.isVisible = true; + await this.plugin.settingsManager.updateBackground(backgroundId, background); } } - - renderTemplateLists(); - new Notice('请重启 Obsidian 或重新加载以使更改生效'); + + renderBackgroundLists(); + new Notice('背景显示设置已更新'); }); - + // 移除按钮事件 removeButton.addEventListener('click', async () => { - const selectedItems = Array.from(visibleTemplatesList.querySelectorAll('.template-list-item.selected')); + const selectedItems = Array.from(visibleBackgroundsList.querySelectorAll('.background-list-item.selected')); if (selectedItems.length === 0) return; - + for (const item of selectedItems) { - const templateId = (item as HTMLElement).dataset.templateId; - if (!templateId) continue; - - const template = allTemplates.find(t => t.id === templateId); - if (template) { - template.isVisible = false; - await this.plugin.settingsManager.updateTemplate(templateId, template); + const backgroundId = (item as HTMLElement).dataset.backgroundId; + if (!backgroundId) continue; + + const background = allBackgrounds.find(b => b.id === backgroundId); + if (background) { + background.isVisible = false; + await this.plugin.settingsManager.updateBackground(backgroundId, background); } } - - renderTemplateLists(); - new Notice('请重启 Obsidian 或重新加载以使更改生效'); + + renderBackgroundLists(); + new Notice('背景显示设置已更新'); }); - - - // 字体管理区域 - const fontSection = containerEl.createDiv('mp-settings-subsection'); - const fontHeader = fontSection.createDiv('mp-settings-subsection-header'); - const fontToggle = fontHeader.createSpan('mp-settings-subsection-toggle'); - setIcon(fontToggle, 'chevron-right'); - - fontHeader.createEl('h3', { text: '字体管理' }); - - const fontContent = fontSection.createDiv('mp-settings-subsection-content'); - - // 折叠/展开逻辑 - fontHeader.addEventListener('click', () => { - const isExpanded = !fontSection.hasClass('is-expanded'); - fontSection.toggleClass('is-expanded', isExpanded); - setIcon(fontToggle, isExpanded ? 'chevron-down' : 'chevron-right'); - }); - - // 字体列表 - const fontList = fontContent.createDiv('font-management'); - this.plugin.settingsManager.getFontOptions().forEach(font => { - const fontItem = fontList.createDiv('font-item'); - const setting = new Setting(fontItem) - .setName(font.label) - .setDesc(font.value); - - // 只为非预设字体添加编辑和删除按钮 - if (!font.isPreset) { - setting - .addExtraButton(btn => + + // 背景管理区域 + const backgroundList = containerEl.createDiv('background-management'); + + // 渲染自定义背景 + backgroundList.createEl('h4', { text: '自定义背景', cls: 'background-custom-header' }); + this.plugin.settingsManager.getAllBackgrounds() + .filter(background => !background.isPreset) + .forEach(background => { + const backgroundItem = backgroundList.createDiv('background-item'); + new Setting(backgroundItem) + .setName(background.name) + .addExtraButton(btn => btn.setIcon('pencil') .setTooltip('编辑') .onClick(() => { - new CreateFontModal( + // 使用背景编辑模态框 + new CreateBackgroundModal( this.app, - async (updatedFont) => { - await this.plugin.settingsManager.updateFont(font.value, updatedFont); + async (updatedBackground) => { + await this.plugin.settingsManager.updateBackground(background.id, updatedBackground); this.display(); - new Notice('请重启 Obsidian 或重新加载以使更改生效'); + new Notice('背景已更新'); }, - font + background ).open(); })) - .addExtraButton(btn => + .addExtraButton(btn => btn.setIcon('trash') .setTooltip('删除') .onClick(() => { - // 新增确认模态框 new ConfirmModal( this.app, - '确认删除字体', - `确定要删除「${font.label}」字体配置吗?`, + '确认删除背景', + `确定要删除「${background.name}」背景吗?此操作不可恢复。`, async () => { - await this.plugin.settingsManager.removeFont(font.value); + await this.plugin.settingsManager.removeBackground(background.id); this.display(); - new Notice('请重启 Obsidian 或重新加载以使更改生效'); + new Notice('背景已删除'); } ).open(); })); - } - }); - - // 添加新字体按钮 - new Setting(fontContent) + + // 添加背景预览 + const previewEl = backgroundItem.createDiv('background-preview'); + previewEl.setAttribute('style', background.style); + }); + + // 添加新背景按钮 + new Setting(containerEl) .addButton(btn => btn - .setButtonText('+ 添加字体') + .setButtonText('+ 新建背景') .setCta() .onClick(() => { - new CreateFontModal( + // 使用新的背景创建模态框 + new CreateBackgroundModal( this.app, - async (newFont) => { - await this.plugin.settingsManager.addCustomFont(newFont); + async (newBackground) => { + await this.plugin.settingsManager.addCustomBackground(newBackground); this.display(); - new Notice('请重启 Obsidian 或重新加载以使更改生效'); + new Notice('背景已创建'); } ).open(); })); diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 2ebbdd1..2a05bc7 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -1,4 +1,5 @@ import { Template } from '../templateManager'; +import { Background } from '../backgroundManager'; interface MPSettings { backgroundId: string; @@ -7,6 +8,8 @@ interface MPSettings { fontSize: number; templates: Template[]; customTemplates: Template[]; + backgrounds: Background[]; + customBackgrounds: Background[]; customFonts: { value: string; label: string; isPreset?: boolean }[]; } @@ -17,6 +20,8 @@ const DEFAULT_SETTINGS: MPSettings = { fontSize: 16, templates: [], customTemplates: [], + backgrounds: [], + customBackgrounds: [], customFonts: [ { value: 'Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif', @@ -58,6 +63,21 @@ export class SettingsManager { if (!savedData.customFonts) { savedData.customFonts = DEFAULT_SETTINGS.customFonts; } + // 加载背景设置 + if (!savedData.backgrounds || savedData.backgrounds.length === 0) { + const { backgrounds } = await import('../backgrounds'); + savedData.backgrounds = backgrounds.backgrounds.map(background => ({ + ...background, + isPreset: true, + isVisible: true + })); + } + if (!savedData.customBackgrounds) { + savedData.customBackgrounds = []; + } + if (!savedData.customFonts) { + savedData.customFonts = DEFAULT_SETTINGS.customFonts; + } this.settings = Object.assign({}, DEFAULT_SETTINGS, savedData); } @@ -155,4 +175,62 @@ export class SettingsManager { await this.saveSettings(); } } + + // 背景相关方法 + getAllBackgrounds(): Background[] { + return [...this.settings.backgrounds, ...this.settings.customBackgrounds]; + } + + getVisibleBackgrounds(): Background[] { + return this.getAllBackgrounds().filter(background => background.isVisible !== false); + } + + getBackground(backgroundId: string): Background | undefined { + return this.settings.backgrounds.find(background => background.id === backgroundId) + || this.settings.customBackgrounds.find(background => background.id === backgroundId); + } + + async addCustomBackground(background: Background) { + background.isPreset = false; + background.isVisible = true; // 默认可见 + this.settings.customBackgrounds.push(background); + await this.saveSettings(); + } + + async updateBackground(backgroundId: string, updatedBackground: Partial) { + const presetBackgroundIndex = this.settings.backgrounds.findIndex(b => b.id === backgroundId); + if (presetBackgroundIndex !== -1) { + this.settings.backgrounds[presetBackgroundIndex] = { + ...this.settings.backgrounds[presetBackgroundIndex], + ...updatedBackground + }; + await this.saveSettings(); + return true; + } + + const customBackgroundIndex = this.settings.customBackgrounds.findIndex(b => b.id === backgroundId); + if (customBackgroundIndex !== -1) { + this.settings.customBackgrounds[customBackgroundIndex] = { + ...this.settings.customBackgrounds[customBackgroundIndex], + ...updatedBackground + }; + await this.saveSettings(); + return true; + } + + return false; + } + + async removeBackground(backgroundId: string): Promise { + const background = this.getBackground(backgroundId); + if (background && !background.isPreset) { + this.settings.customBackgrounds = this.settings.customBackgrounds.filter(b => b.id !== backgroundId); + if (this.settings.backgroundId === backgroundId) { + this.settings.backgroundId = 'default'; + } + await this.saveSettings(); + return true; + } + return false; + } } \ No newline at end of file diff --git a/src/settings/templatePreviewModal.ts b/src/settings/templatePreviewModal.ts new file mode 100644 index 0000000..288d88c --- /dev/null +++ b/src/settings/templatePreviewModal.ts @@ -0,0 +1,75 @@ +import { App, Modal } from 'obsidian'; +import { TemplateManager } from '../templateManager'; + +export class TemplatePreviewModal extends Modal { + private template: any; + private templateManager: TemplateManager; + + constructor(app: App, template: any, templateManager: TemplateManager) { + super(app); + this.template = template; + this.templateManager = templateManager; + } + + onOpen() { + const { contentEl } = this; + contentEl.empty(); + contentEl.addClass('template-preview-modal'); + + // 添加标题 + contentEl.createEl('h2', { text: `模板预览: ${this.template.name}`, cls: 'mp-template-title' }); + + // 添加预览区域 + const container = contentEl.createDiv('tp-mp-preview-area'); + const content = container.createDiv('tp-mp-content-section'); + + // 标题样式 + content.createEl('h2', { text: '探索夜半插件的无限可能'}); + content.createEl('h3', { text: '探索我的插件,让您的笔记发布变得更加轻松!'}); + + // 段落样式 + const paragraph1 = content.createEl('p'); + paragraph1.createEl('span', { text: '插件为您提供各种' }); + paragraph1.createEl('strong', { text: '优雅的操作,' }); + paragraph1.createEl('span', { text: '助您轻松发布笔记。' }); + + const paragraph2 = content.createEl('p'); + paragraph2.createEl('span', { text: '通过插件,您可以快速组织内容,' }); + paragraph2.createEl('em', { text: '提升工作效率。' }); + + content.createEl('hr'); + + // 列表样式 + const list = content.createEl('ol'); + list.createEl('li', { text: '轻松定制模板样式' }); + list.createEl('li', { text: '实时预览模板效果' }); + + // 引用样式 + const quote = content.createEl('blockquote'); + quote.createEl('p', { text: '“让笔记发帖变得如此简单。”' }); + + // 代码样式 + const codeBlock = content.createEl('pre'); + const header = codeBlock.createDiv('mp-code-header'); // 添加窗口按钮 + for (let i = 0; i < 3; i++) { + const dot = document.createElement('span'); + dot.className = 'mp-code-dot'; + header.appendChild(dot); + } + codeBlock.insertBefore(header, codeBlock.firstChild); + codeBlock.createEl('code', { text: 'console.log("欢迎使用夜半插件!");' }); + + // 添加打赏引导文案 + content.createEl('strong', { text: '如果您觉得我的插件对您有帮助,请打赏支持我。'}); + + // 分隔线样式 + content.createEl('hr'); + + this.templateManager.applyTemplate(container, this.template); + } + + onClose() { + const { contentEl } = this; + contentEl.empty(); + } +} \ No newline at end of file diff --git a/src/styles/index.css b/src/styles/index.css index ffe70eb..8d9f1c9 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -2,4 +2,6 @@ @import url('./view/preview.css'); @import url('./settings/settings.css'); @import url('./settings/font-modal.css'); -@import url('./settings/template-modal.css'); \ No newline at end of file +@import url('./settings/template-modal.css'); +@import url('./settings/template-preview-modal.css'); +@import url('./settings/background-modal.css'); \ No newline at end of file diff --git a/src/styles/settings/background-modal.css b/src/styles/settings/background-modal.css new file mode 100644 index 0000000..16c9f71 --- /dev/null +++ b/src/styles/settings/background-modal.css @@ -0,0 +1,71 @@ +.mp-background-modal { + padding: 20px; + max-width: 600px; +} + +.mp-background-modal h2 { + margin-top: 0; + margin-bottom: 20px; + text-align: center; + color: var(--text-normal); +} + +.mp-background-modal .background-basic-section, +.mp-background-modal .background-type-specific-section, +.mp-background-modal .background-preview-section, +.mp-background-modal .background-button-section { + margin-bottom: 20px; +} + +.mp-background-modal .background-preview-section h3 { + margin-bottom: 10px; + font-size: 1.1em; + color: var(--text-normal); +} + +.mp-background-modal .background-preview { + height: 120px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid var(--background-modifier-border); + overflow: hidden; +} + +.mp-background-modal .background-preview span { + padding: 5px 10px; + background-color: rgba(255, 255, 255, 0.7); + border-radius: 4px; + font-size: 0.9em; + color: var(--text-normal); +} + +.mp-background-modal .is-hidden { + display: none; +} + +.mp-background-modal .background-image-preview { + margin-top: 10px; + max-height: 150px; + overflow: hidden; + border-radius: 6px; + border: 1px solid var(--background-modifier-border); +} + +.mp-background-modal .background-image-preview img { + width: 100%; + height: auto; + object-fit: cover; +} + +.mp-background-modal .setting-item { + border-top: none; + padding: 12px 0; +} + +.mp-background-modal textarea { + width: 100%; + font-family: var(--font-monospace); + font-size: 0.9em; +} \ No newline at end of file diff --git a/src/styles/settings/settings.css b/src/styles/settings/settings.css index 77bcaea..c6cc95f 100644 --- a/src/styles/settings/settings.css +++ b/src/styles/settings/settings.css @@ -243,7 +243,7 @@ /* 设置子区域折叠样式 */ .mp-settings .mp-settings-subsection { - margin-bottom: 1.5rem; + margin: 0 12px; border: 1px solid var(--background-modifier-border); border-radius: 8px; overflow-y: auto; @@ -283,4 +283,128 @@ .mp-settings .mp-settings-subsection.is-expanded .mp-settings-subsection-content { display: block; +} + +/* 背景管理区域样式 */ +.mp-settings .background-management { + max-height: 400px; + overflow-y: auto; + padding: 0 12px; +} + +/* 背景管理滚动条样式 */ +.mp-settings .background-management::-webkit-scrollbar { + width: 8px; +} + +.mp-settings .background-management::-webkit-scrollbar-track { + background: var(--background-primary); + border-radius: 4px; +} + +.mp-settings .background-management::-webkit-scrollbar-thumb { + background: var(--background-modifier-border); + border-radius: 4px; +} + +.mp-settings .background-management::-webkit-scrollbar-thumb:hover { + background: var(--background-modifier-border-hover); +} + +/* 背景项目样式 */ +.mp-settings .background-item { + margin-bottom: 0.8rem; + padding: 1rem; + border-radius: 8px; + background-color: var(--background-primary); + border: 1px solid var(--background-modifier-border); + transition: all 0.3s ease; +} + +.mp-settings .background-item:hover { + box-shadow: 0 4px 12px var(--background-modifier-box-shadow); +} + +.mp-settings .background-item .setting-item { + border: none; + padding: 12px; +} + +/* 背景预览区域 */ +.mp-settings .background-preview { + height: 20px; + border-radius: 5px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid var(--background-modifier-border); +} + +/* 背景显示设置样式 */ +.background-selection-container { + display: flex; + margin-top: 10px; + gap: 15px; + align-items: stretch; +} + +.all-backgrounds-container, .visible-backgrounds-container { + flex: 1; + border: 1px solid var(--background-modifier-border); + border-radius: 8px; + padding: 15px; + background-color: var(--background-primary); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); +} + +.all-backgrounds-container h4, .visible-backgrounds-container h4 { + margin: 0 0 15px; + padding-bottom: 10px; + border-bottom: 1px solid var(--background-modifier-border); + font-size: 1.1em; + color: var(--text-normal); + text-align: center; +} + +.backgrounds-list { + max-height: 250px; + overflow-y: auto; + padding: 5px; +} + +.background-list-item { + padding: 10px; + margin-bottom: 8px; + cursor: pointer; + border-radius: 6px; + background-color: var(--background-secondary); + transition: all 0.2s ease; +} + +.background-list-item:hover { + background-color: var(--background-modifier-hover); +} + +.background-list-item.selected { + background-color: var(--interactive-accent); + color: var(--text-on-accent); +} + +/* 背景列表滚动条样式 */ +.backgrounds-list::-webkit-scrollbar { + width: 6px; +} + +.backgrounds-list::-webkit-scrollbar-track { + background: var(--background-primary); + border-radius: 3px; +} + +.backgrounds-list::-webkit-scrollbar-thumb { + background: var(--background-modifier-border); + border-radius: 3px; +} + +.backgrounds-list::-webkit-scrollbar-thumb:hover { + background: var(--background-modifier-border-hover); } \ No newline at end of file diff --git a/src/styles/settings/template-preview-modal.css b/src/styles/settings/template-preview-modal.css new file mode 100644 index 0000000..5935942 --- /dev/null +++ b/src/styles/settings/template-preview-modal.css @@ -0,0 +1,83 @@ +.template-preview-modal { + padding: 20px; + background-color: #f9f9f9; + border-radius: 8px; +} +.template-preview-modal .mp-template-title{ + text-align: center; +} + +/* ===== 预览区域 ===== */ +.tp-mp-preview-area { + padding: 10px 20px 20px 20px; + margin: 10px; + height: calc(100% - 180px); + overflow-y: auto; + background: #fcfcfc; + flex: 1; + border-radius: 12px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.02); + border: 1px solid rgba(82, 144, 220, 0.08); +} + +/* 链接样式 */ +.tp-mp-content-section a { + color: var(--text-accent); + text-decoration: none; +} + +/* 表格样式 */ +.tp-mp-content-section table { + border-collapse: collapse; + margin: 1em 0; + width: 100%; +} + +.tp-mp-content-section th, +.tp-mp-content-section td { + border: 1px solid var(--background-modifier-border); + padding: 8px; +} + +/* 分割线样式 */ +.tp-mp-content-section hr { + border: none; + border-top: 1px solid var(--background-modifier-border); + margin: 20px 0; +} + +/* 删除线样式 */ +.tp-mp-content-section del { + text-decoration: line-through; +} + +/* 任务列表样式 */ +.tp-mp-content-section .task-list-item { + list-style: none; +} + +.tp-mp-content-section .task-list-item input[type="checkbox"] { + margin-right: 6px; +} + +/* 脚注样式 */ +.tp-mp-content-section .footnote-ref, +.tp-mp-content-section .footnote-backref { + color: var(--text-accent); + text-decoration: none; +} + +/* 图片样式 */ +.tp-mp-content-section img { + max-width: 100%; + height: auto; + display: block; + margin: 1em auto; +} + +/* 引用块样式 */ +.tp-mp-content-section blockquote p { + margin: 0; + padding: 0; + line-height: inherit; +} diff --git a/src/templateManager.ts b/src/templateManager.ts index 969491b..cf9f12f 100644 --- a/src/templateManager.ts +++ b/src/templateManager.ts @@ -98,8 +98,8 @@ export class TemplateManager { this.currentFontSize = size; } - public applyTemplate(element: HTMLElement): void { - const styles = this.currentTemplate.styles; + public applyTemplate(element: HTMLElement, template?: Template): void { + const styles = template ? template.styles : this.currentTemplate.styles; // 应用标题样式 ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach(tag => { element.querySelectorAll(tag).forEach(el => { diff --git a/src/utils/nanoid.ts b/src/utils/nanoid.ts new file mode 100644 index 0000000..5a7ab6a --- /dev/null +++ b/src/utils/nanoid.ts @@ -0,0 +1 @@ +export { nanoid } from 'nanoid'; \ No newline at end of file diff --git a/src/view.ts b/src/view.ts index 7b64df9..2590766 100644 --- a/src/view.ts +++ b/src/view.ts @@ -30,7 +30,7 @@ export class MPView extends ItemView { super(leaf); this.templateManager = templateManager; this.settingsManager = settingsManager; - this.backgroundManager = new BackgroundManager(); + this.backgroundManager = new BackgroundManager(this.settingsManager); } getViewType() { @@ -68,7 +68,7 @@ export class MPView extends ItemView { // 添加背景选择器 const backgroundOptions = [ { value: '', label: '无背景' }, - ...(this.backgroundManager.getAllBackgrounds()?.map(bg => ({ + ...(this.settingsManager.getVisibleBackgrounds()?.map(bg => ({ value: bg.id, label: bg.name })) || [])