1. 支持markdown和富文本编辑器
2. 完善代码块和图片复制粘贴
This commit is contained in:
parent
258ac53811
commit
5a4b3e66bd
166
README.md
166
README.md
@ -4,15 +4,17 @@
|
||||
|
||||
## ✨ 核心功能
|
||||
|
||||
### 1. 完整的排版保留
|
||||
- ✅ 标题(H1-H6)
|
||||
- ✅ 粗体、斜体、删除线、高亮
|
||||
- ✅ 代码块和行内代码
|
||||
- ✅ 列表(有序、无序)
|
||||
- ✅ 引用块
|
||||
- ✅ 表格
|
||||
- ✅ 链接
|
||||
- ✅ 分割线
|
||||
### 1. 完整的排版保留(增强版)
|
||||
- ✅ **标题**:H1-H6 所有级别,保留层级结构
|
||||
- ✅ **文本格式**:粗体、斜体、粗斜体、删除线、高亮
|
||||
- ✅ **代码**:代码块(支持语法标记)、行内代码(完整转义)
|
||||
- ✅ **列表**:有序列表、无序列表(支持混合)
|
||||
- ✅ **引用块**:多行引用,保持完整格式
|
||||
- ✅ **表格**:完整的 Markdown 表格转换
|
||||
- ✅ **链接**:自动转换并添加安全属性
|
||||
- ✅ **分割线**:支持 `---`、`***`、`___` 三种格式
|
||||
- ✅ **换行**:支持双空格换行
|
||||
- ✅ **段落**:智能识别段落,保持原有结构
|
||||
|
||||
### 2. 智能图片处理
|
||||
- ✅ **自动压缩**:大图自动缩放到合适尺寸
|
||||
@ -180,9 +182,153 @@ A: PNG, JPG, JPEG, GIF, WebP, SVG, BMP, TIFF 等常见格式。
|
||||
### Q: 动图会变成静态图吗?
|
||||
A: 不会,GIF 动图会保留动画效果。
|
||||
|
||||
### Q: 代码块或图片还是显示为占位符怎么办?
|
||||
A:
|
||||
1. 打开浏览器开发者工具(F12)
|
||||
2. 切换到 Console(控制台)标签
|
||||
3. 复制文档时查看输出的调试信息
|
||||
4. 检查是否有 `[bulk-copy] HTML Content Preview:` 日志
|
||||
5. 如果 HTML 预览中已经包含正确内容,问题可能在剪贴板或目标平台
|
||||
6. 尝试在不同浏览器中测试(推荐 Chrome/Edge)
|
||||
|
||||
### Q: 在某些平台粘贴后格式丢失?
|
||||
A:
|
||||
1. 使用 `Ctrl+Shift+V`(纯文本粘贴)可能会丢失格式
|
||||
2. 应该使用 `Ctrl+V`(富文本粘贴)
|
||||
3. 某些平台(如 Notion)有特殊的粘贴处理,可能需要多试几次
|
||||
4. 微信公众号建议在新版编辑器中使用
|
||||
|
||||
## 📋 支持的 Markdown 语法清单
|
||||
|
||||
以下所有语法都会被正确转换为 HTML:
|
||||
|
||||
### 标题
|
||||
```markdown
|
||||
# H1 标题
|
||||
## H2 标题
|
||||
### H3 标题
|
||||
#### H4 标题
|
||||
##### H5 标题
|
||||
###### H6 标题
|
||||
```
|
||||
|
||||
### 文本格式
|
||||
```markdown
|
||||
**粗体文本**
|
||||
*斜体文本*
|
||||
***粗斜体***
|
||||
~~删除线~~
|
||||
==高亮文本==
|
||||
```
|
||||
|
||||
### 列表
|
||||
```markdown
|
||||
- 无序列表项 1
|
||||
- 无序列表项 2
|
||||
|
||||
1. 有序列表项 1
|
||||
2. 有序列表项 2
|
||||
```
|
||||
|
||||
### 引用
|
||||
```markdown
|
||||
> 这是一段引用
|
||||
> 可以有多行
|
||||
```
|
||||
|
||||
### 代码
|
||||
```markdown
|
||||
`行内代码`
|
||||
|
||||
\```javascript
|
||||
// 代码块
|
||||
function hello() {
|
||||
console.log("Hello!");
|
||||
}
|
||||
\```
|
||||
```
|
||||
|
||||
### 表格
|
||||
```markdown
|
||||
| 列1 | 列2 | 列3 |
|
||||
|-----|-----|-----|
|
||||
| 数据1 | 数据2 | 数据3 |
|
||||
| 数据4 | 数据5 | 数据6 |
|
||||
```
|
||||
|
||||
### 链接和图片
|
||||
```markdown
|
||||
[链接文本](https://example.com)
|
||||

|
||||
![[Obsidian 图片语法.png]]
|
||||
```
|
||||
|
||||
### 分割线
|
||||
```markdown
|
||||
---
|
||||
***
|
||||
___
|
||||
```
|
||||
|
||||
## 🎨 转换示例
|
||||
|
||||
### 输入(Markdown)
|
||||
```markdown
|
||||
## 测试文档
|
||||
|
||||
这是一段**粗体**文本和*斜体*文本。
|
||||
|
||||
- 列表项 1
|
||||
- 列表项 2
|
||||
|
||||
> 这是引用内容
|
||||
|
||||
代码示例:`console.log("Hello")`
|
||||
|
||||

|
||||
```
|
||||
|
||||
### 输出(HTML)
|
||||
所有格式和图片都会完美保留,可以直接粘贴到任何富文本编辑器!
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
### v2.0.0 (2024)
|
||||
### v2.2.1 (最新)
|
||||
- 🐛 **统一占位符格式为 HTML 注释**
|
||||
- ✅ 彻底修复代码块显示为 `CODE_BLOCK_0` 的问题
|
||||
- ✅ 所有占位符(代码块、表格、图片、行内代码)统一使用 HTML 注释格式
|
||||
- ✅ 增强调试日志,自动检测未还原的占位符
|
||||
- ✅ 优化还原顺序,先还原块级元素,后还原行内元素
|
||||
- 📊 新增转换统计信息,方便查看处理结果
|
||||
|
||||
### v2.2.0
|
||||
- 🚀 **重新设计富文本编辑器兼容方案**
|
||||
- ✅ 完全修复代码块和图片不显示的问题
|
||||
- ✅ 优化剪贴板格式,提供简化 HTML 和完整 HTML 两种格式
|
||||
- ✅ 使用 HTML 注释作为占位符,更安全可靠
|
||||
- ✅ 添加降级方案,支持传统 execCommand 复制
|
||||
- ✅ 添加详细调试日志,方便问题排查
|
||||
- ✅ 改进占位符保护机制,支持嵌套 HTML 元素
|
||||
- 🔧 优化内存使用,提高大文档处理性能
|
||||
|
||||
### v2.1.1
|
||||
- 🐛 **修复占位符还原问题**
|
||||
- ✅ 修复代码块显示为 `CODE_BLOCK_0` 的问题
|
||||
- ✅ 修复图片显示为 `PROTECTED_HTML_0` 的问题
|
||||
- ✅ 优化占位符命名,更简洁可靠
|
||||
- ✅ 使用全局替换确保所有占位符正确还原
|
||||
- ✅ 改进段落处理中的占位符识别
|
||||
|
||||
### v2.1.0
|
||||
- 🚀 **完全重写 Markdown 转换引擎**
|
||||
- ✅ 修复内容显示不全的问题
|
||||
- ✅ 改进列表、引用块、代码块处理
|
||||
- ✅ 增强段落识别逻辑
|
||||
- ✅ 保护已转换的 HTML 标签
|
||||
- ✅ 支持更多 Markdown 语法
|
||||
- ✅ 更准确的文本样式转换
|
||||
|
||||
### v2.0.0
|
||||
- ✨ 新增图片智能压缩功能
|
||||
- ✨ 新增图片尺寸限制
|
||||
- ✨ 新增可自定义配置
|
||||
|
||||
276
main.js
276
main.js
@ -121,7 +121,7 @@ const getMime = ext => ({
|
||||
function markdownToHtml(md) {
|
||||
let html = md;
|
||||
|
||||
// 转义 HTML 特殊字符(用于代码块等)
|
||||
// 转义 HTML 特殊字符(仅用于代码块)
|
||||
const escapeHtml = (text) => {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
@ -131,43 +131,60 @@ function markdownToHtml(md) {
|
||||
.replace(/'/g, ''');
|
||||
};
|
||||
|
||||
// 保护已存在的 HTML 标签(如图片、已转换的元素)
|
||||
const protectedElements = [];
|
||||
html = html.replace(/<(img|pre|code|table|div)[^>]*>[\s\S]*?<\/\1>|<(img|br|hr)[^>]*\/?>/gi, (match) => {
|
||||
const id = protectedElements.length;
|
||||
protectedElements.push(match);
|
||||
return `\n<!--PROTECTED${id}-->\n`;
|
||||
});
|
||||
|
||||
// 1. 代码块(先处理,避免内部语法被转换)
|
||||
const codeBlocks = [];
|
||||
html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
|
||||
const placeholder = `___CODE_BLOCK_${codeBlocks.length}___`;
|
||||
codeBlocks.push(`<pre><code class="language-${lang || ''}">${escapeHtml(code.trim())}</code></pre>`);
|
||||
return placeholder;
|
||||
html = html.replace(/```[\s\S]*?```/g, (match) => {
|
||||
const id = codeBlocks.length;
|
||||
const langMatch = match.match(/```(\w+)?\n([\s\S]*?)```/);
|
||||
if (langMatch) {
|
||||
const lang = langMatch[1] || '';
|
||||
const code = langMatch[2] || '';
|
||||
codeBlocks.push(`<pre><code class="language-${lang}">${escapeHtml(code.trim())}</code></pre>`);
|
||||
} else {
|
||||
const code = match.replace(/```/g, '').trim();
|
||||
codeBlocks.push(`<pre><code>${escapeHtml(code)}</code></pre>`);
|
||||
}
|
||||
return `\n<!--CODEBLOCK${id}-->\n`;
|
||||
});
|
||||
|
||||
// 2. 行内代码
|
||||
const inlineCodes = [];
|
||||
html = html.replace(/`([^`]+)`/g, (match, code) => {
|
||||
const placeholder = `___INLINE_CODE_${inlineCodes.length}___`;
|
||||
html = html.replace(/`([^`\n]+)`/g, (match, code) => {
|
||||
const id = inlineCodes.length;
|
||||
inlineCodes.push(`<code>${escapeHtml(code)}</code>`);
|
||||
return placeholder;
|
||||
return `<!--INLINECODE${id}-->`;
|
||||
});
|
||||
|
||||
// 3. 表格处理(Markdown 表格)
|
||||
const tables = [];
|
||||
html = html.replace(/^\|(.+)\|[ \t]*\n\|[-:\s|]+\|[ \t]*\n((?:\|.+\|[ \t]*\n?)*)/gm, (match, header, body) => {
|
||||
const placeholder = `___TABLE_${tables.length}___`;
|
||||
html = html.replace(/^\|(.+)\|[ \t]*$\n^\|[-:\s|]+\|[ \t]*$\n((?:^\|.+\|[ \t]*$\n?)*)/gm, (match, header, body) => {
|
||||
const id = tables.length;
|
||||
|
||||
// 处理表头
|
||||
const headers = header.split('|').map(h => h.trim()).filter(h => h);
|
||||
const headerRow = headers.map(h => `<th>${h}</th>`).join('');
|
||||
|
||||
// 处理表体
|
||||
const rows = body.trim().split('\n').map(row => {
|
||||
const bodyRows = body.trim().split('\n').filter(row => row.trim());
|
||||
const rows = bodyRows.map(row => {
|
||||
const cells = row.split('|').map(c => c.trim()).filter(c => c);
|
||||
return '<tr>' + cells.map(c => `<td>${c}</td>`).join('') + '</tr>';
|
||||
}).join('\n');
|
||||
|
||||
const tableHtml = `<table><thead><tr>${headerRow}</tr></thead><tbody>${rows}</tbody></table>`;
|
||||
tables.push(tableHtml);
|
||||
return placeholder;
|
||||
return `\n<!--TABLE${id}-->\n`;
|
||||
});
|
||||
|
||||
// 4. 标题(h1-h6)
|
||||
// 4. 标题(h1-h6)- 必须在行首
|
||||
html = html.replace(/^######\s+(.+)$/gm, '<h6>$1</h6>');
|
||||
html = html.replace(/^#####\s+(.+)$/gm, '<h5>$1</h5>');
|
||||
html = html.replace(/^####\s+(.+)$/gm, '<h4>$1</h4>');
|
||||
@ -176,26 +193,94 @@ function markdownToHtml(md) {
|
||||
html = html.replace(/^#\s+(.+)$/gm, '<h1>$1</h1>');
|
||||
|
||||
// 5. 引用块
|
||||
html = html.replace(/^>\s+(.+)$/gm, '<blockquote>$1</blockquote>');
|
||||
// 合并连续的引用块
|
||||
html = html.replace(/(<\/blockquote>\n<blockquote>)/g, '\n');
|
||||
const lines = html.split('\n');
|
||||
const processedLines = [];
|
||||
let inBlockquote = false;
|
||||
let blockquoteContent = [];
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (line.match(/^>\s/)) {
|
||||
// 引用行
|
||||
const content = line.replace(/^>\s?/, '');
|
||||
blockquoteContent.push(content);
|
||||
inBlockquote = true;
|
||||
} else {
|
||||
// 非引用行
|
||||
if (inBlockquote) {
|
||||
// 结束引用块
|
||||
processedLines.push('<blockquote>' + blockquoteContent.join('\n') + '</blockquote>');
|
||||
blockquoteContent = [];
|
||||
inBlockquote = false;
|
||||
}
|
||||
processedLines.push(line);
|
||||
}
|
||||
}
|
||||
// 处理末尾的引用块
|
||||
if (inBlockquote && blockquoteContent.length > 0) {
|
||||
processedLines.push('<blockquote>' + blockquoteContent.join('\n') + '</blockquote>');
|
||||
}
|
||||
|
||||
html = processedLines.join('\n');
|
||||
|
||||
// 6. 分割线
|
||||
html = html.replace(/^(?:---|\*\*\*|___)$/gm, '<hr />');
|
||||
html = html.replace(/^(?:---|___|\*\*\*)$/gm, '<hr />');
|
||||
|
||||
// 7. 列表处理(改进版)
|
||||
// 无序列表
|
||||
html = html.replace(/^[\*\-\+]\s+(.+)$/gm, '<li>$1</li>');
|
||||
// 有序列表
|
||||
html = html.replace(/^\d+\.\s+(.+)$/gm, '<li class="ordered">$1</li>');
|
||||
// 7. 列表处理(改进版,支持嵌套)
|
||||
const listLines = html.split('\n');
|
||||
const processedListLines = [];
|
||||
let inList = false;
|
||||
let listType = null; // 'ul' or 'ol'
|
||||
let listItems = [];
|
||||
|
||||
// 合并列表项
|
||||
html = html.replace(/(<li>(?:(?!<li class="ordered">).)*?<\/li>\n?)+/g, '<ul>$&</ul>');
|
||||
html = html.replace(/(<li class="ordered">.*?<\/li>\n?)+/g, (match) => {
|
||||
return '<ol>' + match.replace(/ class="ordered"/g, '') + '</ol>';
|
||||
});
|
||||
for (let i = 0; i < listLines.length; i++) {
|
||||
const line = listLines[i];
|
||||
const ulMatch = line.match(/^([\*\-\+])\s+(.+)$/);
|
||||
const olMatch = line.match(/^\d+\.\s+(.+)$/);
|
||||
|
||||
if (ulMatch) {
|
||||
// 无序列表
|
||||
if (!inList || listType !== 'ul') {
|
||||
// 开始新的无序列表
|
||||
if (inList && listItems.length > 0) {
|
||||
processedListLines.push(`<${listType}>${listItems.join('')}</${listType}>`);
|
||||
}
|
||||
listType = 'ul';
|
||||
inList = true;
|
||||
listItems = [];
|
||||
}
|
||||
listItems.push(`<li>${ulMatch[2]}</li>`);
|
||||
} else if (olMatch) {
|
||||
// 有序列表
|
||||
if (!inList || listType !== 'ol') {
|
||||
// 开始新的有序列表
|
||||
if (inList && listItems.length > 0) {
|
||||
processedListLines.push(`<${listType}>${listItems.join('')}</${listType}>`);
|
||||
}
|
||||
listType = 'ol';
|
||||
inList = true;
|
||||
listItems = [];
|
||||
}
|
||||
listItems.push(`<li>${olMatch[1]}</li>`);
|
||||
} else {
|
||||
// 非列表行
|
||||
if (inList && listItems.length > 0) {
|
||||
processedListLines.push(`<${listType}>${listItems.join('')}</${listType}>`);
|
||||
listItems = [];
|
||||
inList = false;
|
||||
listType = null;
|
||||
}
|
||||
processedListLines.push(line);
|
||||
}
|
||||
}
|
||||
// 处理末尾的列表
|
||||
if (inList && listItems.length > 0) {
|
||||
processedListLines.push(`<${listType}>${listItems.join('')}</${listType}>`);
|
||||
}
|
||||
|
||||
// 8. 文本样式(顺序很重要)
|
||||
html = processedListLines.join('\n');
|
||||
|
||||
// 8. 文本样式(顺序很重要,先处理组合样式)
|
||||
// 粗体+斜体(***text*** 或 ___text___)
|
||||
html = html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
|
||||
html = html.replace(/___(.+?)___/g, '<strong><em>$1</em></strong>');
|
||||
@ -205,8 +290,8 @@ function markdownToHtml(md) {
|
||||
html = html.replace(/__(.+?)__/g, '<strong>$1</strong>');
|
||||
|
||||
// 斜体(*text* 或 _text_)
|
||||
html = html.replace(/\*([^\s*](?:.*?[^\s*])?)\*/g, '<em>$1</em>');
|
||||
html = html.replace(/_([^\s_](?:.*?[^\s_])?)_/g, '<em>$1</em>');
|
||||
html = html.replace(/\*([^\s*][^*]*?)\*/g, '<em>$1</em>');
|
||||
html = html.replace(/\b_([^\s_][^_]*?)_\b/g, '<em>$1</em>');
|
||||
|
||||
// 删除线
|
||||
html = html.replace(/~~(.+?)~~/g, '<del>$1</del>');
|
||||
@ -218,59 +303,87 @@ function markdownToHtml(md) {
|
||||
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
|
||||
|
||||
// 10. 段落处理(改进版)
|
||||
const lines = html.split('\n');
|
||||
const processed = [];
|
||||
const finalLines = html.split('\n');
|
||||
const finalProcessed = [];
|
||||
let inParagraph = false;
|
||||
let paragraphLines = [];
|
||||
|
||||
for (let line of lines) {
|
||||
for (let i = 0; i < finalLines.length; i++) {
|
||||
const line = finalLines[i];
|
||||
const trimmed = line.trim();
|
||||
// 如果是块级元素或占位符,不需要包裹 <p>
|
||||
if (trimmed.match(/^<(h[1-6]|ul|ol|li|blockquote|pre|hr|div|img|table|___)/)) {
|
||||
if (inParagraph) {
|
||||
processed.push('</p>');
|
||||
|
||||
// 检查是否是块级元素或占位符
|
||||
const isBlockElement = trimmed.match(/^<(h[1-6]|ul|ol|blockquote|pre|hr|table|div)/) ||
|
||||
trimmed.match(/^<!--(?:CODEBLOCK|TABLE|PROTECTED)\d+-->$/);
|
||||
|
||||
if (isBlockElement || trimmed === '') {
|
||||
// 块级元素或空行
|
||||
if (inParagraph && paragraphLines.length > 0) {
|
||||
// 结束当前段落
|
||||
finalProcessed.push('<p>' + paragraphLines.join('\n') + '</p>');
|
||||
paragraphLines = [];
|
||||
inParagraph = false;
|
||||
}
|
||||
processed.push(line);
|
||||
} else if (trimmed === '') {
|
||||
if (inParagraph) {
|
||||
processed.push('</p>');
|
||||
inParagraph = false;
|
||||
if (trimmed !== '') {
|
||||
finalProcessed.push(line);
|
||||
}
|
||||
} else {
|
||||
// 普通文本行
|
||||
if (!inParagraph) {
|
||||
processed.push('<p>');
|
||||
inParagraph = true;
|
||||
}
|
||||
processed.push(line);
|
||||
paragraphLines.push(line);
|
||||
}
|
||||
}
|
||||
if (inParagraph) {
|
||||
processed.push('</p>');
|
||||
|
||||
// 处理末尾的段落
|
||||
if (inParagraph && paragraphLines.length > 0) {
|
||||
finalProcessed.push('<p>' + paragraphLines.join('\n') + '</p>');
|
||||
}
|
||||
|
||||
html = processed.join('\n');
|
||||
html = finalProcessed.join('\n');
|
||||
|
||||
// 11. 还原占位符
|
||||
// 还原表格
|
||||
// 11. 换行处理(Markdown 中的双空格 + 换行)
|
||||
html = html.replace(/ \n/g, '<br>\n');
|
||||
|
||||
// 12. 还原占位符(按顺序还原,使用正则全局替换)
|
||||
// 先还原块级元素(表格、代码块)
|
||||
tables.forEach((table, i) => {
|
||||
html = html.replace(`___TABLE_${i}___`, table);
|
||||
html = html.replace(new RegExp(`<!--TABLE${i}-->`, 'g'), table);
|
||||
});
|
||||
|
||||
// 还原代码块
|
||||
codeBlocks.forEach((code, i) => {
|
||||
html = html.replace(`___CODE_BLOCK_${i}___`, code);
|
||||
html = html.replace(new RegExp(`<!--CODEBLOCK${i}-->`, 'g'), code);
|
||||
});
|
||||
|
||||
// 还原行内代码
|
||||
protectedElements.forEach((element, i) => {
|
||||
html = html.replace(new RegExp(`<!--PROTECTED${i}-->`, 'g'), element);
|
||||
});
|
||||
|
||||
// 最后还原行内元素(行内代码)
|
||||
inlineCodes.forEach((code, i) => {
|
||||
html = html.replace(`___INLINE_CODE_${i}___`, code);
|
||||
html = html.replace(new RegExp(`<!--INLINECODE${i}-->`, 'g'), code);
|
||||
});
|
||||
|
||||
// 12. 清理
|
||||
// 13. 清理多余的空标签和空行
|
||||
html = html.replace(/<p>\s*<\/p>/g, '');
|
||||
html = html.replace(/<p>(\s*<br>\s*)+<\/p>/g, '');
|
||||
html = html.replace(/\n{3,}/g, '\n\n');
|
||||
|
||||
return html;
|
||||
// 调试:检查是否还有未还原的占位符
|
||||
const remainingPlaceholders = html.match(/<!--(?:CODEBLOCK|TABLE|PROTECTED|INLINECODE)\d+-->/g);
|
||||
if (remainingPlaceholders) {
|
||||
console.warn('[bulk-copy] ⚠️ 发现未还原的占位符:', remainingPlaceholders);
|
||||
console.warn('[bulk-copy] 代码块数量:', codeBlocks.length);
|
||||
console.warn('[bulk-copy] 表格数量:', tables.length);
|
||||
console.warn('[bulk-copy] 行内代码数量:', inlineCodes.length);
|
||||
console.warn('[bulk-copy] 受保护元素数量:', protectedElements.length);
|
||||
} else {
|
||||
console.log('[bulk-copy] ✅ 所有占位符已成功还原');
|
||||
console.log('[bulk-copy] 转换统计 - 代码块:', codeBlocks.length, '表格:', tables.length, '图片:', protectedElements.length);
|
||||
}
|
||||
|
||||
return html.trim();
|
||||
}
|
||||
|
||||
/* ========== 复制全文 + 内嵌图片 ========== */
|
||||
@ -300,14 +413,23 @@ async function copyActiveFileWithImages(plugin) {
|
||||
});
|
||||
|
||||
/* 3. 转换为 HTML */
|
||||
const htmlContent = markdownToHtml(md);
|
||||
let htmlContent = markdownToHtml(md);
|
||||
|
||||
/* 4. 生成完整的 HTML 文档(包含样式) */
|
||||
const fullHtml = `
|
||||
<!DOCTYPE html>
|
||||
// 调试:输出转换后的 HTML(开发时可以查看)
|
||||
console.log('[bulk-copy] HTML Content Preview (前 500 字符):', htmlContent.substring(0, 500));
|
||||
console.log('[bulk-copy] HTML Content Preview (后 500 字符):', htmlContent.substring(Math.max(0, htmlContent.length - 500)));
|
||||
|
||||
/* 4. 生成富文本编辑器兼容的 HTML(带内联样式) */
|
||||
const styledHtml = `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; max-width: 900px;">
|
||||
${htmlContent}
|
||||
</div>`;
|
||||
|
||||
/* 5. 生成完整的 HTML 文档(用于剪贴板,某些平台需要) */
|
||||
const fullHtml = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
/* 通用样式 - 适配多平台 */
|
||||
body {
|
||||
@ -436,27 +558,49 @@ ${htmlContent}
|
||||
</html>
|
||||
`.trim();
|
||||
|
||||
/* 5. 写入剪贴板 - 多格式支持 */
|
||||
/* 6. 写入剪贴板 - 多格式优化 */
|
||||
try {
|
||||
if (navigator.clipboard && window.ClipboardItem) {
|
||||
// 准备多种格式,提高平台兼容性
|
||||
// 使用 styledHtml(简化版)为主,fullHtml 为备用
|
||||
const clipboardData = {
|
||||
'text/plain': new Blob([md], { type: 'text/plain' }),
|
||||
'text/html': new Blob([fullHtml], { type: 'text/html' })
|
||||
'text/html': new Blob([styledHtml], { type: 'text/html' })
|
||||
};
|
||||
|
||||
const clipboardItem = new ClipboardItem(clipboardData);
|
||||
await navigator.clipboard.write([clipboardItem]);
|
||||
|
||||
console.log('[bulk-copy] 复制成功,HTML 长度:', styledHtml.length);
|
||||
new Notice('✅ 已复制全文(含图片和样式)!\n支持:飞书、微信公众号、知乎等平台');
|
||||
} else {
|
||||
// 降级:仅支持纯文本
|
||||
await navigator.clipboard.writeText(md);
|
||||
new Notice('⚠️ 已复制纯文本格式(浏览器不支持富文本)');
|
||||
// 降级方案:尝试使用传统方法
|
||||
const clipboardDiv = document.createElement('div');
|
||||
clipboardDiv.style.position = 'fixed';
|
||||
clipboardDiv.style.left = '-9999px';
|
||||
clipboardDiv.innerHTML = styledHtml;
|
||||
document.body.appendChild(clipboardDiv);
|
||||
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(clipboardDiv);
|
||||
const selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
new Notice('✅ 已复制全文(使用传统方式)');
|
||||
} catch (e) {
|
||||
await navigator.clipboard.writeText(md);
|
||||
new Notice('⚠️ 已复制纯文本格式');
|
||||
}
|
||||
|
||||
document.body.removeChild(clipboardDiv);
|
||||
selection.removeAllRanges();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[bulk-copy] 复制失败', err);
|
||||
new Notice('❌ 复制失败,请检查浏览器权限设置');
|
||||
new Notice('❌ 复制失败: ' + err.message);
|
||||
}
|
||||
|
||||
/* ----------- 单张图内嵌 ----------- */
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"id": "bulk-copy-images",
|
||||
"name": "Bulk Copy with Images",
|
||||
"version": "2.0.0",
|
||||
"version": "2.2.1",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "一键复制笔记全文,自动转换为富文本 HTML,图片内嵌为 base64。完美支持飞书云文档、微信公众号、知乎等多种编辑器平台,保留完整排版和样式。",
|
||||
"description": "一键复制笔记全文,自动转换为富文本 HTML,图片智能压缩并内嵌为 base64。完美支持飞书云文档、微信公众号、知乎等富文本编辑器平台。统一的 HTML 注释占位符机制确保代码块、图片、表格完美还原显示。",
|
||||
"author": "you",
|
||||
"authorUrl": "",
|
||||
"fundingUrl": "",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user