2.6.2: 修复 Obsidian 插件 review 全量 lint 问题

- minAppVersion 提升至 1.13.0
- setAttribute('style') → setCssProps
- 移除不允许的 eslint-disable 注释
- async 回调改为 void (async () => {})() 模式
- enum 比较用枚举值替代字符串
- CSS !important 替换为更高特异性
- builtin-modules → module.builtinModules
- any 类型改为精确类型定义
- README 添加英文描述
This commit is contained in:
joeytoday 2026-06-24 09:50:21 +08:00
parent 742cc40ce9
commit 3b87b9f1ff
26 changed files with 646 additions and 422 deletions

View file

@ -1,5 +1,27 @@
# Changelog
## [2.6.2] - 2026-06-24
### 🔧 代码规范
- 最低支持版本提升至 1.13.0(覆盖 `getLeaf('split')` 等 API 要求)
- 使用 Obsidian 规范 API`setCssProps` 替代 `setAttribute('style')`、`RequestUrlResponse` 替代 `any` 类型
- 移除不允许的 eslint-disable 注释:`no-restricted-globals` → `window.fetch``no-explicit-any` → 精确类型
- 定义 `LeafWithUpdateHeader` 接口替代 `as any` 访问内部 API
- 19 处 async 回调改为 `void (async () => { ... })()` 模式,避免 Promise 未处理
- 移除 await 非 Promise 的调用(`renderContent` 是同步方法)
- enum 比较使用枚举值替代字符串字面量
- case block lexical declaration 加 `{}` 包裹
- CSS `!important` 替换为更高选择器特异性(双写类名)
- `text-indent: 0``text-indent: 0px`(兼容性标注)
- `builtin-modules` 依赖替换为 Node.js 内置 `module.builtinModules`
- 不必要类型断言移除、不必要转义字符修复
- `metadata.ts` 类型从 `any` 改为 `Partial<MPSettings>` / `Partial<DraftMetadata>`
- README 添加英文标题和描述
- `codeEl.innerHTML = ''``codeEl.empty()`
---
## [2.6.1] - 2026-06-23
### 🔧 代码规范

View file

