mirror of
https://github.com/joeytoday/obsidian-mp-publisher.git
synced 2026-07-22 06:53:08 +00:00
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:
parent
742cc40ce9
commit
3b87b9f1ff
26 changed files with 646 additions and 422 deletions
22
CHANGELOG.md
22
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<MPSettings>` / `Partial<DraftMetadata>`
|
||||
- README 添加英文标题和描述
|
||||
- `codeEl.innerHTML = ''` → `codeEl.empty()`
|
||||
|
||||
---
|
||||
|
||||
## [2.6.1] - 2026-06-23
|
||||
|
||||
### 🔧 代码规范
|
||||
|
|
|
|||
|
|
@ -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 主题**:默认、优雅、暗色、极简、清新绿、暖橙、猩红、学术
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
`/*
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
178
src/converter.ts
178
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');
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
10
src/main.ts
10
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();
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/* 封面图选择内容区域 */
|
||||
|
|
|
|||
|
|
@ -547,8 +547,8 @@
|
|||
|
||||
/* ---- 隐藏 ---- */
|
||||
|
||||
.mp-tm-hidden {
|
||||
display: none !important;
|
||||
.mp-tm-hidden.mp-tm-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ---- 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<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', {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@
|
|||
.mp-content-section p {
|
||||
margin: 1em 0;
|
||||
line-height: 2;
|
||||
text-indent: 0;
|
||||
text-indent: 0px;
|
||||
color: #3f3f3f;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
.mp-content-section p {
|
||||
margin: 1em 0;
|
||||
line-height: 2;
|
||||
text-indent: 0;
|
||||
text-indent: 0px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
|
|
|
|||
218
src/ui/modals.ts
218
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 = '发布';
|
||||
}
|
||||
}
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
|||
141
src/view.ts
141
src/view.ts
|
|
@ -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 }));
|
||||
|
|
|
|||
26
styles.css
26
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 编辑器区域(新建主题) ---- */
|
||||
|
|
|
|||
Loading…
Reference in a new issue