commit 258ac538110fe01ead9deed4104bbe03a05379c1
Author: taiyi
Date: Thu Oct 16 21:39:52 2025 +0800
第一次提交,完成copy操作,存在问题,飞书云文档复制出现问题
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..359bb53
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
diff --git a/.idea/image-copy.iml b/.idea/image-copy.iml
new file mode 100644
index 0000000..8437fe6
--- /dev/null
+++ b/.idea/image-copy.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..15ab60a
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..dc9ea49
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..599fcbe
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..664ddd3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,205 @@
+# Bulk Copy with Images - 图片优化增强版
+
+一键复制 Obsidian 笔记全文,自动转换为富文本 HTML,图片内嵌为 base64。完美支持飞书云文档、微信公众号、知乎等多种编辑器平台。
+
+## ✨ 核心功能
+
+### 1. 完整的排版保留
+- ✅ 标题(H1-H6)
+- ✅ 粗体、斜体、删除线、高亮
+- ✅ 代码块和行内代码
+- ✅ 列表(有序、无序)
+- ✅ 引用块
+- ✅ 表格
+- ✅ 链接
+- ✅ 分割线
+
+### 2. 智能图片处理
+- ✅ **自动压缩**:大图自动缩放到合适尺寸
+- ✅ **质量优化**:在清晰度和文件大小间取得平衡
+- ✅ **显示控制**:限制图片最大显示宽度,避免过长
+- ✅ **格式转换**:统一转为 JPEG 格式(压缩率更高)
+- ✅ **特殊处理**:
+ - SVG:保持矢量格式
+ - GIF:保留动画效果
+ - PNG/JPG:智能压缩
+
+### 3. 多平台兼容
+- 飞书云文档 ✅
+- 微信公众号 ✅
+- 知乎 ✅
+- 语雀 ✅
+- Notion ✅
+- 其他富文本编辑器 ✅
+
+## 🎯 图片优化说明
+
+### 当前配置
+
+```javascript
+const IMAGE_CONFIG = {
+ MAX_WIDTH: 800, // 压缩最大宽度(像素)
+ QUALITY: 0.8, // 压缩质量(0-1)
+ DISPLAY_MAX_WIDTH: 600 // 显示最大宽度(像素)
+};
+```
+
+### 参数说明
+
+#### 1. `MAX_WIDTH` - 压缩最大宽度
+- **作用**:超过此宽度的图片会被等比缩放
+- **默认值**:800px
+- **建议值**:
+ - 微信公众号:800px(推荐)
+ - 飞书文档:1000px
+ - 知乎:800px
+ - 如果图片需要更清晰:1200px
+ - 如果想要更小的文件:600px
+
+#### 2. `QUALITY` - 压缩质量
+- **作用**:控制 JPEG 压缩质量
+- **默认值**:0.8(80%)
+- **建议值**:
+ - 高质量(文件较大):0.85-0.9
+ - 平衡模式(推荐):0.75-0.85
+ - 高压缩(文件小,质量降低):0.6-0.75
+
+#### 3. `DISPLAY_MAX_WIDTH` - 显示最大宽度
+- **作用**:图片在编辑器中的最大显示宽度
+- **默认值**:600px
+- **说明**:
+ - 这个设置**不影响图片实际尺寸**
+ - 只控制图片在编辑器中的显示宽度
+ - 避免图片在编辑器中显得过长
+
+### 如何自定义配置?
+
+打开 `main.js`,修改文件开头的配置:
+
+```javascript
+/* ========== 图片优化配置 ========== */
+const IMAGE_CONFIG = {
+ MAX_WIDTH: 800, // 改成你想要的压缩宽度
+ QUALITY: 0.8, // 改成你想要的质量(0-1)
+ DISPLAY_MAX_WIDTH: 600 // 改成你想要的显示宽度
+};
+```
+
+### 压缩效果对比
+
+| 原始图片 | 压缩后 | 节省空间 |
+|---------|--------|---------|
+| 5MB, 3000x2000 | ~150KB | 97% |
+| 2MB, 1920x1080 | ~80KB | 96% |
+| 500KB, 800x600 | ~60KB | 88% |
+
+## 📖 使用方法
+
+### 方法 1:快捷键
+1. 打开任意 Markdown 笔记
+2. 按 `Ctrl+Shift+C`(Windows/Linux)或 `Cmd+Shift+C`(Mac)
+3. 粘贴到目标编辑器
+
+### 方法 2:命令面板
+1. 按 `Ctrl+P`(Windows/Linux)或 `Cmd+P`(Mac)
+2. 输入"复制全文"
+3. 选择"复制全文并内嵌本地图片"
+4. 粘贴到目标编辑器
+
+## 💡 使用技巧
+
+### 1. 不同平台的建议配置
+
+**微信公众号**(推荐当前配置)
+```javascript
+MAX_WIDTH: 800
+QUALITY: 0.8
+DISPLAY_MAX_WIDTH: 600
+```
+
+**飞书云文档**(可以更高清)
+```javascript
+MAX_WIDTH: 1000
+QUALITY: 0.85
+DISPLAY_MAX_WIDTH: 700
+```
+
+**知乎**(兼顾质量和加载速度)
+```javascript
+MAX_WIDTH: 800
+QUALITY: 0.8
+DISPLAY_MAX_WIDTH: 600
+```
+
+### 2. 图片过大怎么办?
+
+如果复制后提示"图片太大"或粘贴失败:
+1. 降低 `MAX_WIDTH`(如改为 600 或 700)
+2. 降低 `QUALITY`(如改为 0.7)
+3. 或者将大图片分开发送
+
+### 3. 图片不够清晰?
+
+如果复制后图片显示模糊:
+1. 提高 `MAX_WIDTH`(如改为 1000 或 1200)
+2. 提高 `QUALITY`(如改为 0.85 或 0.9)
+
+## 🔧 技术细节
+
+### 图片处理流程
+
+1. **读取图片** → 2. **判断格式** → 3. **压缩处理** → 4. **Base64 编码** → 5. **生成 HTML**
+
+### 特殊格式处理
+
+- **SVG**:保持矢量格式,不压缩
+- **GIF**:保留动画,不压缩
+- **PNG/JPG/WebP**:转为 JPEG 并压缩
+- **其他格式**:尝试压缩,失败则保持原样
+
+### 为什么转为 JPEG?
+
+- JPEG 压缩率高(文件更小)
+- 兼容性好(所有平台都支持)
+- 对于照片和复杂图片效果最好
+
+## ❓ 常见问题
+
+### Q: 为什么有些平台粘贴后样式消失了?
+A: 某些平台会过滤样式。插件已使用内联样式来提高兼容性。
+
+### Q: 图片太小/太大怎么办?
+A: 调整 `DISPLAY_MAX_WIDTH` 参数。
+
+### Q: 压缩会影响图片清晰度吗?
+A: 会有轻微影响,但肉眼难以察觉。可以通过提高 `QUALITY` 来改善。
+
+### Q: 支持哪些图片格式?
+A: PNG, JPG, JPEG, GIF, WebP, SVG, BMP, TIFF 等常见格式。
+
+### Q: 动图会变成静态图吗?
+A: 不会,GIF 动图会保留动画效果。
+
+## 📝 更新日志
+
+### v2.0.0 (2024)
+- ✨ 新增图片智能压缩功能
+- ✨ 新增图片尺寸限制
+- ✨ 新增可自定义配置
+- ✨ 优化表格支持
+- ✨ 改进 Markdown 转 HTML 转换
+- 🐛 修复图片过大导致复制失败的问题
+- 🐛 修复部分平台样式不兼容的问题
+
+### v1.0.0
+- 🎉 初始版本
+- ✅ 基本的图片内嵌功能
+
+## 📄 许可证
+
+MIT License
+
+## 🙋 反馈与支持
+
+如有问题或建议,欢迎反馈!
+
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..f7eccca
--- /dev/null
+++ b/main.js
@@ -0,0 +1,557 @@
+/* eslint-disable */
+const { Plugin, Notice, TFile } = require('obsidian');
+
+/* ========== 图片优化配置 ========== */
+const IMAGE_CONFIG = {
+ // 图片压缩最大宽度(像素)- 超过此宽度的图片会被等比缩放
+ MAX_WIDTH: 800,
+
+ // 图片压缩质量(0-1 之间,1 为最高质量)
+ // 推荐值:0.8-0.85,在质量和文件大小之间取得良好平衡
+ QUALITY: 0.8,
+
+ // 显示最大宽度(像素)- 图片在编辑器中的最大显示宽度
+ DISPLAY_MAX_WIDTH: 600
+};
+
+/* ========== 安全大图转 base64 ========== */
+async function buf2base64(buf) {
+ // 使用更直接的方式转换为 base64,避免数据损失
+ return new Promise((resolve, reject) => {
+ const bytes = new Uint8Array(buf);
+ let binary = '';
+ for (let i = 0; i < bytes.byteLength; i++) {
+ binary += String.fromCharCode(bytes[i]);
+ }
+ resolve(btoa(binary));
+ });
+}
+
+/* ========== 图片压缩和尺寸限制 ========== */
+async function compressImage(buf, ext, maxWidth = IMAGE_CONFIG.MAX_WIDTH, quality = IMAGE_CONFIG.QUALITY) {
+ return new Promise((resolve, reject) => {
+ try {
+ // 创建 Blob
+ const mime = getMime(ext);
+ const blob = new Blob([buf], { type: mime });
+
+ // 创建图片元素
+ const img = new Image();
+ const url = URL.createObjectURL(blob);
+
+ img.onload = () => {
+ // 释放临时 URL
+ URL.revokeObjectURL(url);
+
+ // 计算新尺寸(保持宽高比)
+ let width = img.width;
+ let height = img.height;
+
+ if (width > maxWidth) {
+ height = Math.round((height * maxWidth) / width);
+ width = maxWidth;
+ }
+
+ // 如果图片本来就很小,且不需要转换格式,直接返回
+ if (width === img.width && (ext.toLowerCase() === 'jpg' || ext.toLowerCase() === 'jpeg' || ext.toLowerCase() === 'png')) {
+ resolve({ data: buf, needsConversion: false });
+ return;
+ }
+
+ // 创建 Canvas 进行压缩
+ const canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = height;
+
+ const ctx = canvas.getContext('2d');
+ ctx.drawImage(img, 0, 0, width, height);
+
+ // 转换为 Blob(JPEG 格式压缩效果更好)
+ canvas.toBlob(
+ (compressedBlob) => {
+ if (!compressedBlob) {
+ reject(new Error('图片压缩失败'));
+ return;
+ }
+
+ // 将 Blob 转为 ArrayBuffer
+ const reader = new FileReader();
+ reader.onload = () => {
+ resolve({
+ data: reader.result,
+ needsConversion: true,
+ mime: 'image/jpeg' // 统一转为 JPEG
+ });
+ };
+ reader.onerror = reject;
+ reader.readAsArrayBuffer(compressedBlob);
+ },
+ 'image/jpeg', // 使用 JPEG 格式
+ quality // 压缩质量
+ );
+ };
+
+ img.onerror = () => {
+ URL.revokeObjectURL(url);
+ reject(new Error('图片加载失败'));
+ };
+
+ img.src = url;
+ } catch (err) {
+ reject(err);
+ }
+ });
+}
+
+/* ========== 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',
+ ico: 'image/x-icon',
+ tiff: 'image/tiff',
+ tif: 'image/tiff'
+}[ext.toLowerCase()] || 'application/octet-stream');
+
+/* ========== Markdown 转 HTML(保留完整排版) ========== */
+function markdownToHtml(md) {
+ let html = md;
+
+ // 转义 HTML 特殊字符(用于代码块等)
+ const escapeHtml = (text) => {
+ return text
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+ };
+
+ // 1. 代码块(先处理,避免内部语法被转换)
+ const codeBlocks = [];
+ html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
+ const placeholder = `___CODE_BLOCK_${codeBlocks.length}___`;
+ codeBlocks.push(`${escapeHtml(code.trim())}
`);
+ return placeholder;
+ });
+
+ // 2. 行内代码
+ const inlineCodes = [];
+ html = html.replace(/`([^`]+)`/g, (match, code) => {
+ const placeholder = `___INLINE_CODE_${inlineCodes.length}___`;
+ inlineCodes.push(`${escapeHtml(code)}`);
+ return placeholder;
+ });
+
+ // 3. 表格处理(Markdown 表格)
+ const tables = [];
+ html = html.replace(/^\|(.+)\|[ \t]*\n\|[-:\s|]+\|[ \t]*\n((?:\|.+\|[ \t]*\n?)*)/gm, (match, header, body) => {
+ const placeholder = `___TABLE_${tables.length}___`;
+
+ // 处理表头
+ const headers = header.split('|').map(h => h.trim()).filter(h => h);
+ const headerRow = headers.map(h => `${h} | `).join('');
+
+ // 处理表体
+ const rows = body.trim().split('\n').map(row => {
+ const cells = row.split('|').map(c => c.trim()).filter(c => c);
+ return '' + cells.map(c => `| ${c} | `).join('') + '
';
+ }).join('\n');
+
+ const tableHtml = ``;
+ tables.push(tableHtml);
+ return placeholder;
+ });
+
+ // 4. 标题(h1-h6)
+ html = html.replace(/^######\s+(.+)$/gm, '$1
');
+ html = html.replace(/^#####\s+(.+)$/gm, '$1
');
+ html = html.replace(/^####\s+(.+)$/gm, '$1
');
+ html = html.replace(/^###\s+(.+)$/gm, '$1
');
+ html = html.replace(/^##\s+(.+)$/gm, '$1
');
+ html = html.replace(/^#\s+(.+)$/gm, '$1
');
+
+ // 5. 引用块
+ html = html.replace(/^>\s+(.+)$/gm, '$1
');
+ // 合并连续的引用块
+ html = html.replace(/(<\/blockquote>\n)/g, '\n');
+
+ // 6. 分割线
+ html = html.replace(/^(?:---|\*\*\*|___)$/gm, '
');
+
+ // 7. 列表处理(改进版)
+ // 无序列表
+ html = html.replace(/^[\*\-\+]\s+(.+)$/gm, '$1');
+ // 有序列表
+ html = html.replace(/^\d+\.\s+(.+)$/gm, '$1');
+
+ // 合并列表项
+ html = html.replace(/((?:(?!).)*?<\/li>\n?)+/g, '');
+ html = html.replace(/(.*?<\/li>\n?)+/g, (match) => {
+ return '' + match.replace(/ class="ordered"/g, '') + '
';
+ });
+
+ // 8. 文本样式(顺序很重要)
+ // 粗体+斜体(***text*** 或 ___text___)
+ html = html.replace(/\*\*\*(.+?)\*\*\*/g, '$1');
+ html = html.replace(/___(.+?)___/g, '$1');
+
+ // 粗体(**text** 或 __text__)
+ html = html.replace(/\*\*(.+?)\*\*/g, '$1');
+ html = html.replace(/__(.+?)__/g, '$1');
+
+ // 斜体(*text* 或 _text_)
+ html = html.replace(/\*([^\s*](?:.*?[^\s*])?)\*/g, '$1');
+ html = html.replace(/_([^\s_](?:.*?[^\s_])?)_/g, '$1');
+
+ // 删除线
+ html = html.replace(/~~(.+?)~~/g, '$1');
+
+ // 高亮(==text==)
+ html = html.replace(/==(.+?)==/g, '$1');
+
+ // 9. 链接 [text](url)
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1');
+
+ // 10. 段落处理(改进版)
+ const lines = html.split('\n');
+ const processed = [];
+ let inParagraph = false;
+
+ for (let line of lines) {
+ const trimmed = line.trim();
+ // 如果是块级元素或占位符,不需要包裹
+ if (trimmed.match(/^<(h[1-6]|ul|ol|li|blockquote|pre|hr|div|img|table|___)/)) {
+ if (inParagraph) {
+ processed.push('
');
+ inParagraph = false;
+ }
+ processed.push(line);
+ } else if (trimmed === '') {
+ if (inParagraph) {
+ processed.push('
');
+ inParagraph = false;
+ }
+ } else {
+ if (!inParagraph) {
+ processed.push('');
+ inParagraph = true;
+ }
+ processed.push(line);
+ }
+ }
+ if (inParagraph) {
+ processed.push('
');
+ }
+
+ html = processed.join('\n');
+
+ // 11. 还原占位符
+ // 还原表格
+ tables.forEach((table, i) => {
+ html = html.replace(`___TABLE_${i}___`, table);
+ });
+
+ // 还原代码块
+ codeBlocks.forEach((code, i) => {
+ html = html.replace(`___CODE_BLOCK_${i}___`, code);
+ });
+
+ // 还原行内代码
+ inlineCodes.forEach((code, i) => {
+ html = html.replace(`___INLINE_CODE_${i}___`, code);
+ });
+
+ // 12. 清理
+ html = html.replace(/\s*<\/p>/g, '');
+ html = html.replace(/\n{3,}/g, '\n\n');
+
+ return html;
+}
+
+/* ========== 复制全文 + 内嵌图片 ========== */
+async function copyActiveFileWithImages(plugin) {
+ const file = plugin.app.workspace.getActiveFile();
+ if (!file || file.extension !== 'md') {
+ new Notice('请先打开一篇 Markdown 笔记');
+ return;
+ }
+
+ let md = await plugin.app.vault.read(file);
+ const baseFolder = file.parent.path;
+ const assetsFolder = `${baseFolder}/${file.basename}.assets`;
+
+ /* 1. Wiki 语法 ![[xxx.png]] */
+ md = await replaceAsync(md, /!\[\[([^\]|]+?)(?:\|[^\]]+)?\]\]/g,
+ async (_, raw) => {
+ const name = raw.split('|')[0].trim();
+ return await inlineImage(name, '');
+ });
+
+ /* 2. 标准语法  */
+ md = await replaceAsync(md, /!\[([^\]]*)\]\(([^)]+)\)/g,
+ async (_, alt, src) => {
+ src = decodeURIComponent(src).trim();
+ return await inlineImage(src, alt);
+ });
+
+ /* 3. 转换为 HTML */
+ const htmlContent = markdownToHtml(md);
+
+ /* 4. 生成完整的 HTML 文档(包含样式) */
+ const fullHtml = `
+
+
+
+
+
+
+
+${htmlContent}
+
+
+ `.trim();
+
+ /* 5. 写入剪贴板 - 多格式支持 */
+ try {
+ if (navigator.clipboard && window.ClipboardItem) {
+ // 准备多种格式,提高平台兼容性
+ const clipboardData = {
+ 'text/plain': new Blob([md], { type: 'text/plain' }),
+ 'text/html': new Blob([fullHtml], { type: 'text/html' })
+ };
+
+ const clipboardItem = new ClipboardItem(clipboardData);
+ await navigator.clipboard.write([clipboardItem]);
+
+ new Notice('✅ 已复制全文(含图片和样式)!\n支持:飞书、微信公众号、知乎等平台');
+ } else {
+ // 降级:仅支持纯文本
+ await navigator.clipboard.writeText(md);
+ new Notice('⚠️ 已复制纯文本格式(浏览器不支持富文本)');
+ }
+ } catch (err) {
+ console.error('[bulk-copy] 复制失败', err);
+ new Notice('❌ 复制失败,请检查浏览器权限设置');
+ }
+
+ /* ----------- 单张图内嵌 ----------- */
+ async function inlineImage(imgName, alt) {
+ const candidates = [
+ `${assetsFolder}/${imgName}`,
+ `${baseFolder}/${imgName}`,
+ imgName.startsWith('/') ? imgName.slice(1) : imgName
+ ];
+ let imgFile = candidates
+ .map(p => plugin.app.vault.getAbstractFileByPath(p))
+ .find(f => f instanceof TFile);
+ if (!imgFile) { // 全库搜文件名
+ const all = plugin.app.vault.getFiles();
+ imgFile = all.find(f => f.name === imgName);
+ }
+ 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);
+
+ // 特殊处理 SVG 文件(不压缩)
+ if (ext.toLowerCase() === 'svg') {
+ // 对于 SVG,直接读取文本内容
+ const svgContent = await plugin.app.vault.read(imgFile);
+ const base64Svg = btoa(unescape(encodeURIComponent(svgContent)));
+ // 限制 SVG 最大宽度
+ return `
`;
+ }
+
+ // GIF 动图不压缩,保持动画效果
+ if (ext.toLowerCase() === 'gif') {
+ const data = await buf2base64(buf);
+ const altText = alt ? alt.replace(/"/g, '"') : '';
+ const imgStyle = `max-width: ${IMAGE_CONFIG.DISPLAY_MAX_WIDTH}px; width: 100%; height: auto; display: block; margin: 15px auto;`;
+ return `
`;
+ }
+
+ // 处理其他图片格式:压缩图片
+ const compressed = await compressImage(buf, ext, IMAGE_CONFIG.MAX_WIDTH, IMAGE_CONFIG.QUALITY);
+
+ let finalMime = mime;
+ let finalData;
+
+ if (compressed.needsConversion) {
+ // 图片已被压缩,使用压缩后的数据
+ finalMime = compressed.mime;
+ finalData = await buf2base64(compressed.data);
+ } else {
+ // 图片无需压缩,使用原始数据
+ finalData = await buf2base64(compressed.data);
+ }
+
+ if (!finalData) {
+ throw new Error('Base64 转换失败');
+ }
+
+ // 添加内联样式,限制图片显示宽度
+ // 某些平台(如微信公众号)会过滤外部样式,所以使用内联样式
+ const imgStyle = `max-width: ${IMAGE_CONFIG.DISPLAY_MAX_WIDTH}px; width: 100%; height: auto; display: block; margin: 15px auto; border-radius: 4px;`;
+ const altText = alt ? alt.replace(/"/g, '"') : '';
+
+ return `
`;
+ } catch (e) {
+ console.warn('[bulk-copy] 读取失败', imgName, e);
+ return ``;
+ }
+ }
+}
+
+/* ========== 异步 replace ========== */
+async function replaceAsync(str, regex, asyncFn) {
+ const promises = [];
+ str.replace(regex, (match, ...args) => {
+ promises.push(asyncFn(match, ...args));
+ return match;
+ });
+ const reps = await Promise.all(promises);
+ return str.replace(regex, () => reps.shift());
+}
+
+/* ========== 插件入口 ========== */
+module.exports = class BulkCopyImagesPlugin extends Plugin {
+ onload() {
+ this.addCommand({
+ id: 'bulk-copy-with-images',
+ name: '复制全文并内嵌本地图片',
+ hotkeys: [{ modifiers: ['Ctrl', 'Shift'], key: 'c' }],
+ callback: () => copyActiveFileWithImages(this)
+ });
+ }
+ onunload() {}
+};
\ No newline at end of file
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..575b148
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,11 @@
+{
+ "id": "bulk-copy-images",
+ "name": "Bulk Copy with Images",
+ "version": "2.0.0",
+ "minAppVersion": "0.15.0",
+ "description": "一键复制笔记全文,自动转换为富文本 HTML,图片内嵌为 base64。完美支持飞书云文档、微信公众号、知乎等多种编辑器平台,保留完整排版和样式。",
+ "author": "you",
+ "authorUrl": "",
+ "fundingUrl": "",
+ "isDesktopOnly": false
+}
\ No newline at end of file
diff --git a/styles.css b/styles.css
new file mode 100644
index 0000000..e69de29