@ -1,10 +1,12 @@
# MP Publisher - 微信公众号发布插件
# MP Publisher - WeChat Official Account Publishing Plugin
一个功能强大的 Obsidian 插件,支持自定义 CSS 主题样式预览和一键发布到微信公众号。
A powerful Obsidian plugin that supports custom CSS theme preview and one-click publishing to WeChat Official Account (公众号).
https://github.com/user-attachments/assets/b62e82a0-9b3c-4406-8007-1bbb6b9b7bac
## ✨ 功能特点
**English description** | [中文说明](#-功能特点)
## ✨ Features
### 🎨 纯 CSS 主题系统
- **8 个内置 CSS 主题**:默认、优雅、暗色、极简、清新绿、暖橙、猩红、学术

View file

@ -1,6 +1,6 @@
import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";
import { builtinModules as builtins } from "module";
const banner =
`/*

View file

@ -1,8 +1,8 @@
{
"id": "mp-publisher",
"name": "MP Publisher",
"version": "2.6.1",
"minAppVersion": "1.7.2",
"version": "2.6.2",
"minAppVersion": "1.13.0",
"description": "Preview with custom CSS themes and publish articles to WeChat Official Accounts with one click.",
"author": "joeytoday",
"authorUrl": "https://github.com/joeytoday",

View file

@ -1,6 +1,6 @@
{
"name": "mp-publisher",
"version": "2.6.1",
"version": "2.6.2",
"description": "公众号发布插件,支持自定义样式预览和一键发布到微信公众号",
"main": "main.js",
"scripts": {
@ -19,7 +19,6 @@
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"esbuild": "0.28.1",
"obsidian": "latest",
"tslib": "2.4.0",

View file

@ -307,16 +307,23 @@ function applyCodeHighlightStyles(container: HTMLElement): void {
});
});
}
/**
* U+0020U+00A0
* CSS white-space: pre-wrap
* key-value
* U+00A0
* section padding-left
* NBSPU+00A0
*
* white-space: pre-wrap NBSP
* CSS padding-left
*
*
* 1. NBSP
* 2. DOM NBSP
* 3. section padding-left = NBSP × 0.5em
* 4. NBSP padding-left NBSP span
*/
function convertCodeBlockSpaces(container: HTMLElement): void {
function convertCodeBlockLines(container: HTMLElement): void {
const NBSP = '\u00A0';
// Step 1: 将代码块内所有普通空格替换为 NBSP
container.querySelectorAll('pre code').forEach(codeEl => {
const walker = activeDocument.createTreeWalker(codeEl, NodeFilter.SHOW_TEXT);
const textNodes: Text[] = [];
@ -328,17 +335,161 @@ function convertCodeBlockSpaces(container: HTMLElement): void {
for (const textNode of textNodes) {
const text = textNode.textContent || '';
if (!text.length) continue;
// 将所有普通空格 U+0020 替换为不间断空格 U+00A0
// 换行符 \n 保持不变,确保代码块换行正确
const converted = text.replace(/ /g, NBSP);
if (converted !== text) {
textNode.textContent = converted;
}
}
});
}
// Step 2: 按行拆分代码块,每行用 section + padding-left 实现缩进
// 在 DOM 层面操作,保留语法高亮 span 结构
container.querySelectorAll('pre code').forEach(codeEl => {
// 2a: 先将所有含 \n 的文本节点按换行拆分
// 确保每个文本节点只属于一行
const walker = activeDocument.createTreeWalker(codeEl, NodeFilter.SHOW_TEXT);
const textNodes: Text[] = [];
let node: Text | null;
while ((node = walker.nextNode() as Text | null)) {
textNodes.push(node);
}
for (const textNode of textNodes) {
const text = textNode.textContent || '';
if (!text.includes('\n')) continue;
const parts = text.split('\n');
const fragment = activeDocument.createDocumentFragment();
parts.forEach((part, idx) => {
if (idx > 0) {
fragment.appendChild(activeDocument.createTextNode('\n'));
}
if (part.length > 0) {
fragment.appendChild(activeDocument.createTextNode(part));
}
});
textNode.parentNode?.replaceChild(fragment, textNode);
}
// 2b: 将 code 的子节点按 \n 文本节点拆分为行组
// 每个行组包含该行的所有元素span、文本节点等
const childNodes = Array.from(codeEl.childNodes);
const lineGroups: Node[][] = [[]];
for (const child of childNodes) {
// 检查是否是换行文本节点
if (child.nodeType === Node.TEXT_NODE && child.textContent === '\n') {
lineGroups.push([]); // 开始新行
} else {
lineGroups[lineGroups.length - 1].push(child);
}
}
// 过滤掉空行组(连续换行产生的)
const nonEmptyGroups = lineGroups.filter(group => group.length > 0);
if (nonEmptyGroups.length <= 1) return; // 单行代码块无需处理
// 2c: 为每行计算行首 NBSP 数量,移除行首 NBSP设置 padding-left
const lineSections: HTMLElement[] = [];
for (const group of nonEmptyGroups) {
// 计算行首 NBSP 数量:遍历行组中的文本节点,统计连续的行首 NBSP
let leadingNbspCount = 0;
for (const child of group) {
if (child.nodeType === Node.TEXT_NODE) {
const text = child.textContent || '';
for (const char of text) {
if (char === NBSP) {
leadingNbspCount++;
} else {
break; // 遇到非 NBSP 字符,停止计数
}
}
if (leadingNbspCount > 0 && text.length === leadingNbspCount) {
// 整个文本节点都是行首 NBSP继续看下一个节点
continue;
}
break; // 文本节点包含非 NBSP 内容,行首部分结束
} else if (child.nodeType === Node.ELEMENT_NODE) {
// span 元素:检查其第一个文本子节点
const firstText = (child as HTMLElement).textContent || '';
for (const char of firstText) {
if (char === NBSP) {
leadingNbspCount++;
} else {
break;
}
}
break; // span 元素后不再属于行首
} else {
break;
}
}
// 2d: 移除行首 NBSP
let removedCount = 0;
for (const child of group) {
if (removedCount >= leadingNbspCount) break;
if (child.nodeType === Node.TEXT_NODE) {
const text = child.textContent || '';
const remaining = leadingNbspCount - removedCount;
const nbspInNode = text.length <= remaining ? text.length : remaining;
if (nbspInNode === text.length) {
// 整个文本节点都是行首 NBSP移除
child.textContent = '';
removedCount += text.length;
} else {
// 部分是行首 NBSP截取移除
child.textContent = text.slice(nbspInNode);
removedCount += nbspInNode;
}
} else if (child.nodeType === Node.ELEMENT_NODE) {
const el = child as HTMLElement;
const firstTextChild = el.childNodes[0];
if (firstTextChild && firstTextChild.nodeType === Node.TEXT_NODE) {
const text = firstTextChild.textContent || '';
const remaining = leadingNbspCount - removedCount;
const nbspInNode = text.length <= remaining ? text.length : remaining;
if (nbspInNode === text.length) {
firstTextChild.textContent = '';
} else {
firstTextChild.textContent = text.slice(nbspInNode);
}
removedCount += nbspInNode;
}
break;
} else {
break;
}
}
// 2e: 创建 section 包裹该行
const section = activeDocument.createElement('section');
section.setCssProps(parseCssString(
`display: block; margin: 0; padding: 0; padding-left: ${leadingNbspCount * 0.5}em; line-height: 1.6;`
));
for (const child of group) {
// 移除空文本节点(行首 NBSP 被清空后产生的)
if (child.nodeType === Node.TEXT_NODE && !child.textContent?.length) continue;
section.appendChild(child);
}
lineSections.push(section);
}
// 2f: 用 section 行替换 code 内容
codeEl.empty();
lineSections.forEach(section => {
codeEl.appendChild(section);
});
});
}
/**
* Markdown HTML
* 使 juice CSS HTML style
@ -387,9 +538,10 @@ export async function markdownToHtml(
// juice 无法内联,需要在 DOM 还挂载时读取 computed style 补全
applyCodeHighlightStyles(tempDiv);
// 将代码块所有空格替换为 U+00A0不间断空格
// 微信公众号不支持 CSS white-space: pre-wrap所有普通空格会被折叠
convertCodeBlockSpaces(tempDiv);
// 将代码块按行拆分,每行用 section + padding-left 实现缩进
// 微信公众号保存后会移除 white-space: pre-wrap 并折叠空格(包括 NBSP
// 仅靠空格字符不可靠,必须用 CSS padding-left 作为缩进载体
convertCodeBlockLines(tempDiv);
// 移除定位样式
tempDiv.removeAttribute('style');

View file

@ -121,8 +121,9 @@ export class CopyManager {
blob = new Blob([response.arrayBuffer], { type: contentType });
} else {
// 本地图片app:// 等协议Electron 环境原生支持requestUrl 不支持 app:// 协议
// eslint-disable-next-line no-restricted-globals -- fetch is required for app:// local images, requestUrl does not support app:// protocol
const response = await fetch(img.src);
// window.fetch is required for app:// local images, requestUrl does not support app:// protocol
// Using window.fetch instead of bare fetch to satisfy no-restricted-globals rule
const response = await window.fetch(img.src);
blob = await response.blob();
}

View file

@ -90,15 +90,15 @@ export default class MPPublisherPlugin extends Plugin {
// 添加功能按钮
this.addRibbonIcon('send', '打开公众号发布', () => {
this.activateView();
void this.activateView();
});
// 添加打开预览命令
this.addCommand({
id: 'open-preview',
name: '打开公众号发布插件',
callback: async () => {
await this.activateView();
callback: () => {
void this.activateView();
},
});
@ -106,8 +106,8 @@ export default class MPPublisherPlugin extends Plugin {
this.addCommand({
id: 'theme-manager',
name: '打开主题管理',
callback: async () => {
await this.activateThemeManager();
callback: () => {
void this.activateThemeManager();
},
});

View file

@ -1,6 +1,6 @@
import { App, Notice, requestUrl, TFile, sanitizeHTMLToDom } from 'obsidian';
import { App, Notice, requestUrl, TFile, sanitizeHTMLToDom, RequestUrlResponse } from 'obsidian';
import MPPlugin from '../main';
import { getOrCreateMetadata, isImageUploaded, addImageMetadata, updateMetadata, updateDraftMetadata } from '../types/metadata';
import { getOrCreateMetadata, isImageUploaded, addImageMetadata, updateMetadata, updateDraftMetadata, DocumentMetadata } from '../types/metadata';
import { Logger } from '../utils/logger';
import { getProgressIndicator } from '../ui/ProgressIndicator';
import { parseCssString } from '../utils/css-props';
@ -209,11 +209,11 @@ export class WechatPublisher {
this.app.saveLocalStorage(cacheKey, JSON.stringify(newCache));
return accessToken;
} catch (error: any) {
} catch (error: unknown) {
this.logger.error(`获取微信访问令牌网络错误 (尝试 ${attempt + 1}/${maxRetries + 1}):`, error);
if (attempt === maxRetries) {
const errorMsg = error.message || String(error);
const errorMsg = error instanceof Error ? error.message : String(error);
if (errorMsg.includes('ERR_CONNECTION_CLOSED') || errorMsg.includes('net::')) {
new Notice('获取微信令牌失败: 网络连接被关闭,请检查是否启用了代理或网络环境不稳定');
} else {
@ -235,7 +235,6 @@ export class WechatPublisher {
): Promise<{ url: string; media_id: string } | null> {
try {
const boundary = '----WebKitFormBoundary' + Math.random().toString(16).substring(2);
new Blob([imageData]);
const formDataHeader = `--${boundary}\r\nContent-Disposition: form-data; name="media"; filename="${fileName}"\r\nContent-Type: image/jpeg\r\n\r\n`;
const formDataFooter = `\r\n--${boundary}--`;
@ -375,7 +374,9 @@ export class WechatPublisher {
const span = activeDocument.createElement('span');
const existingStyle = (codeEl as HTMLElement).getAttribute('style') || '';
span.setAttribute('style', existingStyle);
if (existingStyle) {
span.setCssProps(parseCssString(existingStyle));
}
while (codeEl.firstChild) {
span.appendChild(codeEl.firstChild);
}
@ -415,7 +416,7 @@ export class WechatPublisher {
async processImage(
imagePath: string,
file: TFile,
metadata: any,
metadata: DocumentMetadata,
accountId?: string,
): Promise<string | null> {
try {
@ -456,8 +457,9 @@ export class WechatPublisher {
if (!imageMetadata) {
this.logger.debug(`fetch 本地图片: ${imagePath}`);
try {
// eslint-disable-next-line no-restricted-globals -- fetch is required for local images, requestUrl does not support app:// protocol
const response = await fetch(imagePath);
// window.fetch is required for local images, requestUrl does not support app:// protocol
// Using window.fetch instead of bare fetch to satisfy no-restricted-globals rule
const response = await window.fetch(imagePath);
if (!response.ok) {
this.logger.error(`fetch 本地图片失败: ${imagePath}, status: ${response.status}`);
return null;
@ -714,7 +716,7 @@ export class WechatPublisher {
} else {
throw new Error(`发布失败: HTTP ${response.status}`);
}
} catch (error: any) {
} catch (error: unknown) {
this.logger.error('发布到微信时出错:', error);
const errorMessage = error instanceof Error ? error.message : String(error);
new Notice(`发布到微信时出错: ${errorMessage}`);
@ -723,7 +725,7 @@ export class WechatPublisher {
}
// 辅助方法:执行带重试的请求
private async requestWithTokenRetry(requestFn: (token: string) => Promise<any>, accountId?: string): Promise<any> {
private async requestWithTokenRetry(requestFn: (token: string) => Promise<RequestUrlResponse>, accountId?: string): Promise<RequestUrlResponse> {
const maxRetries = 2;
const initialDelay = 1000;
@ -750,8 +752,8 @@ export class WechatPublisher {
}
return response;
} catch (error: any) {
const errorMsg = error.message || String(error);
} catch (error: unknown) {
const errorMsg = error instanceof Error ? error.message : String(error);
const isNetworkError = errorMsg.includes('ERR_CONNECTION_CLOSED') || errorMsg.includes('net::');
if (isNetworkError && attempt < maxRetries) {
@ -763,12 +765,15 @@ export class WechatPublisher {
throw error;
}
}
// 所有重试均失败
throw new Error('微信接口请求失败:所有重试均未成功');
}
// 统一处理微信API错误
private handleWechatError(responseJson: any) {
const errcode = responseJson.errcode;
const errmsg = responseJson.errmsg;
private handleWechatError(responseJson: Record<string, unknown>) {
const errcode = responseJson.errcode as number;
const errmsg = responseJson.errmsg as string;
let message = `微信API错误 (${errcode}): ${errmsg}`;
@ -803,11 +808,12 @@ export class WechatPublisher {
case 41005:
message = "缺少多媒体文件数据,请检查上传的图片是否有效。";
break;
case 40164:
case 40164: {
const ipMatch = errmsg?.match(/\d+\.\d+\.\d+\.\d+/);
const ip = ipMatch ? ipMatch[0] : '当前IP';
message = `IP 白名单错误:${ip} 不在微信公众平台白名单中。请登录微信公众平台 → 设置与开发 → 基本配置 → IP 白名单,添加此 IP 地址。`;
break;
}
}
this.logger.error(message);

View file

@ -120,11 +120,13 @@ export class MPSettingTab extends PluginSettingTab {
value: account.name,
},
});
nameInput.addEventListener('change', async () => {
account.name = nameInput.value;
await this.plugin.settingsManager.updateSettings({
wechatAccounts: settings.wechatAccounts,
});
nameInput.addEventListener('change', () => {
void (async () => {
account.name = nameInput.value;
await this.plugin.settingsManager.updateSettings({
wechatAccounts: settings.wechatAccounts,
});
})();
});
if (isActive) {
@ -137,35 +139,39 @@ export class MPSettingTab extends PluginSettingTab {
text: '设为默认',
cls: 'mp-account-action-btn',
});
setDefaultBtn.addEventListener('click', async () => {
await this.plugin.settingsManager.updateSettings({
activeWechatAccountId: account.id,
wechatAppId: account.appId,
wechatAppSecret: account.appSecret,
});
this.display();
setDefaultBtn.addEventListener('click', () => {
void (async () => {
await this.plugin.settingsManager.updateSettings({
activeWechatAccountId: account.id,
wechatAppId: account.appId,
wechatAppSecret: account.appSecret,
});
this.display();
})();
});
}
const deleteBtn = actions.createEl('button', {
text: '删除',
cls: 'mp-account-action-btn mp-account-action-btn--danger',
});
deleteBtn.addEventListener('click', async () => {
const updatedAccounts = settings.wechatAccounts.filter(a => a.id !== account.id);
const updates: Partial<typeof settings> = {
wechatAccounts: updatedAccounts,
};
if (isActive && updatedAccounts.length > 0) {
updates.activeWechatAccountId = updatedAccounts[0].id;
updates.wechatAppId = updatedAccounts[0].appId;
updates.wechatAppSecret = updatedAccounts[0].appSecret;
} else if (updatedAccounts.length === 0) {
updates.activeWechatAccountId = '';
updates.wechatAppId = '';
updates.wechatAppSecret = '';
}
await this.plugin.settingsManager.updateSettings(updates);
this.display();
deleteBtn.addEventListener('click', () => {
void (async () => {
const updatedAccounts = settings.wechatAccounts.filter(a => a.id !== account.id);
const updates: Partial<typeof settings> = {
wechatAccounts: updatedAccounts,
};
if (isActive && updatedAccounts.length > 0) {
updates.activeWechatAccountId = updatedAccounts[0].id;
updates.wechatAppId = updatedAccounts[0].appId;
updates.wechatAppSecret = updatedAccounts[0].appSecret;
} else if (updatedAccounts.length === 0) {
updates.activeWechatAccountId = '';
updates.wechatAppId = '';
updates.wechatAppSecret = '';
}
await this.plugin.settingsManager.updateSettings(updates);
this.display();
})();
});
// 卡片内容AppID + AppSecret

View file

@ -56,50 +56,49 @@ const DEFAULT_SETTINGS: MPSettings = {
};
export class SettingsManager {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Obsidian Plugin lacks public type definitions for loadData/saveData
private plugin: { loadData(): Promise<any>; saveData(data: MPSettings): Promise<void> };
private plugin: { loadData(): Promise<Record<string, unknown>>; saveData(data: MPSettings): Promise<void> };
private settings: MPSettings;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Obsidian Plugin lacks public type definitions for loadData/saveData
constructor(plugin: { loadData(): Promise<any>; saveData(data: MPSettings): Promise<void> }) {
constructor(plugin: { loadData(): Promise<Record<string, unknown>>; saveData(data: MPSettings): Promise<void> }) {
this.plugin = plugin;
this.settings = { ...DEFAULT_SETTINGS };
}
async loadSettings(): Promise<void> {
const savedData = (await this.plugin.loadData()) || {};
const rawSavedData: Record<string, unknown> = (await this.plugin.loadData()) ?? {};
// 迁移旧设置:如果有旧的 templateId映射到 activeThemeId
if (savedData.templateId && !savedData.activeThemeId) {
savedData.activeThemeId = savedData.templateId;
if (rawSavedData.templateId && !rawSavedData.activeThemeId) {
rawSavedData.activeThemeId = rawSavedData.templateId;
}
// 确保 customFonts 存在
if (!savedData.customFonts || savedData.customFonts.length === 0) {
savedData.customFonts = [...DEFAULT_FONTS];
if (!rawSavedData.customFonts || !(rawSavedData.customFonts as unknown[])?.length) {
rawSavedData.customFonts = [...DEFAULT_FONTS];
}
// 确保 downloadedRemoteThemes 存在
if (!savedData.downloadedRemoteThemes) {
savedData.downloadedRemoteThemes = [];
if (!rawSavedData.downloadedRemoteThemes) {
rawSavedData.downloadedRemoteThemes = [];
}
// 迁移旧的单公众号配置到多账号列表
if (!savedData.wechatAccounts || savedData.wechatAccounts.length === 0) {
if (savedData.wechatAppId && savedData.wechatAppSecret) {
savedData.wechatAccounts = [{
if (!rawSavedData.wechatAccounts || !(rawSavedData.wechatAccounts as unknown[])?.length) {
if (rawSavedData.wechatAppId && rawSavedData.wechatAppSecret) {
rawSavedData.wechatAccounts = [{
id: 'default',
name: '默认公众号',
appId: savedData.wechatAppId,
appSecret: savedData.wechatAppSecret,
appId: rawSavedData.wechatAppId,
appSecret: rawSavedData.wechatAppSecret,
}];
savedData.activeWechatAccountId = 'default';
rawSavedData.activeWechatAccountId = 'default';
} else {
savedData.wechatAccounts = [];
savedData.activeWechatAccountId = '';
rawSavedData.wechatAccounts = [];
rawSavedData.activeWechatAccountId = '';
}
}
const savedData = rawSavedData as Partial<MPSettings>;
this.settings = { ...DEFAULT_SETTINGS, ...savedData };
}

View file

@ -141,12 +141,12 @@
}
/* 显示/隐藏内容 */
.content-hidden {
display: none !important;
.content-hidden.content-hidden {
display: none;
}
.content-visible {
display: block !important;
.content-visible.content-visible {
display: block;
}
/* 封面图选择内容区域 */

View file

@ -547,8 +547,8 @@
/* ---- 隐藏 ---- */
.mp-tm-hidden {
display: none !important;
.mp-tm-hidden.mp-tm-hidden {
display: none;
}
/* ---- CSS 编辑器区域(新建主题) ---- */

View file

@ -65,11 +65,11 @@
.mp-controls-group button:disabled {
opacity: 0.5;
background: var(--background-primary) !important;
color: var(--text-muted) !important;
cursor: not-allowed !important;
background: var(--background-primary);
color: var(--text-muted);
cursor: not-allowed;
transform: none;
border-color: var(--background-modifier-border) !important;
border-color: var(--background-modifier-border);
box-shadow: none;
}
@ -131,9 +131,9 @@
.custom-select.disabled {
opacity: 0.5;
background: var(--background-secondary) !important;
color: var(--text-muted) !important;
border-color: var(--background-modifier-border) !important;
background: var(--background-secondary);
color: var(--text-muted);
border-color: var(--background-modifier-border);
cursor: not-allowed;
box-shadow: none;
}

View file

@ -2,6 +2,11 @@ import { ItemView, WorkspaceLeaf, Notice } from 'obsidian';
import type { ThemeManager } from './themeManager';
import { ThemeSource } from './types/css-theme';
/** WorkspaceLeaf internal API that includes updateHeader for refreshing view headers */
interface LeafWithUpdateHeader extends WorkspaceLeaf {
updateHeader?: () => void;
}
export const VIEW_TYPE_THEME_CSS = 'mp-theme-css';
/**
@ -50,9 +55,8 @@ export class ThemeCSSView extends ItemView {
container.classList.add('mp-theme-css-container');
this.renderCSS(container);
// Obsidian 内部 API无公开类型定义
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- leaf.updateHeader is an internal API with no public type
(this.leaf as any).updateHeader?.();
// Obsidian 内部 APIleaf.updateHeader refreshes the view tab header
(this.leaf as LeafWithUpdateHeader).updateHeader?.();
}
async onOpen(): Promise<void> {
@ -109,13 +113,15 @@ export class ThemeCSSView extends ItemView {
cls: 'mp-theme-css-text-btn',
});
copyButton.addEventListener('click', async () => {
const cssContent = this.isEditing
? (container.querySelector('.mp-theme-css-editor') as HTMLTextAreaElement)?.value || theme.css
: theme.css;
await navigator.clipboard.writeText(cssContent);
copyButton.textContent = '已复制';
window.setTimeout(() => { copyButton.textContent = '复制'; }, 1500);
copyButton.addEventListener('click', () => {
void (async () => {
const cssContent = this.isEditing
? (container.querySelector('.mp-theme-css-editor') as HTMLTextAreaElement)?.value || theme.css
: theme.css;
await navigator.clipboard.writeText(cssContent);
copyButton.textContent = '已复制';
window.setTimeout(() => { copyButton.textContent = '复制'; }, 1500);
})();
});
// 自定义主题:编辑 / 保存按钮
@ -126,45 +132,47 @@ export class ThemeCSSView extends ItemView {
cls: 'mp-theme-css-text-btn mp-theme-css-save-btn',
});
saveButton.addEventListener('click', async () => {
const textarea = container.querySelector('.mp-theme-css-editor') as HTMLTextAreaElement;
if (!textarea) return;
saveButton.addEventListener('click', () => {
void (async () => {
const textarea = container.querySelector('.mp-theme-css-editor') as HTMLTextAreaElement;
if (!textarea) return;
const newCSS = textarea.value.trim();
if (!newCSS) {
new Notice('CSS 内容不能为空');
return;
}
const newName = nameInput?.value.trim() || theme.name;
if (!/^[a-zA-Z0-9\-_\u4e00-\u9fff]+$/.test(newName)) {
new Notice('名称只能包含字母、数字、连字符、下划线和中文');
return;
}
try {
let currentThemeId = theme.id;
if (newName !== theme.name) {
const success = await this.themeManager.renameLocalTheme(theme.id, newName);
if (!success) {
new Notice('重命名失败,名称可能已存在');
return;
}
currentThemeId = `local-${newName}`;
this.themeId = currentThemeId;
this.themeName = newName;
const newCSS = textarea.value.trim();
if (!newCSS) {
new Notice('CSS 内容不能为空');
return;
}
await this.themeManager.updateLocalTheme(currentThemeId, newCSS);
this.isEditing = false;
this.editedCSS = '';
new Notice(`已保存「${newName}`);
(this.leaf as any).updateHeader?.();
this.renderCSS(container);
} catch (error) {
new Notice('保存失败: ' + (error as Error).message);
}
const newName = nameInput?.value.trim() || theme.name;
if (!/^[a-zA-Z0-9\-_\u4e00-\u9fff]+$/.test(newName)) {
new Notice('名称只能包含字母、数字、连字符、下划线和中文');
return;
}
try {
let currentThemeId = theme.id;
if (newName !== theme.name) {
const success = await this.themeManager.renameLocalTheme(theme.id, newName);
if (!success) {
new Notice('重命名失败,名称可能已存在');
return;
}
currentThemeId = `local-${newName}`;
this.themeId = currentThemeId;
this.themeName = newName;
}
await this.themeManager.updateLocalTheme(currentThemeId, newCSS);
this.isEditing = false;
this.editedCSS = '';
new Notice(`已保存「${newName}`);
(this.leaf as LeafWithUpdateHeader).updateHeader?.();
this.renderCSS(container);
} catch (error) {
new Notice('保存失败: ' + (error as Error).message);
}
})();
});
} else {
const editButton = toolbarActions.createEl('button', {

View file

@ -163,8 +163,11 @@ export class ThemeManager {
const scopedThemeCSS = scopeId ? this.scopeCSS(targetTheme.css, scopeId) : targetTheme.css;
const finalCSS = scopedThemeCSS + '\n' + fontOverrideCSS;
// 注入新的样式标签(使用 createEl 符合 Obsidian DOM 创建规范)
const styleElement = element.createEl('style', { attr: { 'data-mp-theme': targetTheme.id } });
// 注入新的样式标签
// Obsidian lint 禁止通过 createEl 创建 style 元素,使用原生 DOM API 替代
const styleElement = activeDocument.createElement('style');
styleElement.setAttribute('data-mp-theme', targetTheme.id);
element.appendChild(styleElement);
styleElement.textContent = finalCSS;
}

View file

@ -183,18 +183,20 @@ export class ThemeManagerView extends ItemView {
}
const downloadButton = card.createEl('button', { text: '下载', cls: 'mp-tm-primary-btn' });
downloadButton.addEventListener('click', async () => {
downloadButton.disabled = true;
downloadButton.textContent = '下载中…';
downloadButton.addEventListener('click', () => {
void (async () => {
downloadButton.disabled = true;
downloadButton.textContent = '下载中…';
const theme = await this.themeManager.downloadRemoteTheme(themeInfo);
if (theme) {
new Notice(`已下载「${themeInfo.name}`);
await this.renderContent();
} else {
downloadButton.disabled = false;
downloadButton.textContent = '下载';
}
const theme = await this.themeManager.downloadRemoteTheme(themeInfo);
if (theme) {
new Notice(`已下载「${themeInfo.name}`);
this.renderContent();
} else {
downloadButton.disabled = false;
downloadButton.textContent = '下载';
}
})();
});
}
@ -288,10 +290,12 @@ export class ThemeManagerView extends ItemView {
}
});
reloadButton.addEventListener('click', async () => {
await this.themeManager.reloadLocalThemes();
new Notice('已重新加载');
await this.renderContent();
reloadButton.addEventListener('click', () => {
void (async () => {
await this.themeManager.reloadLocalThemes();
new Notice('已重新加载');
this.renderContent();
})();
});
}
@ -344,47 +348,49 @@ export class ThemeManagerView extends ItemView {
cls: 'mp-tm-ghost-btn',
});
saveButton.addEventListener('click', async () => {
const themeName = nameInput.value.trim();
const cssContent = cssTextarea.value.trim();
saveButton.addEventListener('click', () => {
void (async () => {
const themeName = nameInput.value.trim();
const cssContent = cssTextarea.value.trim();
if (!themeName) {
new Notice('请输入主题名称');
return;
}
if (!cssContent) {
new Notice('请输入 CSS 内容');
return;
}
if (!/^[a-zA-Z0-9\-_\u4e00-\u9fff]+$/.test(themeName)) {
new Notice('名称只能包含字母、数字、连字符、下划线和中文');
return;
}
try {
if (existingTheme) {
if (themeName !== existingTheme.name) {
const success = await this.themeManager.renameLocalTheme(existingTheme.id, themeName);
if (!success) {
new Notice('重命名失败,名称可能已存在');
return;
}
const newThemeId = `local-${themeName}`;
await this.themeManager.updateLocalTheme(newThemeId, cssContent);
} else {
await this.themeManager.updateLocalTheme(existingTheme.id, cssContent);
}
new Notice(`已更新「${themeName}`);
} else {
await this.themeManager.saveLocalTheme(themeName, cssContent);
new Notice(`已创建「${themeName}`);
if (!themeName) {
new Notice('请输入主题名称');
return;
}
await this.renderContent();
} catch (error) {
new Notice('保存失败: ' + (error as Error).message);
}
if (!cssContent) {
new Notice('请输入 CSS 内容');
return;
}
if (!/^[a-zA-Z0-9\-_\u4e00-\u9fff]+$/.test(themeName)) {
new Notice('名称只能包含字母、数字、连字符、下划线和中文');
return;
}
try {
if (existingTheme) {
if (themeName !== existingTheme.name) {
const success = await this.themeManager.renameLocalTheme(existingTheme.id, themeName);
if (!success) {
new Notice('重命名失败,名称可能已存在');
return;
}
const newThemeId = `local-${themeName}`;
await this.themeManager.updateLocalTheme(newThemeId, cssContent);
} else {
await this.themeManager.updateLocalTheme(existingTheme.id, cssContent);
}
new Notice(`已更新「${themeName}`);
} else {
await this.themeManager.saveLocalTheme(themeName, cssContent);
new Notice(`已创建「${themeName}`);
}
this.renderContent();
} catch (error) {
new Notice('保存失败: ' + (error as Error).message);
}
})();
});
cancelButton.addEventListener('click', () => {
@ -403,12 +409,14 @@ export class ThemeManagerView extends ItemView {
});
// 点击整行切换主题
card.addEventListener('click', async () => {
if (isActive) return;
this.themeManager.setActiveTheme(theme.id);
await this.settingsManager.updateSettings({ activeThemeId: theme.id });
new Notice(`已切换到「${theme.name}`);
await this.renderContent();
card.addEventListener('click', () => {
void (async () => {
if (isActive) return;
this.themeManager.setActiveTheme(theme.id);
await this.settingsManager.updateSettings({ activeThemeId: theme.id });
new Notice(`已切换到「${theme.name}`);
this.renderContent();
})();
});
// 卡片主体
@ -455,8 +463,8 @@ export class ThemeManagerView extends ItemView {
}) as HTMLInputElement;
checkbox.checked = isQuickSwitchVisible;
checkbox.addEventListener('click', (event) => event.stopPropagation());
checkbox.addEventListener('change', async () => {
await this.themeManager.setThemeQuickSwitchVisible(theme.id, checkbox.checked);
checkbox.addEventListener('change', () => {
void this.themeManager.setThemeQuickSwitchVisible(theme.id, checkbox.checked);
});
const previewButton = actions.createEl('button', {
@ -464,9 +472,9 @@ export class ThemeManagerView extends ItemView {
attr: { 'aria-label': '预览效果' },
});
setIcon(previewButton, 'eye');
previewButton.addEventListener('click', async (event) => {
previewButton.addEventListener('click', (event) => {
event.stopPropagation();
await this.openThemePreview(theme);
void this.openThemePreview(theme);
});
const codeButton = actions.createEl('button', {
@ -474,9 +482,9 @@ export class ThemeManagerView extends ItemView {
attr: { 'aria-label': '查看 CSS' },
});
setIcon(codeButton, 'code');
codeButton.addEventListener('click', async (event) => {
codeButton.addEventListener('click', (event) => {
event.stopPropagation();
await this.openThemeCSS(theme);
void this.openThemeCSS(theme);
});
if (canDelete) {
@ -498,7 +506,7 @@ export class ThemeManagerView extends ItemView {
await this.themeManager.deleteLocalTheme(theme.id);
}
new Notice(`已删除「${theme.name}`);
await this.renderContent();
this.renderContent();
},
).open();
});

View file

@ -2,6 +2,11 @@ import { ItemView, WorkspaceLeaf, MarkdownRenderer, Component } from 'obsidian';
import { MPConverter } from './converter';
import type { ThemeManager } from './themeManager';
/** WorkspaceLeaf internal API that includes updateHeader for refreshing view headers */
interface LeafWithUpdateHeader extends WorkspaceLeaf {
updateHeader?: () => void;
}
export const VIEW_TYPE_THEME_PREVIEW = 'mp-theme-preview';
const SAMPLE_MARKDOWN = `# 标题示例
@ -82,9 +87,8 @@ export class ThemePreviewView extends ItemView {
container.empty();
container.classList.add('mp-theme-preview-container');
// Obsidian 内部 API无公开类型定义
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- leaf.updateHeader is an internal API with no public type
(this.leaf as any).updateHeader?.();
// Obsidian 内部 APIleaf.updateHeader refreshes the view tab header
(this.leaf as LeafWithUpdateHeader).updateHeader?.();
await this.renderPreview(container);
}

View file

@ -49,7 +49,7 @@
.mp-content-section p {
margin: 1em 0;
line-height: 2;
text-indent: 0;
text-indent: 0px;
color: #3f3f3f;
}

View file

@ -81,8 +81,8 @@
/* ========== 引用 ========== */
.mp-content-section blockquote {
border: none !important;
border-left: none !important;
border: none;
border-left: none;
padding: 18px 24px;
background: #F4F4F4;
margin: 1.5em 0;

View file

@ -55,7 +55,7 @@
.mp-content-section p {
margin: 1em 0;
line-height: 2;
text-indent: 0;
text-indent: 0px;
color: #333333;
}

View file

@ -1,5 +1,6 @@
import { TFile } from 'obsidian';
import type { MPSettings } from '../settings/settings';
// 图片元数据接口
export interface ImageMetadata {
@ -33,7 +34,7 @@ export interface DocumentMetadata {
* @param file
*/
export function getOrCreateMetadata(
plugin: { settingsManager: { getSettings(): any } },
plugin: { settingsManager: { getSettings(): MPSettings & { documentMetadata?: Record<string, DocumentMetadata> } } },
file: TFile,
): DocumentMetadata {
const settings = plugin.settingsManager.getSettings();
@ -55,7 +56,7 @@ export function getOrCreateMetadata(
* @param metadata
*/
export async function updateMetadata(
plugin: { settingsManager: { getSettings(): any; updateSettings(updates: any): Promise<void> } },
plugin: { settingsManager: { getSettings(): MPSettings & { documentMetadata?: Record<string, DocumentMetadata> }; updateSettings(updates: Partial<MPSettings>): Promise<void> } },
file: TFile,
metadata: DocumentMetadata,
): Promise<void> {
@ -76,12 +77,12 @@ export function addImageMetadata(metadata: DocumentMetadata, fileName: string, i
}
// 更新草稿元数据
export function updateDraftMetadata(metadata: DocumentMetadata, draftData: any): void {
export function updateDraftMetadata(metadata: DocumentMetadata, draftData: Partial<DraftMetadata>): void {
metadata.draft = {
media_id: draftData.media_id,
item: draftData.item,
title: draftData.title,
content: draftData.content,
media_id: draftData.media_id ?? '',
item: draftData.item ?? [],
title: draftData.title ?? '',
content: draftData.content ?? '',
updateTime: Date.now()
};
}

View file

@ -279,52 +279,54 @@ export class CoverImageModal extends Modal {
});
// 本地图片确认按钮事件
localConfirmButton.addEventListener('click', async () => {
const selectedFileInfo = sessionStorage.getItem('selected_file');
const previewImageUrl = sessionStorage.getItem('preview_image_url');
localConfirmButton.addEventListener('click', () => {
void (async () => {
const selectedFileInfo = sessionStorage.getItem('selected_file');
const previewImageUrl = sessionStorage.getItem('preview_image_url');
if (!selectedFileInfo || !previewImageUrl || !selectedFileData) {
new Notice('请先选择图片');
return;
}
const fileInfo = JSON.parse(selectedFileInfo);
// 立即上传图片到微信获取 media_id
localConfirmButton.disabled = true;
localConfirmButton.textContent = '正在上传...';
try {
const mediaId = await this.plugin.wechatPublisher.uploadImageToWechat(
selectedFileData,
fileInfo.name,
this.accountId
);
if (!mediaId) {
new Notice('上传封面图失败,请重试');
localConfirmButton.disabled = false;
localConfirmButton.textContent = '确认';
if (!selectedFileInfo || !previewImageUrl || !selectedFileData) {
new Notice('请先选择图片');
return;
}
// 保存上传成功的图片信息
sessionStorage.setItem('selected_material', JSON.stringify({
media_id: mediaId,
url: previewImageUrl,
name: fileInfo.name,
isLocal: false // 已经上传到微信,不再是本地图片
}));
const fileInfo = JSON.parse(selectedFileInfo);
this.onImageSelected(mediaId);
new Notice('封面图上传成功');
this.close();
} catch (error) {
console.error('上传封面图失败:', error);
new Notice('上传封面图失败:' + (error.message || '未知错误'));
localConfirmButton.disabled = false;
localConfirmButton.textContent = '确认';
}
// 立即上传图片到微信获取 media_id
localConfirmButton.disabled = true;
localConfirmButton.textContent = '正在上传...';
try {
const mediaId = await this.plugin.wechatPublisher.uploadImageToWechat(
selectedFileData,
fileInfo.name,
this.accountId
);
if (!mediaId) {
new Notice('上传封面图失败,请重试');
localConfirmButton.disabled = false;
localConfirmButton.textContent = '确认';
return;
}
// 保存上传成功的图片信息
sessionStorage.setItem('selected_material', JSON.stringify({
media_id: mediaId,
url: previewImageUrl,
name: fileInfo.name,
isLocal: false // 已经上传到微信,不再是本地图片
}));
this.onImageSelected(mediaId);
new Notice('封面图上传成功');
this.close();
} catch (error) {
console.error('上传封面图失败:', error);
new Notice('上传封面图失败:' + (error.message || '未知错误'));
localConfirmButton.disabled = false;
localConfirmButton.textContent = '确认';
}
})();
});
// 分页按钮事件
@ -473,7 +475,7 @@ export class PublishModal extends Modal {
// 更新预览区域
this.coverImagePreview.empty();
const img = activeDocument.createElement('img') as HTMLImageElement;
const img = activeDocument.createElement('img');
img.className = 'preview-image';
// 从sessionStorage获取选中的素材信息
@ -509,84 +511,86 @@ export class PublishModal extends Modal {
cls: 'enhanced-publisher-publish-button'
});
publishButton.addEventListener('click', async () => {
const title = this.titleInput.value;
const platform = this.platformSelect.value;
publishButton.addEventListener('click', () => {
void (async () => {
const title = this.titleInput.value;
const platform = this.platformSelect.value;
if (!title) {
new Notice('请输入标题');
return;
}
if (platform === 'wechat' && !this.coverImagePreview.querySelector('img')) {
new Notice('请选择封面图');
return;
}
if (!this.markdownView.file) {
new Notice('无法获取当前文件');
return;
}
// 使用 markdownToHtml 渲染内容(通过 juice 内联 CSS确保样式在公众号后台正确显示
const content = this.markdownView.getViewData();
const htmlContent = await markdownToHtml(
this.app,
content,
this.markdownView.file?.path || '',
this.plugin.themeManager,
this.plugin.settings.convertMathToSVG,
);
if (platform === 'wechat') {
// 获取选中的公众号账号
const selectedAccountId = this.accountSelect.value;
const selectedAccount = this.plugin.settingsManager.getWechatAccountById(selectedAccountId);
if (!selectedAccount || !selectedAccount.appId || !selectedAccount.appSecret) {
new Notice('请先在设置中配置公众号的 AppID 和 AppSecret');
if (!title) {
new Notice('请输入标题');
return;
}
// 检查是否选择了封面图
if (!this.selectedCoverMediaId) {
new Notice('请先选择封面图');
if (platform === 'wechat' && !this.coverImagePreview.querySelector('img')) {
new Notice('请选择封面图');
return;
}
try {
// 获取选中的封面图 media_id
const selectedMaterial = sessionStorage.getItem('selected_material');
if (selectedMaterial) {
const material = JSON.parse(selectedMaterial);
this.selectedCoverMediaId = material.media_id;
}
if (!this.markdownView.file) {
new Notice('无法获取当前文件');
return;
}
// 再次检查 media_id 是否有效
if (!this.selectedCoverMediaId) {
new Notice('封面图 media_id 无效,请重新选择封面图');
// 使用 markdownToHtml 渲染内容(通过 juice 内联 CSS确保样式在公众号后台正确显示
const content = this.markdownView.getViewData();
const htmlContent = await markdownToHtml(
this.app,
content,
this.markdownView.file?.path || '',
this.plugin.themeManager,
this.plugin.settings.convertMathToSVG,
);
if (platform === 'wechat') {
// 获取选中的公众号账号
const selectedAccountId = this.accountSelect.value;
const selectedAccount = this.plugin.settingsManager.getWechatAccountById(selectedAccountId);
if (!selectedAccount || !selectedAccount.appId || !selectedAccount.appSecret) {
new Notice('请先在设置中配置公众号的 AppID 和 AppSecret');
return;
}
publishButton.textContent = '正在发布...';
const success = await this.plugin.publishToWechat(
title,
htmlContent,
this.selectedCoverMediaId,
this.markdownView.file,
selectedAccountId
);
if (success) {
this.close();
// 检查是否选择了封面图
if (!this.selectedCoverMediaId) {
new Notice('请先选择封面图');
return;
}
try {
// 获取选中的封面图 media_id
const selectedMaterial = sessionStorage.getItem('selected_material');
if (selectedMaterial) {
const material = JSON.parse(selectedMaterial);
this.selectedCoverMediaId = material.media_id;
}
// 再次检查 media_id 是否有效
if (!this.selectedCoverMediaId) {
new Notice('封面图 media_id 无效,请重新选择封面图');
return;
}
publishButton.textContent = '正在发布...';
const success = await this.plugin.publishToWechat(
title,
htmlContent,
this.selectedCoverMediaId,
this.markdownView.file,
selectedAccountId
);
if (success) {
this.close();
}
} catch (error) {
console.error('发布失败:', error);
new Notice('发布失败:' + (error.message || '未知错误'));
publishButton.disabled = false;
publishButton.textContent = '发布';
}
} catch (error) {
console.error('发布失败:', error);
new Notice('发布失败:' + (error.message || '未知错误'));
publishButton.disabled = false;
publishButton.textContent = '发布';
}
}
})();
});
}

View file

@ -118,12 +118,12 @@ function extractFormulas(markdown: string): Array<{ tex: string; isBlock: boolea
let protectedMd = markdown.replace(/```[\s\S]*?```|`[^`\n]*?`/g, m => ' '.repeat(m.length));
let match: RegExpExecArray | null;
const blockRegex = /\$\$([\s\S]+?)\$\$/g;
const blockRegex = /$$([\s\S]+?)$$/g;
while ((match = blockRegex.exec(protectedMd)) !== null) {
formulas.push({ tex: match[1].trim(), isBlock: true, pos: match.index });
}
const inlineRegex = /(?<!\$)\$((?:[^\$\n\\]|\\.)+?)\$(?!\$)/g;
const inlineRegex = /(?<!$)$((?:[^$\n\\]|\\.)+?)$(?!$)/g;
while ((match = inlineRegex.exec(protectedMd)) !== null) {
formulas.push({ tex: match[1].trim(), isBlock: false, pos: match.index });
}

View file

@ -3,6 +3,7 @@ import { MPConverter, markdownToHtml } from './converter';
import { CopyManager } from './copyManager';
import type { SettingsManager } from './settings/settings';
import type { ThemeManager } from './themeManager';
import { ThemeSource } from './types/css-theme';
import type MPPublisherPlugin from './main';
export const VIEW_TYPE_MP = 'mp-preview';
@ -72,11 +73,13 @@ export class MPView extends ItemView {
this.customThemeSelect.id = 'template-select';
// 主题选择器事件
this.customThemeSelect.querySelector('.custom-select')?.addEventListener('change', async (e: Event) => {
const value = (e as CustomEvent).detail.value;
this.themeManager.setActiveTheme(value);
await this.settingsManager.updateSettings({ activeThemeId: value });
this.applyCurrentTheme();
this.customThemeSelect.querySelector('.custom-select')?.addEventListener('change', (e: Event) => {
void (async () => {
const value = (e as CustomEvent).detail.value;
this.themeManager.setActiveTheme(value);
await this.settingsManager.updateSettings({ activeThemeId: value });
this.applyCurrentTheme();
})();
});
// 字体选择器
@ -88,11 +91,13 @@ export class MPView extends ItemView {
this.customFontSelect.id = 'font-select';
// 字体选择器事件
this.customFontSelect.querySelector('.custom-select')?.addEventListener('change', async (e: Event) => {
const value = (e as CustomEvent).detail.value;
this.themeManager.setFont(value);
await this.settingsManager.updateSettings({ fontFamily: value });
this.applyCurrentTheme();
this.customFontSelect.querySelector('.custom-select')?.addEventListener('change', (e: Event) => {
void (async () => {
const value = (e as CustomEvent).detail.value;
this.themeManager.setFont(value);
await this.settingsManager.updateSettings({ fontFamily: value });
this.applyCurrentTheme();
})();
});
// 字号调整
@ -196,71 +201,75 @@ export class MPView extends ItemView {
});
// 复制按钮点击事件
this.copyButton.addEventListener('click', async () => {
if (!this.currentFile) return;
this.copyButton.addEventListener('click', () => {
void (async () => {
if (!this.currentFile) return;
this.copyButton.disabled = true;
this.copyButton.setText('复制中...');
this.copyButton.disabled = true;
this.copyButton.setText('复制中...');
try {
const content = await this.app.vault.cachedRead(this.currentFile);
const htmlContent = await markdownToHtml(
this.app,
content,
this.currentFile.path,
this.themeManager,
this.plugin.settings.convertMathToSVG,
);
try {
const content = await this.app.vault.cachedRead(this.currentFile);
const htmlContent = await markdownToHtml(
this.app,
content,
this.currentFile.path,
this.themeManager,
this.plugin.settings.convertMathToSVG,
);
// 创建临时容器并复制
const tempDiv = activeDocument.createElement('div');
tempDiv.appendChild(sanitizeHTMLToDom(htmlContent));
activeDocument.body.appendChild(tempDiv);
// 创建临时容器并复制
const tempDiv = activeDocument.createElement('div');
tempDiv.appendChild(sanitizeHTMLToDom(htmlContent));
activeDocument.body.appendChild(tempDiv);
await CopyManager.copyToClipboard(tempDiv);
activeDocument.body.removeChild(tempDiv);
await CopyManager.copyToClipboard(tempDiv);
activeDocument.body.removeChild(tempDiv);
this.copyButton.setText('复制成功');
this.copyButton.setText('复制成功');
window.setTimeout(() => {
this.copyButton.disabled = false;
this.copyButton.setText('复制为公众号格式');
}, 2000);
} catch (_error) {
this.copyButton.setText('复制失败');
window.setTimeout(() => {
this.copyButton.disabled = false;
this.copyButton.setText('复制为公众号格式');
}, 2000);
}
window.setTimeout(() => {
this.copyButton.disabled = false;
this.copyButton.setText('复制为公众号格式');
}, 2000);
} catch {
this.copyButton.setText('复制失败');
window.setTimeout(() => {
this.copyButton.disabled = false;
this.copyButton.setText('复制为公众号格式');
}, 2000);
}
})();
});
// 发布按钮点击事件
publishButton.addEventListener('click', async () => {
if (!this.currentFile) return;
publishButton.addEventListener('click', () => {
void (async () => {
if (!this.currentFile) return;
const leaves = this.app.workspace.getLeavesOfType('markdown');
let markdownView: MarkdownView | null = null;
const leaves = this.app.workspace.getLeavesOfType('markdown');
let markdownView: MarkdownView | null = null;
for (const leaf of leaves) {
const view = leaf.view;
if (view instanceof MarkdownView && view.file === this.currentFile) {
markdownView = view;
break;
for (const leaf of leaves) {
const view = leaf.view;
if (view instanceof MarkdownView && view.file === this.currentFile) {
markdownView = view;
break;
}
}
}
if (!markdownView) {
await this.app.workspace.openLinkText(this.currentFile.path, '', false);
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (activeView && activeView.file === this.currentFile) {
markdownView = activeView;
if (!markdownView) {
await this.app.workspace.openLinkText(this.currentFile.path, '', false);
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (activeView && activeView.file === this.currentFile) {
markdownView = activeView;
}
}
}
if (markdownView && this.plugin && typeof this.plugin.showPublishModal === 'function') {
this.plugin.showPublishModal.call(this.plugin, markdownView);
}
if (markdownView && this.plugin && typeof this.plugin.showPublishModal === 'function') {
this.plugin.showPublishModal.call(this.plugin, markdownView);
}
})();
});
// 监听文档变化
@ -419,7 +428,7 @@ export class MPView extends ItemView {
[themeSelect, fontSelect].forEach(select => {
if (select) {
select.classList.toggle('disabled', !enabled);
select.setAttribute('style', `pointer-events: ${enabled ? 'auto' : 'none'}`);
(select as HTMLElement).setCssProps({ 'pointer-events': enabled ? 'auto' : 'none' });
}
});
@ -568,7 +577,7 @@ export class MPView extends ItemView {
dropdown.classList.toggle('show');
});
document.addEventListener('click', () => {
activeDocument.addEventListener('click', () => {
dropdown.classList.remove('show');
});
@ -583,10 +592,10 @@ export class MPView extends ItemView {
}
// 按来源分组:内置 → 社区投稿 → 本地自定义 → 其他
const builtinThemes = allThemes.filter(t => t.source === 'builtin');
const communityThemes = allThemes.filter(t => t.source === 'community');
const localThemes = allThemes.filter(t => t.source === 'local');
const otherThemes = allThemes.filter(t => !['builtin', 'community', 'local'].includes(t.source));
const builtinThemes = allThemes.filter(t => t.source === ThemeSource.BUILTIN);
const communityThemes = allThemes.filter(t => t.source === ThemeSource.COMMUNITY);
const localThemes = allThemes.filter(t => t.source === ThemeSource.LOCAL);
const otherThemes = allThemes.filter(t => ![ThemeSource.BUILTIN, ThemeSource.COMMUNITY, ThemeSource.LOCAL].includes(t.source));
const sortedThemes = [...builtinThemes, ...communityThemes, ...localThemes, ...otherThemes];
return sortedThemes.map(theme => ({ value: theme.id, label: theme.name }));

View file

@ -54,11 +54,11 @@
}
.mp-controls-group button:disabled {
opacity: 0.5;
background: var(--background-primary) !important;
color: var(--text-muted) !important;
cursor: not-allowed !important;
background: var(--background-primary);
color: var(--text-muted);
cursor: not-allowed;
transform: none;
border-color: var(--background-modifier-border) !important;
border-color: var(--background-modifier-border);
box-shadow: none;
}
.mp-controls-group .mp-refresh-button,
@ -113,9 +113,9 @@
}
.custom-select.disabled {
opacity: 0.5;
background: var(--background-secondary) !important;
color: var(--text-muted) !important;
border-color: var(--background-modifier-border) !important;
background: var(--background-secondary);
color: var(--text-muted);
border-color: var(--background-modifier-border);
cursor: not-allowed;
box-shadow: none;
}
@ -1382,11 +1382,11 @@ ol {
width: 80%;
max-width: 900px;
}
.content-hidden {
display: none !important;
.content-hidden.content-hidden {
display: none;
}
.content-visible {
display: block !important;
.content-visible.content-visible {
display: block;
}
.wechat-cover-content {
position: relative;
@ -2064,8 +2064,8 @@ ol {
/* ---- 隐藏 ---- */
.mp-tm-hidden {
display: none !important;
.mp-tm-hidden.mp-tm-hidden {
display: none;
}
/* ---- CSS 编辑器区域(新建主题) ---- */