update at 2025-10-10 21:54:05

This commit is contained in:
douboer
2025-10-10 21:54:05 +08:00
parent 1db58695e8
commit 0ab20de880
9 changed files with 347 additions and 228 deletions

View File

@@ -160,99 +160,141 @@ export class NoteToMpSettingTab extends PluginSettingTab {
this.wxInfo = this.parseWXInfo();
const helpEl = containerEl.createEl('div', { cls: 'setting-help-section' });
helpEl.createEl('h2', {text: '帮助文档', cls: 'setting-help-title'});
helpEl.createEl('a', {text: 'https://sunboshi.tech/doc', attr: {href: 'https://sunboshi.tech/doc'}});
const tabs = [
{ id: 'style', label: '样式', render: (panel: HTMLElement) => this.renderStyleTab(panel) },
{ id: 'shortcode', label: '短代码', render: (panel: HTMLElement) => this.renderShortcodeTab(panel) },
{ id: 'theme', label: '主题', render: (panel: HTMLElement) => this.renderThemeTab(panel) },
{ id: 'image', label: '图片', render: (panel: HTMLElement) => this.renderImageTab(panel) },
{ id: 'user', label: '用户信息', render: (panel: HTMLElement) => this.renderUserTab(panel) },
];
containerEl.createEl('h2', {text: '插件设置'});
const tabBar = containerEl.createDiv({ cls: 'nmp-settings-tabs' });
const panelsWrapper = containerEl.createDiv({ cls: 'nmp-settings-panels' });
new Setting(containerEl)
const panelMap = new Map<string, HTMLElement>();
const buttonMap = new Map<string, HTMLButtonElement>();
const activate = (id: string) => {
buttonMap.forEach((btn, key) => btn.toggleClass('is-active', key === id));
panelMap.forEach((panel, key) => panel.toggleClass('is-active', key === id));
};
tabs.forEach((tab) => {
const button = tabBar.createEl('button', { text: tab.label, cls: 'nmp-settings-tab-button' });
button.onclick = () => activate(tab.id);
buttonMap.set(tab.id, button);
const panel = panelsWrapper.createDiv({ cls: 'nmp-settings-panel', attr: { 'data-tab': tab.id } });
panelMap.set(tab.id, panel);
tab.render(panel);
});
if (tabs.length > 0) {
activate(tabs[0].id);
}
}
private renderStyleTab(panel: HTMLElement): void {
new Setting(panel)
.setName('默认样式')
.addDropdown(dropdown => {
const styles = this.plugin.assetsManager.themes;
for (let s of styles) {
dropdown.addOption(s.className, s.name);
}
const styles = this.plugin.assetsManager.themes;
for (const s of styles) {
dropdown.addOption(s.className, s.name);
}
dropdown.setValue(this.settings.defaultStyle);
dropdown.onChange(async (value) => {
dropdown.onChange(async (value) => {
this.settings.defaultStyle = value;
await this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName('代码高亮')
.addDropdown(dropdown => {
const styles = this.plugin.assetsManager.highlights;
for (let s of styles) {
dropdown.addOption(s.name, s.name);
}
dropdown.setValue(this.settings.defaultHighlight);
dropdown.onChange(async (value) => {
this.settings.defaultHighlight = value;
await this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName('在工具栏展示样式选择')
.setDesc('建议在移动端关闭,可以增大文章预览区域')
.addToggle(toggle => {
toggle.setValue(this.settings.showStyleUI);
toggle.onChange(async (value) => {
this.settings.showStyleUI = value;
await this.plugin.saveSettings();
});
});
new Setting(containerEl)
new Setting(panel)
.setName('代码高亮')
.addDropdown(dropdown => {
const styles = this.plugin.assetsManager.highlights;
for (const s of styles) {
dropdown.addOption(s.name, s.name);
}
dropdown.setValue(this.settings.defaultHighlight);
dropdown.onChange(async (value) => {
this.settings.defaultHighlight = value;
await this.plugin.saveSettings();
});
});
new Setting(panel)
.setName('链接展示样式')
.addDropdown(dropdown => {
dropdown.addOption('inline', '内嵌');
dropdown.addOption('footnote', '脚注');
dropdown.addOption('footnote', '脚注');
dropdown.setValue(this.settings.linkStyle);
dropdown.onChange(async (value) => {
this.settings.linkStyle = value;
this.settings.linkStyle = value;
await this.plugin.saveSettings();
});
});
new Setting(containerEl)
new Setting(panel)
.setName('文件嵌入展示样式')
.addDropdown(dropdown => {
dropdown.addOption('quote', '引用');
dropdown.addOption('content', '正文');
dropdown.addOption('content', '正文');
dropdown.setValue(this.settings.embedStyle);
dropdown.onChange(async (value) => {
this.settings.embedStyle = value;
this.settings.embedStyle = value;
await this.plugin.saveSettings();
});
});
new Setting(containerEl)
new Setting(panel)
.setName('数学公式语法')
.addDropdown(dropdown => {
dropdown.addOption('latex', 'latex');
dropdown.addOption('asciimath', 'asciimath');
dropdown.addOption('asciimath', 'asciimath');
dropdown.setValue(this.settings.math);
dropdown.onChange(async (value) => {
this.settings.math = value;
this.settings.math = value;
cleanMathCache();
await this.plugin.saveSettings();
});
});
new Setting(containerEl)
new Setting(panel)
.setName('显示代码行号')
.addToggle(toggle => {
toggle.setValue(this.settings.lineNumber);
toggle.setValue(this.settings.lineNumber);
toggle.onChange(async (value) => {
this.settings.lineNumber = value;
this.settings.lineNumber = value;
await this.plugin.saveSettings();
});
})
});
new Setting(containerEl)
new Setting(panel)
.setName('启用空行渲染')
.addToggle(toggle => {
toggle.setValue(this.settings.enableEmptyLine);
toggle.onChange(async (value) => {
this.settings.enableEmptyLine = value;
await this.plugin.saveSettings();
});
});
new Setting(panel)
.setName('默认开启评论')
.setDesc('发布到公众号时默认开启评论,可在 frontmatter 使用 need_open_comment 关闭')
.addToggle(toggle => {
toggle.setValue(this.settings.needOpenComment);
toggle.onChange(async (value) => {
this.settings.needOpenComment = value;
await this.plugin.saveSettings();
});
});
}
private renderShortcodeTab(panel: HTMLElement): void {
new Setting(panel)
.setName('Gallery 根路径')
.setDesc('用于 {{<gallery dir="..."/>}} 短代码解析;需指向本地图片根目录')
.addText(text => {
@@ -265,7 +307,7 @@ export class NoteToMpSettingTab extends PluginSettingTab {
text.inputEl.setAttr('style', 'width: 360px;');
});
new Setting(containerEl)
new Setting(panel)
.setName('Gallery 选取图片数')
.setDesc('每个 gallery 短代码最多替换为前 N 张图片')
.addText(text => {
@@ -281,7 +323,18 @@ export class NoteToMpSettingTab extends PluginSettingTab {
text.inputEl.setAttr('style', 'width: 120px;');
});
new Setting(containerEl)
new Setting(panel)
.setName('忽略 frontmatter 封面')
.setDesc('开启后不使用 frontmatter 中 cover/image 字段封面将按正文首图→gallery→默认封面回退')
.addToggle(toggle => {
toggle.setValue(this.settings.ignoreFrontmatterImage);
toggle.onChange(async (value) => {
this.settings.ignoreFrontmatterImage = value;
await this.plugin.saveSettings();
});
});
new Setting(panel)
.setName('默认封面图片')
.setDesc('当文章无任何图片/短代码时使用;可填 wikilink 文件名或 http(s) URL')
.addText(text => {
@@ -293,33 +346,84 @@ export class NoteToMpSettingTab extends PluginSettingTab {
});
text.inputEl.setAttr('style', 'width: 360px;');
});
}
new Setting(containerEl)
.setName('忽略 frontmatter 封面')
.setDesc('开启后不使用 frontmatter 中 cover/image 字段封面将按正文首图→gallery→默认封面回退')
.addToggle(toggle => {
toggle.setValue(this.settings.ignoreFrontmatterImage);
toggle.onChange(async (value) => {
this.settings.ignoreFrontmatterImage = value;
private renderThemeTab(panel: HTMLElement): void {
new Setting(panel)
.setName('获取更多主题')
.addButton(button => {
button.setButtonText('下载');
button.onClick(async () => {
button.setButtonText('下载中...');
await this.plugin.assetsManager.downloadThemes();
button.setButtonText('下载完成');
});
})
.addButton(button => {
button.setIcon('folder-open');
button.onClick(async () => {
await this.plugin.assetsManager.openAssets();
});
});
new Setting(panel)
.setName('清空主题')
.addButton(button => {
button.setButtonText('清空');
button.onClick(async () => {
await this.plugin.assetsManager.removeThemes();
this.settings.resetStyelAndHighlight();
await this.plugin.saveSettings();
});
});
new Setting(panel)
.setName('全局CSS属性')
.setDesc('只能填写CSS属性不能写选择器')
.addTextArea(text => {
this.wxTextArea = text;
text.setPlaceholder('请输入CSS属性background: #fff;padding: 10px;')
.setValue(this.settings.baseCSS)
.onChange(async (value) => {
this.settings.baseCSS = value;
await this.plugin.saveSettings();
})
.inputEl.setAttr('style', 'width: 520px; height: 60px;');
});
new Setting(containerEl)
.setName('启用空行渲染')
.addToggle(toggle => {
toggle.setValue(this.settings.enableEmptyLine);
toggle.onChange(async (value) => {
this.settings.enableEmptyLine = value;
await this.plugin.saveSettings();
});
})
const customCSSDoc = '使用指南:<a href="https://sunboshi.tech/customcss">https://sunboshi.tech/customcss</a>';
new Setting(panel)
.setName('自定义CSS笔记')
.setDesc(sanitizeHTMLToDom(customCSSDoc))
.addText(text => {
text.setPlaceholder('请输入自定义CSS笔记标题')
.setValue(this.settings.customCSSNote)
.onChange(async (value) => {
this.settings.customCSSNote = value.trim();
await this.plugin.saveSettings();
await this.plugin.assetsManager.loadCustomCSS();
})
.inputEl.setAttr('style', 'width: 320px;');
});
// 切图配置区块
containerEl.createEl('h2', {text: '切图配置'});
const expertDoc = '使用指南:<a href="https://sunboshi.tech/expert">https://sunboshi.tech/expert</a>';
new Setting(panel)
.setName('专家设置笔记')
.setDesc(sanitizeHTMLToDom(expertDoc))
.addText(text => {
text.setPlaceholder('请输入专家设置笔记标题')
.setValue(this.settings.expertSettingsNote)
.onChange(async (value) => {
this.settings.expertSettingsNote = value.trim();
await this.plugin.saveSettings();
await this.plugin.assetsManager.loadExpertSettings();
})
.inputEl.setAttr('style', 'width: 320px;');
});
}
new Setting(containerEl)
private renderImageTab(panel: HTMLElement): void {
new Setting(panel)
.setName('切图保存路径')
.setDesc('切图文件的保存目录,默认:/Users/gavin/note2mp/images/xhs')
.addText(text => {
@@ -332,7 +436,7 @@ export class NoteToMpSettingTab extends PluginSettingTab {
text.inputEl.setAttr('style', 'width: 360px;');
});
new Setting(containerEl)
new Setting(panel)
.setName('切图宽度')
.setDesc('长图及切图的宽度像素默认1080')
.addText(text => {
@@ -348,7 +452,7 @@ export class NoteToMpSettingTab extends PluginSettingTab {
text.inputEl.setAttr('style', 'width: 120px;');
});
new Setting(containerEl)
new Setting(panel)
.setName('切图横竖比例')
.setDesc('格式:宽:高,例如 3:4 表示竖图16:9 表示横图')
.addText(text => {
@@ -360,182 +464,119 @@ export class NoteToMpSettingTab extends PluginSettingTab {
});
text.inputEl.setAttr('style', 'width: 120px;');
});
new Setting(containerEl)
.setName('渲染图片标题')
.addToggle(toggle => {
toggle.setValue(this.settings.useFigcaption);
toggle.onChange(async (value) => {
this.settings.useFigcaption = value;
await this.plugin.saveSettings();
});
})
new Setting(containerEl)
.setName('Excalidraw 渲染为 PNG 图片')
.setDesc('开启:将 Excalidraw 笔记/嵌入转换为位图 PNG 插入;关闭:保持原始 SVG/矢量渲染(更清晰,体积更小)。')
.addToggle(toggle => {
toggle.setValue(this.settings.excalidrawToPNG);
toggle.onChange(async (value) => {
this.settings.excalidrawToPNG = value;
await this.plugin.saveSettings();
new Setting(panel)
.setName('渲染图片标题')
.addToggle(toggle => {
toggle.setValue(this.settings.useFigcaption);
toggle.onChange(async (value) => {
this.settings.useFigcaption = value;
await this.plugin.saveSettings();
});
});
})
new Setting(containerEl)
new Setting(panel)
.setName('Excalidraw 渲染为 PNG 图片')
.setDesc('开启:将 Excalidraw 笔记/嵌入转换为位图 PNG 插入;关闭:保持原始 SVG/矢量渲染。')
.addToggle(toggle => {
toggle.setValue(this.settings.excalidrawToPNG);
toggle.onChange(async (value) => {
this.settings.excalidrawToPNG = value;
await this.plugin.saveSettings();
});
});
new Setting(panel)
.setName('水印图片')
.setDesc('可填写文件名或 URL')
.addText(text => {
text.setPlaceholder('请输入图片名称')
text.setPlaceholder('例如 watermark.png')
.setValue(this.settings.watermark)
.onChange(async (value) => {
this.settings.watermark = value.trim();
this.settings.watermark = value.trim();
await this.plugin.saveSettings();
})
.inputEl.setAttr('style', 'width: 320px;')
})
new Setting(containerEl)
.setName('获取更多主题')
.addButton(button => {
button.setButtonText('下载');
button.onClick(async () => {
button.setButtonText('下载中...');
await this.plugin.assetsManager.downloadThemes();
button.setButtonText('下载完成');
});
})
.addButton(button => {
button.setIcon('folder-open');
button.onClick(async () => {
await this.plugin.assetsManager.openAssets();
});
.inputEl.setAttr('style', 'width: 320px;');
});
}
new Setting(containerEl)
.setName('清空主题')
.addButton(button => {
button.setButtonText('清空');
button.onClick(async () => {
await this.plugin.assetsManager.removeThemes();
this.settings.resetStyelAndHighlight();
await this.plugin.saveSettings();
});
})
new Setting(containerEl)
.setName('全局CSS属性')
.setDesc('只能填写CSS属性不能写选择器')
.addTextArea(text => {
this.wxTextArea = text;
text.setPlaceholder('请输入CSS属性background: #fff;padding: 10px;')
.setValue(this.settings.baseCSS)
.onChange(async (value) => {
this.settings.baseCSS = value;
await this.plugin.saveSettings();
})
.inputEl.setAttr('style', 'width: 520px; height: 60px;');
})
const customCSSDoc = '使用指南:<a href="https://sunboshi.tech/customcss">https://sunboshi.tech/customcss</a>';
new Setting(containerEl)
.setName('自定义CSS笔记')
.setDesc(sanitizeHTMLToDom(customCSSDoc))
.addText(text => {
text.setPlaceholder('请输入自定义CSS笔记标题')
.setValue(this.settings.customCSSNote)
.onChange(async (value) => {
this.settings.customCSSNote = value.trim();
await this.plugin.saveSettings();
await this.plugin.assetsManager.loadCustomCSS();
})
.inputEl.setAttr('style', 'width: 320px;')
});
const expertDoc = '使用指南:<a href="https://sunboshi.tech/expert">https://sunboshi.tech/expert</a>';
new Setting(containerEl)
.setName('专家设置笔记')
.setDesc(sanitizeHTMLToDom(expertDoc))
.addText(text => {
text.setPlaceholder('请输入专家设置笔记标题')
.setValue(this.settings.expertSettingsNote)
.onChange(async (value) => {
this.settings.expertSettingsNote = value.trim();
await this.plugin.saveSettings();
await this.plugin.assetsManager.loadExpertSettings();
})
.inputEl.setAttr('style', 'width: 320px;')
});
private renderUserTab(panel: HTMLElement): void {
let descHtml = '详情说明:<a href="https://sunboshi.tech/subscribe">https://sunboshi.tech/subscribe</a>';
if (this.settings.isVip) {
descHtml = '<span style="color:rgb(245, 70, 85);font-weight: bold;">👑永久会员</span><br/>' + descHtml;
}
else if (this.settings.expireat) {
const timestr = this.settings.expireat.toLocaleString();
descHtml = `有效期至:${timestr} <br/>${descHtml}`
descHtml = `有效期至:${timestr} <br/>${descHtml}`;
}
new Setting(containerEl)
new Setting(panel)
.setName('注册码AuthKey')
.setDesc(sanitizeHTMLToDom(descHtml))
.addText(text => {
text.setPlaceholder('请输入注册码')
.setValue(this.settings.authKey)
.onChange(async (value) => {
.setValue(this.settings.authKey)
.onChange(async (value) => {
this.settings.authKey = value.trim();
this.settings.getExpiredDate();
await this.plugin.saveSettings();
})
.inputEl.setAttr('style', 'width: 320px;')
}).descEl.setAttr('style', '-webkit-user-select: text; user-select: text;')
this.settings.getExpiredDate();
await this.plugin.saveSettings();
})
.inputEl.setAttr('style', 'width: 320px;');
}).descEl.setAttr('style', '-webkit-user-select: text; user-select: text;');
let isClear = this.settings.wxInfo.length > 0;
let isRealClear = false;
const buttonText = isClear ? '清空公众号信息' : '保存公众号信息';
new Setting(containerEl)
new Setting(panel)
.setName('公众号信息')
.addTextArea(text => {
this.wxTextArea = text;
text.setPlaceholder('请输入公众号信息\n格式公众号名称|公众号AppID|公众号AppSecret\n多个公众号请换行输入\n输入完成后点击加密按钮')
.setValue(this.wxInfo)
text.setPlaceholder('请输入公众号信息\n格式公众号名称|公众号AppID|公众号AppSecret\n多个公众号请换行输入\n输入完成后点击加密按钮')
.setValue(this.wxInfo)
.onChange(value => {
this.wxInfo = value;
this.wxInfo = value;
})
.inputEl.setAttr('style', 'width: 520px; height: 120px;');
.inputEl.setAttr('style', 'width: 520px; height: 120px;');
})
new Setting(containerEl).addButton(button => {
button.setButtonText(buttonText);
button.onClick(async () => {
if (isClear) {
isRealClear = true;
isClear = false;
button.setButtonText('确认清空?');
}
else if (isRealClear) {
isRealClear = false;
isClear = false;
this.clear();
button.setButtonText('保存公众号信息');
}
else {
button.setButtonText('保存中...');
if (await this.encrypt()) {
isClear = true;
isRealClear = false;
button.setButtonText('清空公众号信息');
.addButton(button => {
button.setButtonText(buttonText);
button.onClick(async () => {
if (isClear) {
isRealClear = true;
isClear = false;
button.setButtonText('确认清空?');
}
else {
else if (isRealClear) {
isRealClear = false;
isClear = false;
this.clear();
button.setButtonText('保存公众号信息');
}
}
});
})
.addButton(button => {
button.setButtonText('测试公众号');
button.onClick(async () => {
button.setButtonText('测试中...');
await this.testWXInfo();
button.setButtonText('测试公众号');
else {
button.setButtonText('保存中...');
if (await this.encrypt()) {
isClear = true;
isRealClear = false;
button.setButtonText('清空公众号信息');
}
else {
button.setButtonText('保存公众号信息');
}
}
});
})
})
.addButton(button => {
button.setButtonText('测试公众号');
button.onClick(async () => {
button.setButtonText('测试中...');
await this.testWXInfo();
button.setButtonText('测试公众号');
});
});
//
const helpEl = panel.createEl('div', { cls: 'setting-help-section' });
helpEl.createEl('h2', { text: '帮助文档', cls: 'setting-help-title' });
helpEl.createEl('a', { text: 'https://sunboshi.tech/doc', attr: { href: 'https://sunboshi.tech/doc' } });
}
}