update at 2025-10-10 19:13:38

This commit is contained in:
douboer
2025-10-10 19:13:38 +08:00
parent 86c3beea49
commit 90933673f3
4 changed files with 492 additions and 331 deletions

View File

@@ -9,7 +9,7 @@
* 4. 提供文章导出HTML功能
*/
import { Notice, Platform, TFile, TFolder } from 'obsidian';
import { Notice, Platform, TFile } from 'obsidian';
import { NMPSettings } from '../settings';
import AssetsManager from '../assets';
import { ArticleRender } from '../article-render';
@@ -32,8 +32,9 @@ export class WechatPreview {
currentHighlight: string;
// UI 元素
toolbar: HTMLDivElement | null = null;
renderDiv: HTMLDivElement | null = null;
board: HTMLDivElement | null = null;
contentCell: HTMLElement | null = null;
contentEl: HTMLElement | null = null;
wechatSelect: HTMLSelectElement | null = null;
themeSelect: HTMLSelectElement | null = null;
highlightSelect: HTMLSelectElement | null = null;
@@ -65,97 +66,95 @@ export class WechatPreview {
*/
build(): void {
this.container.empty();
// 创建工具栏
this.toolbar = this.container.createDiv({ cls: 'preview-toolbar' });
this.buildToolbar(this.toolbar);
// 创建渲染区域
this.renderDiv = this.container.createDiv({ cls: 'render-div' });
this.renderDiv.id = 'render-div';
this.container.addClass('wechat-preview-container');
// 将 ArticleRender 的 style 与内容节点挂载
try {
if (this.render && this.render.styleEl && !this.renderDiv.contains(this.render.styleEl)) {
this.renderDiv.appendChild(this.render.styleEl);
}
if (this.render && this.render.articleDiv && !this.renderDiv.contains(this.render.articleDiv)) {
// 容器样式:模拟公众号编辑器宽度,更好的排版显示
this.render.articleDiv.addClass('wechat-article-wrapper');
this.renderDiv.appendChild(this.render.articleDiv);
}
} catch (e) {
console.warn('[WechatPreview] 挂载文章容器失败', e);
}
this.board = this.container.createDiv({ cls: 'wechat-board' });
this.buildAccountRow();
//this.buildCoverRow();
//this.buildStyleRow();
this.contentCell = this.createCell('content');
this.contentCell.addClass('wechat-cell-content');
this.mountArticle(this.board);
}
/**
* 构建工具栏
*/
private buildToolbar(parent: HTMLDivElement): void {
// 单层 Grid所有控件直接挂到 parent.preview-toolbar
private createCell(area: string, tag: keyof HTMLElementTagNameMap = 'div', extraClasses: string[] = []): HTMLElement {
if (!this.board) {
throw new Error('Wechat board not initialized');
}
const cell = this.board.createEl(tag, { attr: { 'data-area': area } });
cell.addClass('wechat-cell');
for (const cls of extraClasses) {
cell.addClass(cls);
}
return cell;
}
// 公众号选择
if (this.settings.wxInfo.length > 1 || Platform.isDesktop) {
const wxLabel = parent.createDiv({ cls: 'style-label' });
wxLabel.innerText = '公众号';
private buildAccountRow(): void {
const selectCell = this.createCell('account-select');
const selectLabel = selectCell.createEl('label', {
cls: 'style-label',
attr: { for: 'wechat-account-select' },
text: '公众号'
});
selectLabel.addClass('wechat-account-label');
const wxSelect = parent.createEl('select', { cls: 'wechat-select' });
wxSelect.onchange = async () => {
this.currentAppId = wxSelect.value;
this.onAppIdChanged();
const wxSelect = selectCell.createEl('select', {
cls: 'wechat-select',
attr: { id: 'wechat-account-select' }
}) as HTMLSelectElement;
wxSelect.onchange = async () => {
this.currentAppId = wxSelect.value;
this.onAppIdChanged();
};
const defaultOp = wxSelect.createEl('option');
defaultOp.value = '';
defaultOp.text = '请在设置里配置公众号';
for (let i = 0; i < this.settings.wxInfo.length; i++) {
const op = wxSelect.createEl('option');
const wx = this.settings.wxInfo[i];
op.value = wx.appid;
op.text = wx.name;
if (i === 0) {
op.selected = true;
this.currentAppId = wx.appid;
}
}
this.wechatSelect = wxSelect;
const actionsCell = this.createCell('account-back-export');
if (Platform.isDesktop) {
const openBtn = actionsCell.createEl('button', {
text: '访问后台',
cls: 'toolbar-button purple-gradient wechat-action-button'
});
openBtn.onclick = async () => {
const { shell } = require('electron');
shell.openExternal('https://mp.weixin.qq.com');
uevent('open-mp');
};
const defaultOp = wxSelect.createEl('option');
defaultOp.value = '';
defaultOp.text = '请在设置里配置公众号';
for (let i = 0; i < this.settings.wxInfo.length; i++) {
const op = wxSelect.createEl('option');
const wx = this.settings.wxInfo[i];
op.value = wx.appid;
op.text = wx.name;
if (i === 0) {
op.selected = true;
this.currentAppId = wx.appid;
}
}
this.wechatSelect = wxSelect;
if (Platform.isDesktop) {
//parent.createDiv({ cls: 'toolbar-separator' });
const openBtn = parent.createEl('button', { text: '🌐 去公众号后台', cls: 'toolbar-button purple-gradient' });
openBtn.onclick = async () => {
const { shell } = require('electron');
shell.openExternal('https://mp.weixin.qq.com');
uevent('open-mp');
};
}
}
// 操作按钮(直接平铺在 Grid 中)
if (Platform.isDesktop && this.settings.isAuthKeyVaild()) {
const htmlBtn = parent.createEl('button', { text: '💾 导出HTML', cls: 'toolbar-button' });
htmlBtn.onclick = async () => await this.exportHTML();
const exportBtn = actionsCell.createEl('button', { text: '导出页面', cls: 'toolbar-button wechat-action-button' });
exportBtn.onclick = async () => await this.exportHTML();
}
// 封面选择
//this.buildCoverSelector(parent);
// 样式选择(如果启用)
if (this.settings.showStyleUI) {
this.buildStyleSelector(parent);
if (actionsCell.childElementCount === 0) {
actionsCell.addClass('wechat-cell-placeholder');
}
}
/**
* 构建封面选择器
*/
private buildCoverSelector(parent: HTMLDivElement): void {
const coverTitle = parent.createDiv({ cls: 'style-label' });
coverTitle.innerText = '封面';
private buildCoverRow(): void {
const selectCell = this.createCell('cover-select');
selectCell.createDiv({ cls: 'style-label', text: '封面' });
this.useDefaultCover = parent.createEl('input', { cls: 'input-style' });
this.useDefaultCover = selectCell.createEl('input', { cls: 'input-style' }) as HTMLInputElement;
this.useDefaultCover.setAttr('type', 'radio');
this.useDefaultCover.setAttr('name', 'cover');
this.useDefaultCover.setAttr('value', 'default');
@@ -166,43 +165,42 @@ export class WechatPreview {
this.coverEl.setAttr('style', 'visibility:hidden;width:0px;');
}
};
selectCell.createEl('label', { text: '默认', attr: { for: 'default-cover' } });
const defaultLabel = parent.createEl('label');
defaultLabel.innerText = '默认';
defaultLabel.setAttr('for', 'default-cover');
this.useLocalCover = parent.createEl('input', { cls: 'input-style' });
this.useLocalCover = selectCell.createEl('input', { cls: 'input-style' }) as HTMLInputElement;
this.useLocalCover.setAttr('type', 'radio');
this.useLocalCover.setAttr('name', 'cover');
this.useLocalCover.setAttr('value', 'local');
this.useLocalCover.id = 'local-cover';
this.useLocalCover.setAttr('style', 'margin-left:20px;');
this.useLocalCover.onchange = async () => {
if (this.useLocalCover?.checked && this.coverEl) {
this.coverEl.setAttr('style', 'visibility:visible;width:180px;');
}
};
selectCell.createEl('label', { text: '上传', attr: { for: 'local-cover' } });
const localLabel = parent.createEl('label');
localLabel.setAttr('for', 'local-cover');
localLabel.innerText = '上传';
this.coverEl = parent.createEl('input', { cls: 'upload-input' });
this.coverEl.setAttr('type', 'file');
this.coverEl.setAttr('placeholder', '封面图片');
this.coverEl.setAttr('accept', '.png, .jpg, .jpeg');
this.coverEl.setAttr('name', 'cover');
this.coverEl.id = 'cover-input';
const inputCell = this.createCell('cover-input');
this.coverEl = inputCell.createEl('input', {
cls: 'upload-input',
attr: {
type: 'file',
placeholder: '封面图片',
accept: '.png, .jpg, .jpeg',
name: 'cover',
id: 'cover-input'
}
}) as HTMLInputElement;
if (this.useDefaultCover?.checked) {
this.coverEl.setAttr('style', 'visibility:hidden;width:0px;');
}
}
/**
* 构建样式选择器
*/
private buildStyleSelector(parent: HTMLDivElement): void {
const cssStyle = parent.createDiv({ cls: 'style-label' });
cssStyle.innerText = '样式';
private buildStyleRow(): void {
const styleLabelCell = this.createCell('style-label', 'div', ['style-label']);
styleLabelCell.setText('样式');
const selectBtn = parent.createEl('select', { cls: 'style-select' });
const styleSelectCell = this.createCell('style-select');
const selectBtn = styleSelectCell.createEl('select', { cls: 'style-select wechat-style-select' }) as HTMLSelectElement;
selectBtn.onchange = async () => {
this.currentTheme = selectBtn.value;
this.render.updateStyle(selectBtn.value);
@@ -216,12 +214,11 @@ export class WechatPreview {
}
this.themeSelect = selectBtn;
parent.createDiv({ cls: 'toolbar-separator' });
const highlightLabelCell = this.createCell('highlight-label', 'div', ['style-label']);
highlightLabelCell.setText('代码高亮');
const highlightStyle = parent.createDiv({ cls: 'style-label' });
highlightStyle.innerText = '代码高亮';
const highlightStyleBtn = parent.createEl('select', { cls: 'style-select' });
const highlightSelectCell = this.createCell('highlight-select');
const highlightStyleBtn = highlightSelectCell.createEl('select', { cls: 'style-select wechat-style-select' }) as HTMLSelectElement;
highlightStyleBtn.onchange = async () => {
this.currentHighlight = highlightStyleBtn.value;
this.render.updateHighLight(highlightStyleBtn.value);
@@ -237,6 +234,29 @@ export class WechatPreview {
this.highlightSelect = highlightStyleBtn;
}
private mountArticle(_parent: HTMLElement): void {
if (!this.contentCell) {
return;
}
try {
if (this.render?.styleEl && !this.contentCell.contains(this.render.styleEl)) {
this.contentCell.appendChild(this.render.styleEl);
}
if (this.render?.articleDiv) {
this.render.articleDiv.addClass('wechat-article-wrapper');
if (this.render.articleDiv.parentElement !== this.contentCell) {
this.contentCell.appendChild(this.render.articleDiv);
}
this.contentEl = this.render.articleDiv;
}
} catch (error) {
console.warn('[WechatPreview] 挂载文章容器失败', error);
}
}
/**
* 构建封面选择器
*/
/**
* 显示微信预览视图
*/
@@ -360,8 +380,9 @@ export class WechatPreview {
* 清理资源
*/
destroy(): void {
this.toolbar = null;
this.renderDiv = null;
this.board = null;
this.contentCell = null;
this.contentEl = null;
this.wechatSelect = null;
this.themeSelect = null;
this.highlightSelect = null;