新增模板预览及背景自定义

This commit is contained in:
Yeban8090 2025-04-21 01:27:17 +08:00
parent 914411bac3
commit 70db8a3327
16 changed files with 1395 additions and 179 deletions

25
package-lock.json generated
View file

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

View file

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

View file

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

View file

@ -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)
);
// 自动打开视图但不聚焦

View file

@ -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<string, CssTemplate> = {
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();
}
}

View file

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

View file

@ -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<string> = 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();
}));

View file

@ -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<Background>) {
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<boolean> {
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;
}
}

View file

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

View file

@ -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');
@import url('./settings/template-modal.css');
@import url('./settings/template-preview-modal.css');
@import url('./settings/background-modal.css');

View file

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

View file

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

View file

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

View file

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

1
src/utils/nanoid.ts Normal file
View file

@ -0,0 +1 @@
export { nanoid } from 'nanoid';

View file

@ -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
})) || [])