diff --git a/CHANGELOG.md b/CHANGELOG.md index cb460ac..fb53269 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` / `Partial` +- README 添加英文标题和描述 +- `codeEl.innerHTML = ''` → `codeEl.empty()` + +--- + ## [2.6.1] - 2026-06-23 ### 🔧 代码规范 diff --git a/README.md b/README.md index 024a6f8..3dfb8fd 100644 --- a/README.md +++ b/README.md @@ -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 主题**:默认、优雅、暗色、极简、清新绿、暖橙、猩红、学术 diff --git a/esbuild.config.mjs b/esbuild.config.mjs index ad95113..cee5f99 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -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 = `/* diff --git a/manifest.json b/manifest.json index f8eaa45..08d12fd 100644 --- a/manifest.json +++ b/manifest.json @@ -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", diff --git a/package.json b/package.json index b478456..ad10a7b 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/converter.ts b/src/converter.ts index 0c584fc..e1781fa 100644 --- a/src/converter.ts +++ b/src/converter.ts @@ -307,16 +307,23 @@ function applyCodeHighlightStyles(container: HTMLElement): void { }); }); } - /** - * 将代码块中所有普通空格(U+0020)替换为不间断空格(U+00A0) - * 微信公众号富文本引擎不支持 CSS white-space: pre-wrap,会折叠所有连续普通空格 - * 不仅行首缩进空格会丢失,行内的对齐空格、key-value 之间的空格也会被折叠 - * U+00A0(不间断空格)在公众号中不会被折叠,能正确保留所有代码空格 + * 将代码块每行包裹在 section 中,用 padding-left 实现缩进 + * 同时将行内空格替换为 NBSP(U+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'); diff --git a/src/copyManager.ts b/src/copyManager.ts index 3fe744c..109e20f 100644 --- a/src/copyManager.ts +++ b/src/copyManager.ts @@ -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(); } diff --git a/src/main.ts b/src/main.ts index 5af928a..7d88510 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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(); }, }); diff --git a/src/publisher/wechat.ts b/src/publisher/wechat.ts index 81e9c11..6b4eeaa 100644 --- a/src/publisher/wechat.ts +++ b/src/publisher/wechat.ts @@ -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 { 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, accountId?: string): Promise { + private async requestWithTokenRetry(requestFn: (token: string) => Promise, accountId?: string): Promise { 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) { + 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); diff --git a/src/settings/MPSettingTab.ts b/src/settings/MPSettingTab.ts index 6684bd9..ec9f9b8 100644 --- a/src/settings/MPSettingTab.ts +++ b/src/settings/MPSettingTab.ts @@ -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 = { - 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 = { + 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 diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 215eaac..ee4c462 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -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; saveData(data: MPSettings): Promise }; + private plugin: { loadData(): Promise>; saveData(data: MPSettings): Promise }; 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; saveData(data: MPSettings): Promise }) { + constructor(plugin: { loadData(): Promise>; saveData(data: MPSettings): Promise }) { this.plugin = plugin; this.settings = { ...DEFAULT_SETTINGS }; } async loadSettings(): Promise { - const savedData = (await this.plugin.loadData()) || {}; + const rawSavedData: Record = (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; this.settings = { ...DEFAULT_SETTINGS, ...savedData }; } diff --git a/src/styles/index.css b/src/styles/index.css index d3c8403..c729f13 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -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; } /* 封面图选择内容区域 */ diff --git a/src/styles/theme-manager.css b/src/styles/theme-manager.css index 33f6712..346232f 100644 --- a/src/styles/theme-manager.css +++ b/src/styles/theme-manager.css @@ -547,8 +547,8 @@ /* ---- 隐藏 ---- */ -.mp-tm-hidden { - display: none !important; +.mp-tm-hidden.mp-tm-hidden { + display: none; } /* ---- CSS 编辑器区域(新建主题) ---- */ diff --git a/src/styles/view/layout.css b/src/styles/view/layout.css index 8223f7d..04002a3 100644 --- a/src/styles/view/layout.css +++ b/src/styles/view/layout.css @@ -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; } diff --git a/src/themeCSSView.ts b/src/themeCSSView.ts index 4f7b880..56c0865 100644 --- a/src/themeCSSView.ts +++ b/src/themeCSSView.ts @@ -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 内部 API,leaf.updateHeader refreshes the view tab header + (this.leaf as LeafWithUpdateHeader).updateHeader?.(); } async onOpen(): Promise { @@ -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', { diff --git a/src/themeManager.ts b/src/themeManager.ts index 3d8ad55..4c5cfe6 100644 --- a/src/themeManager.ts +++ b/src/themeManager.ts @@ -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; } diff --git a/src/themeManagerView.ts b/src/themeManagerView.ts index c3ca040..11cce0b 100644 --- a/src/themeManagerView.ts +++ b/src/themeManagerView.ts @@ -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(); }); diff --git a/src/themePreviewView.ts b/src/themePreviewView.ts index ad4b5bb..8cec8c1 100644 --- a/src/themePreviewView.ts +++ b/src/themePreviewView.ts @@ -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 内部 API,leaf.updateHeader refreshes the view tab header + (this.leaf as LeafWithUpdateHeader).updateHeader?.(); await this.renderPreview(container); } diff --git a/src/themes/builtin/elegant.css b/src/themes/builtin/elegant.css index eea957f..de42723 100644 --- a/src/themes/builtin/elegant.css +++ b/src/themes/builtin/elegant.css @@ -49,7 +49,7 @@ .mp-content-section p { margin: 1em 0; line-height: 2; - text-indent: 0; + text-indent: 0px; color: #3f3f3f; } diff --git a/src/themes/custom/colorful-style.css b/src/themes/custom/colorful-style.css index 2b34f5f..4ac1025 100644 --- a/src/themes/custom/colorful-style.css +++ b/src/themes/custom/colorful-style.css @@ -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; diff --git a/src/themes/custom/wechat-green.css b/src/themes/custom/wechat-green.css index abfe3a3..493fa5f 100644 --- a/src/themes/custom/wechat-green.css +++ b/src/themes/custom/wechat-green.css @@ -55,7 +55,7 @@ .mp-content-section p { margin: 1em 0; line-height: 2; - text-indent: 0; + text-indent: 0px; color: #333333; } diff --git a/src/types/metadata.ts b/src/types/metadata.ts index 8e299f8..4838b27 100644 --- a/src/types/metadata.ts +++ b/src/types/metadata.ts @@ -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 } } }, 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 } }, + plugin: { settingsManager: { getSettings(): MPSettings & { documentMetadata?: Record }; updateSettings(updates: Partial): Promise } }, file: TFile, metadata: DocumentMetadata, ): Promise { @@ -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): 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() }; } diff --git a/src/ui/modals.ts b/src/ui/modals.ts index 3d6a7ae..ad6076b 100644 --- a/src/ui/modals.ts +++ b/src/ui/modals.ts @@ -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 = '发布'; } - } + })(); }); } diff --git a/src/utils/math-formula.ts b/src/utils/math-formula.ts index aa6a245..8c6bfd8 100644 --- a/src/utils/math-formula.ts +++ b/src/utils/math-formula.ts @@ -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 = /(? { - 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 })); diff --git a/styles.css b/styles.css index b4d2464..d725e61 100644 --- a/styles.css +++ b/styles.css @@ -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 编辑器区域(新建主题) ---- */