/* eslint-disable */
const { Plugin, Notice, TFile, Modal, Setting, MarkdownView, MarkdownRenderer, PluginSettingTab } = require('obsidian');
let electronClipboard = null;
try {
electronClipboard = require('electron').clipboard;
} catch (e) {
electronClipboard = null;
}
/* ========== 用户配置 ========== */
const DEFAULT_USER_CONFIG = {
currentPlatform: 'feishu',
imageMaxWidth: 1000,
imageQuality: 0.92,
displayMaxWidth: 800,
imageFormat: 'auto',
platformImageStrategies: {
wechat: 'balanced',
feishu: 'quality',
zhihu: 'balanced',
yuque: 'quality'
},
platformThemes: {
wechat: 'official',
feishu: 'minimal',
zhihu: 'knowledge',
yuque: 'soft'
},
customThemes: {
custom: {
name: '我的主题',
fontFamily: "'PingFang SC', 'Microsoft YaHei', sans-serif",
textColor: '#24303f',
mutedColor: '#64748b',
backgroundColor: '#ffffff',
accentColor: '#2563eb',
linkColor: '#2563eb',
blockquoteBorder: '#60a5fa',
blockquoteBackground: '#eff6ff',
codeBackground: '#f8fafc',
codeColor: '#1e293b',
headingColor: '#0f172a',
borderColor: '#dbe2ea',
borderRadius: '8px',
paragraphSpacing: '16px',
lineHeight: '1.75',
headingDivider: true,
tableHeaderFill: true,
imageRounded: true,
codeBlockBorder: true
}
},
customThemeEditorKey: 'custom',
customThemeSearchQuery: '',
customThemeOrder: ['custom'],
themePreviewPlatform: 'wechat',
themeListShowUsedOnly: false,
themeListUsedFirst: true,
themeListShowRecommendedOnly: false,
themeManagerListCollapsed: false,
lastAppliedRecommendedTheme: null,
themeManagerSidebarWidth: 280,
platformThemeOverrides: {
wechat: {},
feishu: {},
zhihu: {},
yuque: {}
},
compressGif: false,
enableCodeHighlight: true,
enableThemeStyles: true,
enableFrontmatter: false,
enableProgressNotice: true,
enablePreview: true,
parallelImageProcessing: true,
maxParallelImages: 5,
themeStylesCache: true,
includeFrontmatter: false,
clipboardFormats: ['text/html', 'text/plain'],
};
let USER_CONFIG = { ...DEFAULT_USER_CONFIG };
/* ========== 图片优化配置 ========== */
const IMAGE_CONFIG = {
MAX_WIDTH: 1000,
QUALITY: 0.85,
DISPLAY_MAX_WIDTH: 800,
FORMAT: 'jpeg',
COMPRESS_GIF: false
};
/* ========== 缓存管理 ========== */
const Cache = {
themeStyles: null,
themeType: null,
clear() {
this.themeStyles = null;
this.themeType = null;
}
};
/* ========== 平台预设配置 ========== */
const PLATFORM_PRESETS = {
wechat: {
name: '微信公众号',
imageMaxWidth: 900,
imageQuality: 0.9,
displayMaxWidth: '100%',
imageFormat: 'auto',
compressGif: false,
enableCodeHighlight: true,
enableThemeStyles: false,
enableProgressNotice: true,
description: '适合微信公众号,图片宽度900px,质量0.85',
compatibilityNotes: [
'代码块使用截图效果最佳',
'表格可能显示异常',
'样式已优化为内联样式'
],
styleStrategy: 'inline',
styleMode: 'inline-only',
preserveThemeLevel: 'medium',
supports: {
styleTag: false,
className: false,
table: 'partial',
codeBlock: 'partial',
math: 'weak'
},
transforms: {
flattenCallout: true,
simplifyTable: true,
simplifyNestedBlocks: true
},
previewFrame: {
background: '#f5f5f5',
padding: '20px 14px',
contentBackground: '#ffffff',
contentShadow: '0 8px 24px rgba(0,0,0,0.06)',
contentBorder: '1px solid #ebeef5'
},
maxImageWidth: 900,
fontSize: '16px',
lineHeight: '1.75'
},
feishu: {
name: '飞书云文档',
imageMaxWidth: 1000,
imageQuality: 0.92,
displayMaxWidth: 800,
imageFormat: 'auto',
compressGif: false,
enableCodeHighlight: true,
enableThemeStyles: true,
enableProgressNotice: true,
description: '适合飞书文档,图片宽度1000px,质量0.85',
compatibilityNotes: [
'支持完整的样式和代码高亮',
'图片支持拖拽调整大小',
'推荐使用此预设'
],
styleStrategy: 'css',
styleMode: 'inline-preferred',
preserveThemeLevel: 'high',
supports: {
styleTag: true,
className: true,
table: 'good',
codeBlock: 'good',
math: 'medium'
},
transforms: {
flattenCallout: false,
simplifyTable: false,
simplifyNestedBlocks: false
},
previewFrame: {
background: '#eef2f7',
padding: '24px',
contentBackground: '#ffffff',
contentShadow: '0 10px 28px rgba(15,23,42,0.08)',
contentBorder: '1px solid #d8dee9'
},
maxImageWidth: 1000,
fontSize: '16px',
lineHeight: '1.6'
},
zhihu: {
name: '知乎',
imageMaxWidth: 800,
imageQuality: 0.9,
displayMaxWidth: 600,
imageFormat: 'auto',
compressGif: false,
enableCodeHighlight: true,
enableThemeStyles: true,
enableProgressNotice: true,
description: '适合知乎回答,图片宽度800px,质量0.8',
compatibilityNotes: [
'代码高亮支持良好',
'表格样式基本兼容',
'图片支持点击查看大图'
],
styleStrategy: 'css',
styleMode: 'inline-preferred',
preserveThemeLevel: 'medium',
supports: {
styleTag: true,
className: true,
table: 'partial',
codeBlock: 'partial',
math: 'weak'
},
transforms: {
flattenCallout: false,
simplifyTable: true,
simplifyNestedBlocks: true
},
previewFrame: {
background: '#f6f6f6',
padding: '28px 18px',
contentBackground: '#fffdf8',
contentShadow: '0 12px 30px rgba(120, 113, 108, 0.08)',
contentBorder: '1px solid #eee7da'
},
maxImageWidth: 800,
fontSize: '16px',
lineHeight: '1.6'
},
yuque: {
name: '语雀',
imageMaxWidth: 900,
imageQuality: 0.9,
displayMaxWidth: 700,
imageFormat: 'auto',
compressGif: false,
enableCodeHighlight: true,
enableThemeStyles: true,
enableProgressNotice: true,
description: '适合语雀文档,图片宽度900px,质量0.8',
compatibilityNotes: [
'样式支持完整',
'代码高亮效果佳',
'推荐使用'
],
styleStrategy: 'css',
styleMode: 'inline-preferred',
preserveThemeLevel: 'high',
supports: {
styleTag: true,
className: true,
table: 'good',
codeBlock: 'good',
math: 'medium'
},
transforms: {
flattenCallout: false,
simplifyTable: false,
simplifyNestedBlocks: false
},
previewFrame: {
background: '#f7f8fb',
padding: '24px 18px',
contentBackground: '#fcfcfd',
contentShadow: '0 10px 26px rgba(99,102,241,0.06)',
contentBorder: '1px solid #e6e8f0'
},
maxImageWidth: 900,
fontSize: '16px',
lineHeight: '1.6'
}
};
const PLATFORM_THEMES = {
official: {
name: '官方简洁',
fontFamily: "'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif",
textColor: '#222222',
mutedColor: '#57606a',
backgroundColor: '#ffffff',
accentColor: '#07c160',
linkColor: '#576b95',
blockquoteBorder: '#07c160',
blockquoteBackground: '#f6fffb',
codeBackground: '#f6f8fa',
codeColor: '#1f2328',
headingColor: '#111111',
borderColor: '#d0d7de',
borderRadius: '6px',
paragraphSpacing: '16px',
lineHeight: '1.75',
headingDivider: true,
tableHeaderFill: true,
imageRounded: true,
codeBlockBorder: true
},
minimal: {
name: '极简专业',
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif",
textColor: '#1f2328',
mutedColor: '#59636e',
backgroundColor: '#ffffff',
accentColor: '#2f80ed',
linkColor: '#0969da',
blockquoteBorder: '#2f80ed',
blockquoteBackground: '#f6f8fa',
codeBackground: '#f6f8fa',
codeColor: '#24292f',
headingColor: '#111827',
borderColor: '#d8dee4',
borderRadius: '8px',
paragraphSpacing: '16px',
lineHeight: '1.7',
headingDivider: true,
tableHeaderFill: true,
imageRounded: true,
codeBlockBorder: true
},
knowledge: {
name: '知识专栏',
fontFamily: "'Noto Serif SC', 'Source Han Serif SC', Georgia, serif",
textColor: '#2d2d2d',
mutedColor: '#666666',
backgroundColor: '#fffdf8',
accentColor: '#d97706',
linkColor: '#b45309',
blockquoteBorder: '#f59e0b',
blockquoteBackground: '#fff7ed',
codeBackground: '#f8fafc',
codeColor: '#1e293b',
headingColor: '#1f2937',
borderColor: '#e5dccf',
borderRadius: '8px',
paragraphSpacing: '18px',
lineHeight: '1.85',
headingDivider: false,
tableHeaderFill: true,
imageRounded: false,
codeBlockBorder: false
},
soft: {
name: '柔和文档',
fontFamily: "'Inter', 'PingFang SC', 'Microsoft YaHei', sans-serif",
textColor: '#24303f',
mutedColor: '#64748b',
backgroundColor: '#fcfcfd',
accentColor: '#7c3aed',
linkColor: '#6d28d9',
blockquoteBorder: '#8b5cf6',
blockquoteBackground: '#f5f3ff',
codeBackground: '#f8fafc',
codeColor: '#1e293b',
headingColor: '#0f172a',
borderColor: '#e2e8f0',
borderRadius: '10px',
paragraphSpacing: '16px',
lineHeight: '1.75',
headingDivider: true,
tableHeaderFill: true,
imageRounded: true,
codeBlockBorder: true
},
'wechat-business': {
name: '公众号商务风',
fontFamily: "'PingFang SC', 'Microsoft YaHei', sans-serif",
textColor: '#1f1f1f',
mutedColor: '#5f6368',
backgroundColor: '#ffffff',
accentColor: '#0f766e',
linkColor: '#0f766e',
blockquoteBorder: '#14b8a6',
blockquoteBackground: '#f0fdfa',
codeBackground: '#f8fafc',
codeColor: '#0f172a',
headingColor: '#111827',
borderColor: '#dbe4ea',
borderRadius: '4px',
paragraphSpacing: '18px',
lineHeight: '1.8',
headingDivider: true,
tableHeaderFill: false,
imageRounded: false,
codeBlockBorder: false
},
'wechat-minimal': {
name: '公众号极简风',
fontFamily: "'PingFang SC', 'Helvetica Neue', sans-serif",
textColor: '#262626',
mutedColor: '#737373',
backgroundColor: '#ffffff',
accentColor: '#3b82f6',
linkColor: '#2563eb',
blockquoteBorder: '#93c5fd',
blockquoteBackground: '#eff6ff',
codeBackground: '#f5f7fb',
codeColor: '#1f2937',
headingColor: '#111827',
borderColor: '#e5e7eb',
borderRadius: '2px',
paragraphSpacing: '16px',
lineHeight: '1.75',
headingDivider: false,
tableHeaderFill: false,
imageRounded: false,
codeBlockBorder: false
},
'zhihu-column': {
name: '知乎长文风',
fontFamily: "'Noto Serif SC', Georgia, serif",
textColor: '#2b2b2b',
mutedColor: '#646464',
backgroundColor: '#fffefb',
accentColor: '#2563eb',
linkColor: '#1d4ed8',
blockquoteBorder: '#60a5fa',
blockquoteBackground: '#f8fbff',
codeBackground: '#f8fafc',
codeColor: '#111827',
headingColor: '#1f2937',
borderColor: '#e6e8ec',
borderRadius: '6px',
paragraphSpacing: '20px',
lineHeight: '1.9',
headingDivider: false,
tableHeaderFill: true,
imageRounded: false,
codeBlockBorder: false
},
'feishu-team': {
name: '飞书团队文档风',
fontFamily: "Inter, 'PingFang SC', 'Segoe UI', sans-serif",
textColor: '#172033',
mutedColor: '#5b6475',
backgroundColor: '#ffffff',
accentColor: '#1456f0',
linkColor: '#1456f0',
blockquoteBorder: '#7aa2ff',
blockquoteBackground: '#f5f8ff',
codeBackground: '#f3f6fb',
codeColor: '#172033',
headingColor: '#111827',
borderColor: '#dbe2ea',
borderRadius: '8px',
paragraphSpacing: '16px',
lineHeight: '1.7',
headingDivider: true,
tableHeaderFill: true,
imageRounded: true,
codeBlockBorder: true
},
'yuque-notes': {
name: '语雀知识库风',
fontFamily: "'Inter', 'PingFang SC', sans-serif",
textColor: '#243042',
mutedColor: '#64748b',
backgroundColor: '#fcfcfe',
accentColor: '#7c3aed',
linkColor: '#6d28d9',
blockquoteBorder: '#a78bfa',
blockquoteBackground: '#f5f3ff',
codeBackground: '#f8fafc',
codeColor: '#0f172a',
headingColor: '#111827',
borderColor: '#e5e7ef',
borderRadius: '10px',
paragraphSpacing: '17px',
lineHeight: '1.78',
headingDivider: true,
tableHeaderFill: true,
imageRounded: true,
codeBlockBorder: true
}
};
const PLATFORM_THEME_RECOMMENDATIONS = {
wechat: ['wechat-business', 'official', 'wechat-minimal'],
feishu: ['feishu-team', 'minimal'],
zhihu: ['zhihu-column', 'knowledge'],
yuque: ['yuque-notes', 'soft']
};
const REMOVE_SELECTORS = [
'.collapse-indicator',
'.internal-query-header',
'.copy-code-button',
'.markdown-embed-link',
'.mod-header',
'.metadata-container',
'.metadata-properties',
'.inline-title',
'.cm-widgetBuffer',
'.markdown-reading-view .print',
'script',
'style'
];
const BASE_STYLE_PROPS = [
'color',
'background-color',
'font-size',
'font-family',
'font-weight',
'font-style',
'line-height',
'text-align',
'text-decoration',
'letter-spacing',
'margin-top',
'margin-bottom',
'margin-left',
'margin-right',
'padding-top',
'padding-bottom',
'padding-left',
'padding-right',
'border-top',
'border-right',
'border-bottom',
'border-left',
'border-radius',
'display',
'width',
'max-width',
'min-width',
'white-space',
'overflow-x',
'list-style-type'
];
const TAG_STYLE_PROPS = {
p: ['margin-top', 'margin-bottom', 'line-height', 'text-align'],
li: ['margin-top', 'margin-bottom', 'line-height'],
blockquote: ['color', 'background-color', 'border-left', 'padding-left', 'margin-top', 'margin-bottom'],
pre: ['background-color', 'color', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'border-radius', 'overflow-x', 'white-space'],
code: ['background-color', 'color', 'font-family', 'font-size', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'border-radius', 'white-space'],
table: ['width', 'border-collapse', 'margin-top', 'margin-bottom'],
th: ['border-top', 'border-right', 'border-bottom', 'border-left', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'background-color', 'text-align'],
td: ['border-top', 'border-right', 'border-bottom', 'border-left', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'background-color', 'text-align'],
img: ['display', 'width', 'max-width', 'height', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'border-radius'],
h1: ['color', 'font-size', 'font-weight', 'line-height', 'margin-top', 'margin-bottom'],
h2: ['color', 'font-size', 'font-weight', 'line-height', 'margin-top', 'margin-bottom'],
h3: ['color', 'font-size', 'font-weight', 'line-height', 'margin-top', 'margin-bottom'],
h4: ['color', 'font-size', 'font-weight', 'line-height', 'margin-top', 'margin-bottom'],
h5: ['color', 'font-size', 'font-weight', 'line-height', 'margin-top', 'margin-bottom'],
h6: ['color', 'font-size', 'font-weight', 'line-height', 'margin-top', 'margin-bottom']
};
/* ========== 应用平台预设 ========== */
function applyPlatformPreset(platformKey) {
const preset = PLATFORM_PRESETS[platformKey];
if (!preset) {
console.warn('[bulk-copy] 未找到平台预设:', platformKey);
return;
}
USER_CONFIG.currentPlatform = platformKey;
USER_CONFIG.imageMaxWidth = preset.imageMaxWidth;
USER_CONFIG.imageQuality = preset.imageQuality;
USER_CONFIG.displayMaxWidth = preset.displayMaxWidth;
USER_CONFIG.imageFormat = preset.imageFormat;
USER_CONFIG.compressGif = preset.compressGif;
USER_CONFIG.enableCodeHighlight = preset.enableCodeHighlight;
USER_CONFIG.enableThemeStyles = preset.enableThemeStyles;
USER_CONFIG.enableProgressNotice = preset.enableProgressNotice;
IMAGE_CONFIG.MAX_WIDTH = preset.maxImageWidth || preset.imageMaxWidth;
console.log('[bulk-copy] 已应用平台预设:', preset.name);
new Notice(`✅ 已切换到 ${preset.name} 预设`);
}
function syncImageConfigFromUserConfig() {
IMAGE_CONFIG.MAX_WIDTH = USER_CONFIG.imageMaxWidth;
IMAGE_CONFIG.QUALITY = USER_CONFIG.imageQuality;
IMAGE_CONFIG.DISPLAY_MAX_WIDTH = USER_CONFIG.displayMaxWidth;
IMAGE_CONFIG.FORMAT = USER_CONFIG.imageFormat;
IMAGE_CONFIG.COMPRESS_GIF = USER_CONFIG.compressGif;
}
function getCurrentImageStrategy(platformKey = USER_CONFIG.currentPlatform) {
return USER_CONFIG.platformImageStrategies?.[platformKey] || 'quality';
}
function getImageStrategyConfig(platformKey = USER_CONFIG.currentPlatform) {
switch (getCurrentImageStrategy(platformKey)) {
case 'size':
return {
keepPng: false,
smallImageThreshold: 150 * 1024,
qualityBoost: -0.12,
resizeThresholdBoost: -200
};
case 'balanced':
return {
keepPng: true,
smallImageThreshold: 280 * 1024,
qualityBoost: -0.04,
resizeThresholdBoost: 0
};
case 'quality':
default:
return {
keepPng: true,
smallImageThreshold: 500 * 1024,
qualityBoost: 0,
resizeThresholdBoost: 200
};
}
}
function getImageStrategyLabel(platformKey = USER_CONFIG.currentPlatform) {
switch (getCurrentImageStrategy(platformKey)) {
case 'size':
return '体积优先';
case 'balanced':
return '平衡';
case 'quality':
default:
return '高清优先';
}
}
function getCurrentPlatformTheme(platformKey = USER_CONFIG.currentPlatform) {
return USER_CONFIG.platformThemes?.[platformKey] || 'minimal';
}
function getAllThemeOptions() {
return {
...PLATFORM_THEMES,
...(USER_CONFIG.customThemes || {})
};
}
function getEditableCustomThemeKey() {
return USER_CONFIG.customThemeEditorKey || 'custom';
}
function getEditableCustomTheme() {
const customThemes = USER_CONFIG.customThemes || DEFAULT_USER_CONFIG.customThemes;
return customThemes[getEditableCustomThemeKey()] || customThemes.custom || DEFAULT_USER_CONFIG.customThemes.custom;
}
function getOrderedCustomThemeKeys() {
const customThemes = USER_CONFIG.customThemes || DEFAULT_USER_CONFIG.customThemes;
const allKeys = Object.keys(customThemes);
const configuredOrder = USER_CONFIG.customThemeOrder || DEFAULT_USER_CONFIG.customThemeOrder || [];
const ordered = configuredOrder.filter(key => allKeys.includes(key));
const missing = allKeys.filter(key => !ordered.includes(key));
return [...ordered, ...missing];
}
function getPlatformThemeConfig(platformKey = USER_CONFIG.currentPlatform) {
const allThemes = getAllThemeOptions();
const baseTheme = allThemes[getCurrentPlatformTheme(platformKey)] || allThemes.minimal || PLATFORM_THEMES.minimal;
const overrides = USER_CONFIG.platformThemeOverrides?.[platformKey] || {};
return {
...baseTheme,
...overrides
};
}
function getPlatformThemeLabel(platformKey = USER_CONFIG.currentPlatform) {
return getPlatformThemeConfig(platformKey).name;
}
function getRecommendedThemeKeys(platformKey) {
return PLATFORM_THEME_RECOMMENDATIONS[platformKey] || [];
}
function isRecommendedTheme(platformKey, themeKey) {
return getRecommendedThemeKeys(platformKey).includes(themeKey);
}
function getRecommendedThemeLabels(platformKey) {
return getRecommendedThemeKeys(platformKey)
.map(themeKey => getAllThemeOptions()[themeKey]?.name)
.filter(Boolean);
}
function updatePlatformThemeOverride(platformKey, key, value) {
USER_CONFIG.platformThemeOverrides = {
...DEFAULT_USER_CONFIG.platformThemeOverrides,
...(USER_CONFIG.platformThemeOverrides || {}),
[platformKey]: {
...(USER_CONFIG.platformThemeOverrides?.[platformKey] || {}),
[key]: value
}
};
}
function resetPlatformToRecommendedTheme(platformKey) {
const recommendedThemeKey = getRecommendedThemeKeys(platformKey)[0] || DEFAULT_USER_CONFIG.platformThemes[platformKey] || 'official';
USER_CONFIG.platformThemes = {
...DEFAULT_USER_CONFIG.platformThemes,
...(USER_CONFIG.platformThemes || {}),
[platformKey]: recommendedThemeKey
};
USER_CONFIG.platformThemeOverrides = {
...DEFAULT_USER_CONFIG.platformThemeOverrides,
...(USER_CONFIG.platformThemeOverrides || {}),
[platformKey]: {}
};
}
function validateSingleThemeImport(parsed) {
if (!parsed || typeof parsed !== 'object') {
return 'JSON 顶层必须是对象';
}
if (!parsed.theme || typeof parsed.theme !== 'object') {
return '缺少 theme 对象';
}
if (parsed.key && typeof parsed.key !== 'string') {
return 'key 必须是字符串';
}
return null;
}
function validateThemeCollectionImport(parsed) {
if (!parsed || typeof parsed !== 'object') {
return 'JSON 顶层必须是对象';
}
if (!parsed.customThemes || typeof parsed.customThemes !== 'object') {
return '缺少 customThemes 对象';
}
if (parsed.customThemeOrder && !Array.isArray(parsed.customThemeOrder)) {
return 'customThemeOrder 必须是数组';
}
if (parsed.customThemeEditorKey && typeof parsed.customThemeEditorKey !== 'string') {
return 'customThemeEditorKey 必须是字符串';
}
return null;
}
function getThemeDetailDiffs(currentTheme, compareTheme) {
const items = [
['headingDivider', '标题分隔线'],
['tableHeaderFill', '表头底色'],
['imageRounded', '图片圆角'],
['codeBlockBorder', '代码块边框']
];
return items
.filter(([key]) => !!currentTheme && !!compareTheme && currentTheme[key] !== compareTheme[key])
.map(([key, label]) => `${label}: 当前${currentTheme[key] ? '开启' : '关闭'} / 推荐${compareTheme[key] ? '开启' : '关闭'}`);
}
function getThemeColorDiffs(currentTheme, compareTheme) {
const items = [
['accentColor', '强调色'],
['backgroundColor', '背景色'],
['codeBackground', '代码背景'],
['headingColor', '标题颜色']
];
return items
.filter(([key]) => !!currentTheme && !!compareTheme && currentTheme[key] !== compareTheme[key])
.map(([key, label]) => `${label}: 当前 ${currentTheme[key] || '-'} / 推荐 ${compareTheme[key] || '-'}`);
}
function applyCurrentPlatformConfig() {
const preset = PLATFORM_PRESETS[USER_CONFIG.currentPlatform];
if (!preset) {
syncImageConfigFromUserConfig();
return;
}
USER_CONFIG.platformImageStrategies = {
...DEFAULT_USER_CONFIG.platformImageStrategies,
...(USER_CONFIG.platformImageStrategies || {})
};
USER_CONFIG = {
...USER_CONFIG,
imageMaxWidth: USER_CONFIG.imageMaxWidth ?? preset.imageMaxWidth,
imageQuality: USER_CONFIG.imageQuality ?? preset.imageQuality,
displayMaxWidth: USER_CONFIG.displayMaxWidth ?? preset.displayMaxWidth,
imageFormat: USER_CONFIG.imageFormat || preset.imageFormat,
compressGif: typeof USER_CONFIG.compressGif === 'boolean' ? USER_CONFIG.compressGif : preset.compressGif,
enableCodeHighlight: typeof USER_CONFIG.enableCodeHighlight === 'boolean' ? USER_CONFIG.enableCodeHighlight : preset.enableCodeHighlight,
enableThemeStyles: typeof USER_CONFIG.enableThemeStyles === 'boolean' ? USER_CONFIG.enableThemeStyles : preset.enableThemeStyles,
enableProgressNotice: typeof USER_CONFIG.enableProgressNotice === 'boolean' ? USER_CONFIG.enableProgressNotice : preset.enableProgressNotice
};
syncImageConfigFromUserConfig();
}
/* ========== 安全大图转 base64 ========== */
async function buf2base64(buf) {
return new Promise((resolve, reject) => {
try {
const bytes = new Uint8Array(buf);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
resolve(btoa(binary));
} catch (e) {
reject(e);
}
});
}
/* ========== 图片压缩和尺寸限制 ========== */
async function compressImage(buf, ext, maxWidth = IMAGE_CONFIG.MAX_WIDTH, quality = IMAGE_CONFIG.QUALITY) {
return new Promise((resolve) => {
try {
const normalizedExt = (ext || '').toLowerCase();
const mime = getMime(normalizedExt);
const blob = new Blob([buf], { type: mime });
const img = new Image();
const url = URL.createObjectURL(blob);
const timeout = setTimeout(() => {
URL.revokeObjectURL(url);
resolve({ data: buf, needsConversion: false });
}, 30000);
img.onload = () => {
clearTimeout(timeout);
URL.revokeObjectURL(url);
try {
let width = img.width;
let height = img.height;
if (width > maxWidth) {
height = Math.round((height * maxWidth) / width);
width = maxWidth;
}
if (width === img.width) {
resolve({ data: buf, needsConversion: false, wasResized: false, mime });
return;
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.drawImage(img, 0, 0, width, height);
const preservePng = normalizedExt === 'png' && IMAGE_CONFIG.FORMAT === 'auto';
const outputMime = preservePng ? 'image/png' : 'image/jpeg';
const outputQuality = outputMime === 'image/png' ? undefined : quality;
canvas.toBlob(
(compressedBlob) => {
if (!compressedBlob) {
resolve({ data: buf, needsConversion: false, wasResized: false, mime });
return;
}
const reader = new FileReader();
reader.onload = () => {
resolve({
data: reader.result,
needsConversion: true,
mime: outputMime,
wasResized: true
});
};
reader.onerror = () => resolve({ data: buf, needsConversion: false, wasResized: false, mime });
reader.readAsArrayBuffer(compressedBlob);
},
outputMime,
outputQuality
);
} else {
resolve({ data: buf, needsConversion: false, wasResized: false, mime });
}
} catch (e) {
resolve({ data: buf, needsConversion: false, wasResized: false, mime });
}
};
img.onerror = () => {
clearTimeout(timeout);
URL.revokeObjectURL(url);
resolve({ data: buf, needsConversion: false, wasResized: false, mime });
};
img.src = url;
} catch (err) {
resolve({ data: buf, needsConversion: false, wasResized: false, mime: getMime(ext || '') });
}
});
}
/* ========== mime 映射 ========== */
const getMime = ext => ({
png: 'image/png',
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
gif: 'image/gif',
svg: 'image/svg+xml',
webp: 'image/webp',
bmp: 'image/bmp'
}[ext.toLowerCase()] || 'application/octet-stream');
function shouldKeepOriginalImage(ext, mime, buf, maxWidth, platformKey = USER_CONFIG.currentPlatform) {
const normalizedExt = (ext || '').toLowerCase();
const strategy = getImageStrategyConfig(platformKey);
if (!mime.startsWith('image/')) {
return true;
}
if (normalizedExt === 'png' && IMAGE_CONFIG.FORMAT === 'auto' && strategy.keepPng) {
return true;
}
if ((normalizedExt === 'jpg' || normalizedExt === 'jpeg' || normalizedExt === 'webp') && buf && buf.byteLength < strategy.smallImageThreshold) {
return true;
}
if (maxWidth >= 1200 + strategy.resizeThresholdBoost) {
return true;
}
return false;
}
/* ========== 单张图内嵌 ========== */
async function inlineImage(imgName, alt, context) {
const { plugin, baseFolder, assetsFolder } = context;
const candidates = [
`${assetsFolder}/${imgName}`,
`${baseFolder}/${imgName}`,
imgName.startsWith('/') ? imgName.slice(1) : imgName
];
let imgFile = candidates
.map(p => {
try {
return plugin.app.vault.getAbstractFileByPath(p);
} catch (e) {
return null;
}
})
.find(f => f instanceof TFile);
if (!imgFile) {
try {
const all = plugin.app.vault.getFiles();
imgFile = all.find(f => f.name === imgName);
} catch (e) {
// ignore
}
}
if (!imgFile) {
console.warn('[bulk-copy] 未找到图片文件', imgName);
return ``;
}
try {
const buf = await plugin.app.vault.readBinary(imgFile);
const ext = imgFile.extension;
const mime = getMime(ext);
if (ext && ext.toLowerCase() === 'svg') {
try {
const svgContent = await plugin.app.vault.read(imgFile);
const base64Svg = btoa(unescape(encodeURIComponent(svgContent)));
const currentPreset = PLATFORM_PRESETS[USER_CONFIG.currentPlatform];
const maxWidth = currentPreset?.maxImageWidth || IMAGE_CONFIG.DISPLAY_MAX_WIDTH;
return ``;
} catch (e) {
return ``;
}
}
if (ext && ext.toLowerCase() === 'gif') {
try {
const data = await buf2base64(buf);
const currentPreset = PLATFORM_PRESETS[USER_CONFIG.currentPlatform];
const maxWidth = currentPreset?.maxImageWidth || IMAGE_CONFIG.DISPLAY_MAX_WIDTH;
return `
`;
} catch (e) {
return ``;
}
}
const currentPlatform = USER_CONFIG.currentPlatform;
const currentPreset = PLATFORM_PRESETS[currentPlatform];
const maxWidth = currentPreset?.maxImageWidth || IMAGE_CONFIG.MAX_WIDTH;
const strategy = getImageStrategyConfig(currentPlatform);
const baseQuality = currentPreset?.imageQuality || IMAGE_CONFIG.QUALITY;
const quality = Math.max(0.55, Math.min(0.98, baseQuality + strategy.qualityBoost));
const shouldKeepOriginal = shouldKeepOriginalImage(ext, mime, buf, maxWidth, currentPlatform);
const compressed = shouldKeepOriginal
? { data: buf, needsConversion: false, wasResized: false, mime }
: await compressImage(buf, ext, maxWidth, quality);
let finalMime = mime;
let finalData;
if (compressed && compressed.needsConversion) {
finalMime = compressed.mime;
finalData = await buf2base64(compressed.data);
} else if (compressed && compressed.data) {
finalData = await buf2base64(compressed.data);
} else {
finalData = await buf2base64(buf);
}
if (!finalData) {
return ``;
}
const displayWidth = currentPreset?.displayMaxWidth || IMAGE_CONFIG.DISPLAY_MAX_WIDTH;
const imgStyle = `max-width: ${typeof displayWidth === 'number' ? `${displayWidth}px` : displayWidth}; width: 100%; height: auto; display: block; margin: 15px auto; border-radius: 4px;`;
const altText = alt ? alt.replace(/"/g, '"') : '';
return `
`;
} catch (e) {
console.error('[bulk-copy] 图片处理失败:', imgName, e);
return ``;
}
}
/* ========== Markdown 转 HTML(简化版) ========== */
function markdownToHtml(md) {
if (!md) return '';
let html = md;
// 转义 HTML 特殊字符
const escapeHtml = (text) => {
if (typeof text !== 'string') return '';
return text
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"');
};
// 代码块(先处理,避免内部语法被转换)
const codeBlocks = [];
html = html.replace(/```[\s\S]*?```/g, (match) => {
const id = codeBlocks.length;
const code = match.replace(/```/g, '').trim();
codeBlocks.push(`
${escapeHtml(code)}`);
return `\n\n`;
});
// 行内代码
const inlineCodes = [];
html = html.replace(/`([^`\n]+)`/g, (match, code) => {
const id = inlineCodes.length;
inlineCodes.push(`${escapeHtml(code)}`);
return ``;
});
// Wiki 链接 [[link]]
html = html.replace(/\[\[([^\]|]+?)(?:\|([^\]]+))?\]\]/g, (match, link, text) => {
const display = text || link;
return `${display}`;
});
// 标签 #tag
html = html.replace(/(? {
return `#${tag}`;
});
// 图片
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => {
return `$1'); // 分割线 html = html.replace(/^(---|\*\*\*|___)$/gm, '
这是一段用于预览导出效果的正文内容,展示当前平台主题的字体、颜色、间距和整体观感,并包含 行内代码 与 链接样式。
下面的区块用于观察更复杂的富文本结构在当前主题下的表现。
这是一段引用示例,用于观察边框、背景和文本色。
inline codeconst message = 'theme preview';
| 字段 | 说明 |
|---|---|
| 主题 | ${theme.name} |
| 平台 | ${PLATFORM_PRESETS[platformKey].name} |
图片说明文字:用于观察图片上下间距、圆角和说明段落是否协调。
`; applyThemeToRoot(preview, theme); } addThemeOptions(dropdown, platformKey) { const allThemes = getAllThemeOptions(); const builtInKeys = Object.keys(PLATFORM_THEMES); const customKeys = getOrderedCustomThemeKeys(); [...builtInKeys, ...customKeys].forEach(themeKey => { const label = isRecommendedTheme(platformKey, themeKey) ? `${allThemes[themeKey].name} (推荐)` : allThemes[themeKey].name; dropdown.addOption(themeKey, label); }); } renderCustomThemeEditor(containerEl, options = {}) { const editorKey = getEditableCustomThemeKey(); const customThemes = USER_CONFIG.customThemes || DEFAULT_USER_CONFIG.customThemes; const customTheme = getEditableCustomTheme(); const searchQuery = String(USER_CONFIG.customThemeSearchQuery || '').trim().toLowerCase(); const filteredThemeKeys = getOrderedCustomThemeKeys().filter(themeKey => { const name = String(customThemes[themeKey]?.name || themeKey).toLowerCase(); return !searchQuery || name.includes(searchQuery) || themeKey.toLowerCase().includes(searchQuery); }); const visibleTabs = options.visibleTabs || ['basic', 'color', 'manage']; const headerBox = containerEl.createDiv({ cls: 'bulk-copy-settings-section' }); headerBox.createEl('h3', { text: customTheme.name || '我的主题', cls: 'bulk-copy-settings-section-title' }); headerBox.createEl('p', { text: `主题 Key: ${editorKey}`, cls: 'bulk-copy-subtle-note' }); headerBox.createEl('p', { text: '可以维护多个自定义主题模板,并分配给任意平台。建议先调整字体、标题颜色和背景色,再微调代码背景与圆角。', cls: 'setting-item-description' }); if (visibleTabs.includes('preview')) { const previewSection = containerEl.createDiv({ cls: 'bulk-copy-settings-section' }); previewSection.createEl('h3', { text: '主题预览', cls: 'bulk-copy-settings-section-title' }); previewSection.createEl('p', { text: '当前主题可在不同平台容器下进行预览,不会影响全局默认平台。', cls: 'bulk-copy-subtle-note' }); new Setting(previewSection) .setName('预览平台容器') .setDesc('仅用于主题管理器中的预览切换') .addDropdown(dropdown => { Object.keys(PLATFORM_PRESETS).forEach(platformKey => { dropdown.addOption(platformKey, PLATFORM_PRESETS[platformKey].name); }); dropdown.setValue(USER_CONFIG.themePreviewPlatform || 'wechat'); dropdown.onChange(async value => { USER_CONFIG.themePreviewPlatform = value; await this.plugin.saveUserConfig(); this.display(); }); }); const previewTheme = getPlatformThemeConfig(USER_CONFIG.themePreviewPlatform || 'wechat'); const statusGrid = previewSection.createDiv({ cls: 'bulk-copy-settings-grid' }); [ ['标题分隔线', previewTheme.headingDivider ? '开启' : '关闭'], ['表头底色', previewTheme.tableHeaderFill ? '开启' : '关闭'], ['图片圆角', previewTheme.imageRounded ? '开启' : '关闭'], ['代码块边框', previewTheme.codeBlockBorder ? '开启' : '关闭'] ].forEach(([label, value]) => { const stat = statusGrid.createDiv({ cls: 'bulk-copy-settings-stat' }); stat.createDiv({ text: label, cls: 'bulk-copy-settings-stat-label' }); stat.createDiv({ text: value, cls: 'bulk-copy-settings-stat-value' }); }); const compareGrid = previewSection.createDiv({ cls: 'bulk-copy-preview-compare-grid' }); const currentCard = compareGrid.createDiv({ cls: 'bulk-copy-preview-compare-card' }); currentCard.createEl('h4', { text: '当前主题' }); currentCard.createEl('p', { text: customTheme.name || '我的主题', cls: 'bulk-copy-preview-compare-note' }); this.renderThemePreview(currentCard, USER_CONFIG.themePreviewPlatform || 'wechat'); const recommendedKey = getRecommendedThemeKeys(USER_CONFIG.themePreviewPlatform || 'wechat')[0]; if (recommendedKey) { const recommendedTheme = getAllThemeOptions()[recommendedKey]; const diffs = getThemeDetailDiffs(customTheme, recommendedTheme); const colorDiffs = getThemeColorDiffs(customTheme, recommendedTheme); if (diffs.length > 0) { const diffSection = previewSection.createDiv({ cls: 'bulk-copy-settings-section' }); diffSection.createEl('h3', { text: '差异提示', cls: 'bulk-copy-settings-section-title' }); diffs.forEach(text => diffSection.createEl('p', { text, cls: 'bulk-copy-subtle-note' })); } if (colorDiffs.length > 0) { const colorDiffSection = previewSection.createDiv({ cls: 'bulk-copy-settings-section' }); colorDiffSection.createEl('h3', { text: '颜色差异', cls: 'bulk-copy-settings-section-title' }); colorDiffs.forEach(text => colorDiffSection.createEl('p', { text, cls: 'bulk-copy-subtle-note' })); } const compareSection = compareGrid.createDiv({ cls: 'bulk-copy-preview-compare-card' }); compareSection.createEl('h4', { text: '推荐主题' }); compareSection.createEl('p', { text: recommendedTheme?.name || recommendedKey, cls: 'bulk-copy-preview-compare-note' }); const actionRow = compareSection.createDiv(); actionRow.style.marginBottom = '10px'; const applyButton = actionRow.createEl('button', { text: '应用到当前预览平台' }); applyButton.addEventListener('click', async () => { const previewPlatform = USER_CONFIG.themePreviewPlatform || 'wechat'; USER_CONFIG.lastAppliedRecommendedTheme = { platformKey: previewPlatform, previousThemeKey: USER_CONFIG.platformThemes?.[previewPlatform], previousOverrides: USER_CONFIG.platformThemeOverrides?.[previewPlatform] || {} }; USER_CONFIG.platformThemes = { ...DEFAULT_USER_CONFIG.platformThemes, ...(USER_CONFIG.platformThemes || {}), [previewPlatform]: recommendedKey }; USER_CONFIG.platformThemeOverrides = { ...DEFAULT_USER_CONFIG.platformThemeOverrides, ...(USER_CONFIG.platformThemeOverrides || {}), [previewPlatform]: {} }; await this.plugin.saveUserConfig(); new Notice(`✅ 已将推荐主题应用到 ${PLATFORM_PRESETS[previewPlatform]?.name || previewPlatform}`); this.display(); }); if (USER_CONFIG.lastAppliedRecommendedTheme && USER_CONFIG.lastAppliedRecommendedTheme.platformKey === (USER_CONFIG.themePreviewPlatform || 'wechat')) { const undoButton = actionRow.createEl('button', { text: '撤销刚才应用' }); undoButton.style.marginLeft = '8px'; undoButton.addEventListener('click', async () => { const snapshot = USER_CONFIG.lastAppliedRecommendedTheme; USER_CONFIG.platformThemes = { ...DEFAULT_USER_CONFIG.platformThemes, ...(USER_CONFIG.platformThemes || {}), [snapshot.platformKey]: snapshot.previousThemeKey || DEFAULT_USER_CONFIG.platformThemes[snapshot.platformKey] }; USER_CONFIG.platformThemeOverrides = { ...DEFAULT_USER_CONFIG.platformThemeOverrides, ...(USER_CONFIG.platformThemeOverrides || {}), [snapshot.platformKey]: snapshot.previousOverrides || {} }; USER_CONFIG.lastAppliedRecommendedTheme = null; await this.plugin.saveUserConfig(); new Notice(`✅ 已撤销 ${PLATFORM_PRESETS[snapshot.platformKey]?.name || snapshot.platformKey} 的推荐主题应用`); this.display(); }); } const previewThemeKeyBackup = USER_CONFIG.platformThemes?.[USER_CONFIG.themePreviewPlatform || 'wechat']; USER_CONFIG.platformThemes = { ...USER_CONFIG.platformThemes, [USER_CONFIG.themePreviewPlatform || 'wechat']: recommendedKey }; this.renderThemePreview(compareSection, USER_CONFIG.themePreviewPlatform || 'wechat'); USER_CONFIG.platformThemes = { ...USER_CONFIG.platformThemes, [USER_CONFIG.themePreviewPlatform || 'wechat']: previewThemeKeyBackup }; } return; } if (visibleTabs.includes('details')) { const detailSection = containerEl.createDiv({ cls: 'bulk-copy-settings-section' }); detailSection.createEl('h3', { text: '主题细节', cls: 'bulk-copy-settings-section-title' }); detailSection.createEl('p', { text: '这些开关属于当前自定义主题本身,会影响不同平台绑定该主题后的基础表现。', cls: 'bulk-copy-subtle-note' }); new Setting(detailSection) .setName('标题分隔线') .setDesc('控制 H1/H2 是否显示下边线') .addToggle(toggle => { toggle.setValue(!!customTheme.headingDivider); toggle.onChange(async value => { await updateCustomThemeField('headingDivider', value); this.display(); }); }); new Setting(detailSection) .setName('表头底色') .setDesc('控制表格表头是否显示底色') .addToggle(toggle => { toggle.setValue(!!customTheme.tableHeaderFill); toggle.onChange(async value => { await updateCustomThemeField('tableHeaderFill', value); this.display(); }); }); new Setting(detailSection) .setName('图片圆角') .setDesc('控制图片是否保留圆角') .addToggle(toggle => { toggle.setValue(!!customTheme.imageRounded); toggle.onChange(async value => { await updateCustomThemeField('imageRounded', value); this.display(); }); }); new Setting(detailSection) .setName('代码块边框') .setDesc('控制代码块是否显示边框') .addToggle(toggle => { toggle.setValue(!!customTheme.codeBlockBorder); toggle.onChange(async value => { await updateCustomThemeField('codeBlockBorder', value); this.display(); }); }); return; } const searchSection = containerEl.createDiv({ cls: 'bulk-copy-settings-section' }); searchSection.createEl('h3', { text: '搜索与切换', cls: 'bulk-copy-settings-section-title' }); if (visibleTabs.includes('manage')) { new Setting(searchSection) .setName('主题搜索') .setDesc('按名称快速筛选自定义主题模板') .addText(text => { text.setPlaceholder('输入主题名称'); text.setValue(String(USER_CONFIG.customThemeSearchQuery || '')); text.onChange(async value => { USER_CONFIG.customThemeSearchQuery = value; await this.plugin.saveUserConfig(); this.display(); }); }); } const updateCustomThemeField = async (key, value) => { USER_CONFIG.customThemes = { ...DEFAULT_USER_CONFIG.customThemes, ...(USER_CONFIG.customThemes || {}), [editorKey]: { ...DEFAULT_USER_CONFIG.customThemes.custom, ...(USER_CONFIG.customThemes?.[editorKey] || {}), [key]: value } }; await this.plugin.saveUserConfig(); }; if (visibleTabs.includes('manage')) { new Setting(searchSection) .setName('当前编辑主题') .setDesc('选择一个自定义主题进行编辑') .addDropdown(dropdown => { filteredThemeKeys.forEach(themeKey => { dropdown.addOption(themeKey, customThemes[themeKey].name || themeKey); }); dropdown.setValue(filteredThemeKeys.includes(editorKey) ? editorKey : (filteredThemeKeys[0] || editorKey)); dropdown.onChange(async value => { USER_CONFIG.customThemeEditorKey = value; await this.plugin.saveUserConfig(); this.display(); }); }); } const manageSection = containerEl.createDiv({ cls: 'bulk-copy-settings-section' }); manageSection.createEl('h3', { text: '模板管理', cls: 'bulk-copy-settings-section-title' }); if (visibleTabs.includes('manage')) { new Setting(manageSection) .setName('主题模板管理') .setDesc('新增、复制、排序、导出或导入自定义主题模板') .addButton(button => { button.setButtonText('新增主题'); button.onClick(async () => { const newKey = `custom-${Date.now()}`; USER_CONFIG.customThemes = { ...DEFAULT_USER_CONFIG.customThemes, ...(USER_CONFIG.customThemes || {}), [newKey]: { ...DEFAULT_USER_CONFIG.customThemes.custom, name: `我的主题 ${Object.keys(USER_CONFIG.customThemes || {}).length + 1}` } }; USER_CONFIG.customThemeOrder = [...getOrderedCustomThemeKeys(), newKey]; USER_CONFIG.customThemeEditorKey = newKey; await this.plugin.saveUserConfig(); this.display(); }); }) .addButton(button => { button.setButtonText('复制当前主题'); button.onClick(async () => { const newKey = `custom-${Date.now()}`; USER_CONFIG.customThemes = { ...DEFAULT_USER_CONFIG.customThemes, ...(USER_CONFIG.customThemes || {}), [newKey]: { ...customTheme, name: `${customTheme.name || '我的主题'} 副本` } }; USER_CONFIG.customThemeOrder = [...getOrderedCustomThemeKeys(), newKey]; USER_CONFIG.customThemeEditorKey = newKey; await this.plugin.saveUserConfig(); this.display(); }); }) .addButton(button => { button.setButtonText('上移'); button.onClick(async () => { const order = [...getOrderedCustomThemeKeys()]; const index = order.indexOf(editorKey); if (index > 0) { [order[index - 1], order[index]] = [order[index], order[index - 1]]; USER_CONFIG.customThemeOrder = order; await this.plugin.saveUserConfig(); this.display(); } }); }) .addButton(button => { button.setButtonText('下移'); button.onClick(async () => { const order = [...getOrderedCustomThemeKeys()]; const index = order.indexOf(editorKey); if (index >= 0 && index < order.length - 1) { [order[index + 1], order[index]] = [order[index], order[index + 1]]; USER_CONFIG.customThemeOrder = order; await this.plugin.saveUserConfig(); this.display(); } }); }) .addButton(button => { button.setButtonText('导出主题'); button.onClick(() => { const exportPayload = JSON.stringify({ key: editorKey, theme: customTheme }, null, 2); new ThemeJsonModal(this.app, { title: '导出当前主题', description: '复制下面的 JSON,可在其他环境中导入该主题。', initialValue: exportPayload, readOnly: true }).open(); }); }) .addButton(button => { button.setButtonText('导出全部'); button.onClick(() => { const exportPayload = JSON.stringify({ customThemes: USER_CONFIG.customThemes || DEFAULT_USER_CONFIG.customThemes, customThemeOrder: getOrderedCustomThemeKeys(), customThemeEditorKey: getEditableCustomThemeKey() }, null, 2); new ThemeJsonModal(this.app, { title: '导出全部自定义主题', description: '复制下面的 JSON,可完整备份当前的自定义主题库。', initialValue: exportPayload, readOnly: true }).open(); }); }) .addButton(button => { button.setButtonText('导入主题'); button.onClick(() => { new ThemeJsonModal(this.app, { title: '导入当前主题', description: '粘贴单个主题 JSON 后确认导入。', initialValue: '', readOnly: false, onConfirm: async (raw) => { try { const parsed = JSON.parse(raw); const validationError = validateSingleThemeImport(parsed); if (validationError) { new Notice(`❌ 主题导入失败:${validationError}`); return; } const importedKey = parsed.key || `custom-${Date.now()}`; const importedTheme = { ...DEFAULT_USER_CONFIG.customThemes.custom, ...(parsed.theme || {}) }; USER_CONFIG.customThemes = { ...DEFAULT_USER_CONFIG.customThemes, ...(USER_CONFIG.customThemes || {}), [importedKey]: importedTheme }; USER_CONFIG.customThemeOrder = [...getOrderedCustomThemeKeys().filter(key => key !== importedKey), importedKey]; USER_CONFIG.customThemeEditorKey = importedKey; await this.plugin.saveUserConfig(); this.display(); new Notice('✅ 主题导入成功'); } catch (error) { new Notice('❌ 主题导入失败:JSON 格式无效'); } } }).open(); }); }) .addButton(button => { button.setButtonText('导入全部'); button.onClick(() => { new ThemeJsonModal(this.app, { title: '导入全部自定义主题', description: '粘贴完整主题集合 JSON 后确认导入。', initialValue: '', readOnly: false, onConfirm: async (raw) => { try { const parsed = JSON.parse(raw); const validationError = validateThemeCollectionImport(parsed); if (validationError) { new Notice(`❌ 全部主题导入失败:${validationError}`); return; } const importedThemes = parsed.customThemes || {}; const importedOrder = Array.isArray(parsed.customThemeOrder) ? parsed.customThemeOrder : Object.keys(importedThemes); const importedEditorKey = parsed.customThemeEditorKey || importedOrder[0] || 'custom'; USER_CONFIG.customThemes = Object.keys(importedThemes).length > 0 ? importedThemes : { ...DEFAULT_USER_CONFIG.customThemes }; USER_CONFIG.customThemeOrder = importedOrder.length > 0 ? importedOrder : [...DEFAULT_USER_CONFIG.customThemeOrder]; USER_CONFIG.customThemeEditorKey = importedEditorKey; await this.plugin.saveUserConfig(); this.display(); new Notice('✅ 全部自定义主题导入成功'); } catch (error) { new Notice('❌ 全部主题导入失败:JSON 格式无效'); } } }).open(); }); }) .addButton(button => { button.setButtonText('删除当前主题'); button.setWarning(); button.setDisabled(editorKey === 'custom' && Object.keys(customThemes).length <= 1); button.onClick(async () => { const themeEntries = { ...(USER_CONFIG.customThemes || {}) }; delete themeEntries[editorKey]; const boundPlatforms = Object.keys(USER_CONFIG.platformThemes || {}).filter(platformKey => USER_CONFIG.platformThemes[platformKey] === editorKey); if (boundPlatforms.length > 0) { const confirmed = window.confirm(`当前主题正被以下平台使用:${boundPlatforms.map(platformKey => PLATFORM_PRESETS[platformKey]?.name || platformKey).join(' / ')}。删除后这些平台将恢复到默认推荐主题,是否继续?`); if (!confirmed) { return; } } Object.keys(USER_CONFIG.platformThemes || {}).forEach(platformKey => { if (USER_CONFIG.platformThemes[platformKey] === editorKey) { USER_CONFIG.platformThemes[platformKey] = DEFAULT_USER_CONFIG.platformThemes[platformKey] || 'official'; } }); USER_CONFIG.customThemes = Object.keys(themeEntries).length > 0 ? themeEntries : { ...DEFAULT_USER_CONFIG.customThemes }; USER_CONFIG.customThemeOrder = getOrderedCustomThemeKeys().filter(key => key !== editorKey); if (USER_CONFIG.customThemeOrder.length === 0) { USER_CONFIG.customThemeOrder = [...DEFAULT_USER_CONFIG.customThemeOrder]; } USER_CONFIG.customThemeEditorKey = Object.keys(USER_CONFIG.customThemes)[0]; await this.plugin.saveUserConfig(); this.display(); }); }); } const isHexColor = (value) => /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(String(value || '').trim()); const fontPresets = { system: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif", pingfang: "'PingFang SC', 'Microsoft YaHei', sans-serif", serif: "'Noto Serif SC', Georgia, serif", inter: "Inter, 'PingFang SC', 'Segoe UI', sans-serif" }; const basicSection = containerEl.createDiv({ cls: 'bulk-copy-settings-section' }); basicSection.createEl('h3', { text: '基础样式', cls: 'bulk-copy-settings-section-title' }); if (visibleTabs.includes('basic')) { new Setting(basicSection) .setName('主题名称') .setDesc('显示在平台主题下拉中的名称') .addText(text => { text.setValue(String(customTheme.name || '我的主题')); text.onChange(async value => { await updateCustomThemeField('name', value || '我的主题'); }); }); new Setting(basicSection) .setName('字体预设') .setDesc('选择常用字体组合,可继续在下方自定义字体文本') .addDropdown(dropdown => { dropdown.addOption('system', '系统无衬线'); dropdown.addOption('pingfang', '苹方/雅黑'); dropdown.addOption('serif', '中文衬线'); dropdown.addOption('inter', 'Inter 文档风'); const matchedKey = Object.keys(fontPresets).find(key => fontPresets[key] === customTheme.fontFamily) || 'pingfang'; dropdown.setValue(matchedKey); dropdown.onChange(async value => { await updateCustomThemeField('fontFamily', fontPresets[value] || fontPresets.pingfang); this.display(); }); }); new Setting(basicSection) .setName('字体族') .setDesc('支持直接输入完整 font-family 字符串') .addText(text => { text.setValue(String(customTheme.fontFamily || '')); text.onChange(async value => { await updateCustomThemeField('fontFamily', value); }); }); new Setting(basicSection) .setName('圆角') .setDesc('例如 0px、6px、8px、12px') .addDropdown(dropdown => { ['0px', '4px', '6px', '8px', '10px', '12px', '16px'].forEach(value => { dropdown.addOption(value, value); }); dropdown.setValue(String(customTheme.borderRadius || '8px')); dropdown.onChange(async value => { await updateCustomThemeField('borderRadius', value); }); }) .addText(text => { text.setPlaceholder('8px'); text.setValue(String(customTheme.borderRadius || '')); text.onChange(async value => { await updateCustomThemeField('borderRadius', value); }); }); new Setting(basicSection) .setName('行高') .setDesc('推荐 1.6 - 1.9') .addDropdown(dropdown => { ['1.6', '1.7', '1.75', '1.8', '1.85', '1.9'].forEach(value => { dropdown.addOption(value, value); }); dropdown.setValue(String(customTheme.lineHeight || '1.75')); dropdown.onChange(async value => { await updateCustomThemeField('lineHeight', value); }); }) .addText(text => { text.setPlaceholder('1.75'); text.setValue(String(customTheme.lineHeight || '')); text.onChange(async value => { await updateCustomThemeField('lineHeight', value); }); }); } const colorSection = containerEl.createDiv({ cls: 'bulk-copy-settings-section' }); colorSection.createEl('h3', { text: '颜色设置', cls: 'bulk-copy-settings-section-title' }); colorSection.createEl('p', { text: '支持点击色块选择颜色,也可以直接输入十六进制色值。', cls: 'bulk-copy-subtle-note' }); if (visibleTabs.includes('color')) { [ ['textColor', '正文颜色'], ['headingColor', '标题颜色'], ['backgroundColor', '背景颜色'], ['linkColor', '链接颜色'], ['blockquoteBackground', '引用背景'], ['codeBackground', '代码背景'], ['borderColor', '边框颜色'] ].forEach(([key, label]) => { new Setting(colorSection) .setName(label) .setDesc(`自定义主题的 ${label}`) .addColorPicker(color => { color.setValue(isHexColor(customTheme[key]) ? customTheme[key] : '#000000'); color.onChange(async value => { await updateCustomThemeField(key, value); }); }) .addText(text => { text.setPlaceholder('#000000'); text.setValue(String(customTheme[key] || '')); text.onChange(async value => { if (!value || isHexColor(value)) { await updateCustomThemeField(key, value); } }); }); }); } } createPlatformSection(containerEl, platformKey) { const section = containerEl.createEl('details', { cls: 'bulk-copy-platform-section' }); if (platformKey === USER_CONFIG.currentPlatform) { section.open = true; } const summary = section.createEl('summary'); summary.textContent = `${PLATFORM_PRESETS[platformKey].name} 配置`; new Setting(section) .setName('图片导出策略') .setDesc('设置该平台复制时使用的图片导出策略') .addDropdown(dropdown => { dropdown.addOption('quality', '高清优先'); dropdown.addOption('balanced', '平衡'); dropdown.addOption('size', '体积优先'); dropdown.setValue(getCurrentImageStrategy(platformKey)); dropdown.onChange(async value => { USER_CONFIG.platformImageStrategies = { ...DEFAULT_USER_CONFIG.platformImageStrategies, ...(USER_CONFIG.platformImageStrategies || {}), [platformKey]: value }; await this.plugin.saveUserConfig(); }); }); new Setting(section) .setName('平台主题') .setDesc('设置该平台导出时使用的主题样式') .addDropdown(dropdown => { this.addThemeOptions(dropdown, platformKey); dropdown.setValue(getCurrentPlatformTheme(platformKey)); dropdown.onChange(async value => { USER_CONFIG.platformThemes = { ...DEFAULT_USER_CONFIG.platformThemes, ...(USER_CONFIG.platformThemes || {}), [platformKey]: value }; await this.plugin.saveUserConfig(); this.display(); }); }); new Setting(section) .setName('恢复推荐主题') .setDesc(`一键恢复到 ${getRecommendedThemeLabels(platformKey).join(' / ') || '平台推荐主题'},并清空该平台的主题细节覆盖`) .addButton(button => { button.setButtonText('恢复推荐'); button.onClick(async () => { resetPlatformToRecommendedTheme(platformKey); await this.plugin.saveUserConfig(); this.display(); }); }); this.renderThemeDetailSettings(section, platformKey, '主题细节', '单独调整该平台导出时的标题、表格、图片和代码块表现。'); this.renderThemePreview(section, platformKey); } display() { const { containerEl } = this; containerEl.empty(); containerEl.addClass('bulk-copy-settings-root'); this.renderSettingsStyles(containerEl); const hero = containerEl.createDiv({ cls: 'bulk-copy-settings-hero' }); hero.createEl('h2', { text: 'Bulk Copy with Images 设置' }); hero.createEl('p', { text: '按平台管理复制主题、图片策略和导出细节。推荐先确定平台,再调整主题与局部风格。' }); const stats = hero.createDiv({ cls: 'bulk-copy-settings-grid' }); [ ['当前默认平台', PLATFORM_PRESETS[USER_CONFIG.currentPlatform]?.name || USER_CONFIG.currentPlatform], ['当前主题', getPlatformThemeLabel(USER_CONFIG.currentPlatform)], ['图片策略', getImageStrategyLabel(USER_CONFIG.currentPlatform)] ].forEach(([label, value]) => { const stat = stats.createDiv({ cls: 'bulk-copy-settings-stat' }); stat.createDiv({ text: label, cls: 'bulk-copy-settings-stat-label' }); stat.createDiv({ text: value, cls: 'bulk-copy-settings-stat-value' }); }); const basicSection = this.createSection(containerEl, '基础配置', '设置默认平台和基础图片参数。'); new Setting(basicSection) .setName('默认平台') .setDesc('快速复制命令会直接使用这里设置的平台预设') .addDropdown(dropdown => { Object.keys(PLATFORM_PRESETS).forEach(key => { dropdown.addOption(key, PLATFORM_PRESETS[key].name); }); dropdown.setValue(USER_CONFIG.currentPlatform); dropdown.onChange(async value => { applyPlatformPreset(value); await this.plugin.saveUserConfig(); this.display(); }); }); new Setting(basicSection) .setName('图片最大宽度') .setDesc('超过此宽度的图片会被等比压缩') .addText(text => { text.setPlaceholder('1000'); text.setValue(String(USER_CONFIG.imageMaxWidth)); text.onChange(async value => { const parsed = Number(value); if (!Number.isFinite(parsed) || parsed <= 0) { return; } USER_CONFIG.imageMaxWidth = parsed; syncImageConfigFromUserConfig(); await this.plugin.saveUserConfig(); }); }); new Setting(basicSection) .setName('图片压缩质量') .setDesc('范围 0.1 - 1.0,值越高越清晰,体积也越大') .addText(text => { text.setPlaceholder('0.92'); text.setValue(String(USER_CONFIG.imageQuality)); text.onChange(async value => { const parsed = Number(value); if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 1) { return; } USER_CONFIG.imageQuality = parsed; syncImageConfigFromUserConfig(); await this.plugin.saveUserConfig(); }); }); const currentPlatformSection = this.createSection(containerEl, '当前平台快捷配置', '快速调整当前默认平台的主题和图片策略。'); new Setting(currentPlatformSection) .setName('图片导出策略') .setDesc('为当前默认平台设置图片导出策略') .addDropdown(dropdown => { dropdown.addOption('quality', '高清优先'); dropdown.addOption('balanced', '平衡'); dropdown.addOption('size', '体积优先'); dropdown.setValue(getCurrentImageStrategy(USER_CONFIG.currentPlatform)); dropdown.onChange(async value => { USER_CONFIG.platformImageStrategies = { ...DEFAULT_USER_CONFIG.platformImageStrategies, ...(USER_CONFIG.platformImageStrategies || {}), [USER_CONFIG.currentPlatform]: value }; await this.plugin.saveUserConfig(); this.display(); }); }); new Setting(currentPlatformSection) .setName('平台主题') .setDesc('为当前默认平台设置导出主题样式') .addDropdown(dropdown => { this.addThemeOptions(dropdown, USER_CONFIG.currentPlatform); dropdown.setValue(getCurrentPlatformTheme(USER_CONFIG.currentPlatform)); dropdown.onChange(async value => { USER_CONFIG.platformThemes = { ...DEFAULT_USER_CONFIG.platformThemes, ...(USER_CONFIG.platformThemes || {}), [USER_CONFIG.currentPlatform]: value }; await this.plugin.saveUserConfig(); this.display(); }); }); this.renderThemeDetailSettings( currentPlatformSection, USER_CONFIG.currentPlatform, '当前平台主题细节', '这里的细节设置会应用到当前默认平台。' ); const customThemeSection = this.createSection(containerEl, '自定义主题', '自定义主题模板已迁移到专门的管理器中,便于集中编辑和维护。'); new Setting(customThemeSection) .setName('主题模板管理器') .setDesc('打开独立管理器,集中处理搜索、排序、导入导出和主题编辑') .addButton(button => { button.setButtonText('打开管理器'); button.setCta(); button.onClick(() => { new ThemeManagerModal(this.app, this.plugin, () => this.display()).open(); }); }); const allPlatformsSection = this.createSection(containerEl, '各平台配置', '每个平台都可以独立设置图片策略、主题、细节参数,并查看预览。'); Object.keys(PLATFORM_PRESETS).forEach(platformKey => { this.createPlatformSection(allPlatformsSection, platformKey); }); const behaviorSection = this.createSection(containerEl, '行为偏好', '控制复制时的辅助行为与提示。'); new Setting(behaviorSection) .setName('启用主题样式保留') .setDesc('导出时尽量保留当前 Obsidian 主题的视觉效果') .addToggle(toggle => { toggle.setValue(!!USER_CONFIG.enableThemeStyles); toggle.onChange(async value => { USER_CONFIG.enableThemeStyles = value; await this.plugin.saveUserConfig(); }); }); new Setting(behaviorSection) .setName('显示进度提示') .setDesc('处理多张图片时显示通知提示') .addToggle(toggle => { toggle.setValue(!!USER_CONFIG.enableProgressNotice); toggle.onChange(async value => { USER_CONFIG.enableProgressNotice = value; await this.plugin.saveUserConfig(); }); }); } } /* ========== 复制内容函数 ========== */ async function copyContentWithImages(plugin, content, isFullFile) { const startTime = performance.now(); try { const file = plugin.app.workspace.getActiveFile(); if (!file) { new Notice('❌ 请先打开一篇 Markdown 笔记'); return; } console.log('[bulk-copy] 🚀 开始复制内容...'); const exportResult = await exportContentForClipboard(plugin, { mode: isFullFile ? 'full' : 'selection', selectedText: content || '', platform: USER_CONFIG.currentPlatform, includeImages: true, preserveTheme: USER_CONFIG.enableThemeStyles }); // 写入剪贴板 try { const payload = buildClipboardPayload(exportResult); if (Object.keys(payload).length === 0) { throw new Error('没有可用的剪贴板格式'); } await writeClipboardPayload(payload, exportResult); console.log('[bulk-copy] ✅ 复制成功', exportResult.meta); let sourceLabel = 'Markdown 兜底'; if (exportResult.meta && exportResult.meta.source === 'preview-dom') { sourceLabel = '预览 DOM'; } else if (exportResult.meta && exportResult.meta.source === 'offscreen-render') { sourceLabel = '离屏渲染'; } const strategyLabel = getImageStrategyLabel(USER_CONFIG.currentPlatform); const themeLabel = getPlatformThemeLabel(USER_CONFIG.currentPlatform); const imageCount = exportResult.meta?.imageCount ?? 0; new Notice(`✅ 已复制内容(来源: ${sourceLabel},主题: ${themeLabel},图片策略: ${strategyLabel},图片数: ${imageCount})`); if (exportResult.meta && exportResult.meta.warnings && exportResult.meta.warnings.length > 0) { exportResult.meta.warnings.forEach(warning => new Notice(`⚠️ ${warning}`)); } } catch (err) { console.error('[bulk-copy] 复制失败:', err); new Notice('❌ 复制失败: ' + err.message); } const endTime = performance.now(); const duration = ((endTime - startTime) / 1000).toFixed(2); console.log(`[bulk-copy] ✅ 复制完成,耗时: ${duration}秒`); } catch (err) { console.error('[bulk-copy] 复制过程发生错误:', err); new Notice('❌ 复制失败: ' + err.message); } } /* ========== 快速复制 ========== */ async function quickCopyActiveFileWithImages(plugin) { const file = plugin.app.workspace.getActiveFile(); if (!file) { new Notice('❌ 请先打开一篇 Markdown 笔记'); return; } await copyContentWithImages(plugin, null, true); } /* ========== 复制全文(带平台选择) ========== */ async function copyActiveFileWithImages(plugin) { const file = plugin.app.workspace.getActiveFile(); if (!file) { new Notice('❌ 请先打开一篇 Markdown 笔记'); return; } new PlatformSelectorModal(plugin.app, async (selectedPlatform) => { applyPlatformPreset(selectedPlatform); await copyContentWithImages(plugin, null, true); }).open(); } /* ========== 复制选定内容(带平台选择) ========== */ async function copySelectedContentWithImages(plugin) { const file = plugin.app.workspace.getActiveFile(); if (!file) { new Notice('❌ 请先打开一篇 Markdown 笔记'); return; } const activeEditor = plugin.app.workspace.activeEditor; if (!activeEditor || !activeEditor.editor) { new Notice('❌ 无法获取编辑器实例'); return; } const editor = activeEditor.editor; const selectedText = editor.getSelection(); if (!selectedText || selectedText.trim() === '') { new Notice('❌ 请先选择要复制的文本内容'); return; } new PlatformSelectorModal(plugin.app, async (selectedPlatform) => { applyPlatformPreset(selectedPlatform); await copyContentWithImages(plugin, selectedText, false); }).open(); } /* ========== 快速复制选定内容 ========== */ async function quickCopySelectedContentWithImages(plugin) { const file = plugin.app.workspace.getActiveFile(); if (!file) { new Notice('❌ 请先打开一篇 Markdown 笔记'); return; } const activeEditor = plugin.app.workspace.activeEditor; if (!activeEditor || !activeEditor.editor) { new Notice('❌ 无法获取编辑器实例'); return; } const editor = activeEditor.editor; const selectedText = editor.getSelection(); if (!selectedText || selectedText.trim() === '') { new Notice('❌ 请先选择要复制的文本内容'); return; } await copyContentWithImages(plugin, selectedText, false); } /* ========== 插件入口 ========== */ module.exports = class BulkCopyImagesPlugin extends Plugin { async onload() { console.log('[bulk-copy] Bulk Copy with Images 插件已加载'); await this.loadUserConfig(); new Notice('✅ Bulk Copy with Images 插件已加载'); // 主命令:复制全文(带平台选择) this.addCommand({ id: 'bulk-copy-with-images', name: '复制全文(选择平台)', hotkeys: [{ modifiers: ['Ctrl', 'Shift'], key: 'c' }], callback: () => copyActiveFileWithImages(this) }); // 主命令:复制选定内容(带平台选择) this.addCommand({ id: 'bulk-copy-selected-with-images', name: '复制选定内容(选择平台)', hotkeys: [{ modifiers: ['Ctrl', 'Shift'], key: 'v' }], callback: () => copySelectedContentWithImages(this) }); // 快速命令:复制全文(使用默认平台) this.addCommand({ id: 'quick-copy-with-images', name: '快速复制全文(使用默认平台)', hotkeys: [{ modifiers: ['Ctrl', 'Alt'], key: 'c' }], callback: () => quickCopyActiveFileWithImages(this) }); // 快速命令:复制选定内容(使用默认平台) this.addCommand({ id: 'quick-copy-selected-with-images', name: '快速复制选定内容(使用默认平台)', hotkeys: [{ modifiers: ['Ctrl', 'Alt'], key: 'v' }], callback: () => quickCopySelectedContentWithImages(this) }); // 平台选择命令 Object.keys(PLATFORM_PRESETS).forEach(platformKey => { const preset = PLATFORM_PRESETS[platformKey]; this.addCommand({ id: `switch-platform-${platformKey}`, name: `切换默认平台: ${preset.name}`, callback: async () => { applyPlatformPreset(platformKey); await this.saveUserConfig(); } }); }); this.settingTab = new BulkCopySettingTab(this.app, this); this.addSettingTab(this.settingTab); // 监听主题切换 this.registerEvent( this.app.workspace.on('css-change', () => { Cache.clear(); console.log('[bulk-copy] 主题已切换,清除样式缓存'); }) ); } async loadUserConfig() { const savedConfig = await this.loadData(); USER_CONFIG = { ...DEFAULT_USER_CONFIG, ...(savedConfig || {}) }; USER_CONFIG.platformImageStrategies = { ...DEFAULT_USER_CONFIG.platformImageStrategies, ...(savedConfig?.platformImageStrategies || {}) }; USER_CONFIG.platformThemes = { ...DEFAULT_USER_CONFIG.platformThemes, ...(savedConfig?.platformThemes || {}) }; USER_CONFIG.customThemes = { ...DEFAULT_USER_CONFIG.customThemes, ...(savedConfig?.customThemes || {}) }; USER_CONFIG.customThemeEditorKey = savedConfig?.customThemeEditorKey || DEFAULT_USER_CONFIG.customThemeEditorKey; USER_CONFIG.customThemeSearchQuery = savedConfig?.customThemeSearchQuery || DEFAULT_USER_CONFIG.customThemeSearchQuery; USER_CONFIG.customThemeOrder = savedConfig?.customThemeOrder || DEFAULT_USER_CONFIG.customThemeOrder; USER_CONFIG.themePreviewPlatform = savedConfig?.themePreviewPlatform || DEFAULT_USER_CONFIG.themePreviewPlatform; USER_CONFIG.themeListShowUsedOnly = typeof savedConfig?.themeListShowUsedOnly === 'boolean' ? savedConfig.themeListShowUsedOnly : DEFAULT_USER_CONFIG.themeListShowUsedOnly; USER_CONFIG.themeListUsedFirst = typeof savedConfig?.themeListUsedFirst === 'boolean' ? savedConfig.themeListUsedFirst : DEFAULT_USER_CONFIG.themeListUsedFirst; USER_CONFIG.themeListShowRecommendedOnly = typeof savedConfig?.themeListShowRecommendedOnly === 'boolean' ? savedConfig.themeListShowRecommendedOnly : DEFAULT_USER_CONFIG.themeListShowRecommendedOnly; USER_CONFIG.themeManagerListCollapsed = typeof savedConfig?.themeManagerListCollapsed === 'boolean' ? savedConfig.themeManagerListCollapsed : DEFAULT_USER_CONFIG.themeManagerListCollapsed; USER_CONFIG.lastAppliedRecommendedTheme = savedConfig?.lastAppliedRecommendedTheme || DEFAULT_USER_CONFIG.lastAppliedRecommendedTheme; USER_CONFIG.themeManagerSidebarWidth = savedConfig?.themeManagerSidebarWidth || DEFAULT_USER_CONFIG.themeManagerSidebarWidth; USER_CONFIG.platformThemeOverrides = { ...DEFAULT_USER_CONFIG.platformThemeOverrides, ...(savedConfig?.platformThemeOverrides || {}) }; applyCurrentPlatformConfig(); } async saveUserConfig() { syncImageConfigFromUserConfig(); await this.saveData(USER_CONFIG); } onunload() { console.log('[bulk-copy] Bulk Copy with Images 插件已卸载'); Cache.clear(); } };