fix: 修复代码块样式复制到公众号后丢失 (#27)

- 修复代码高亮丢失:从预览 DOM 读取 computed style 补全 Obsidian 内置高亮颜色
- 修复仿 macOS 窗口圆点不显示:使用 section 标签 + inline style 替代 CSS 伪选择器
- 统一使用 section 标签:列表标记、Callout 标题/内容等元素改为 section,提升公众号兼容性
- 移除主题 CSS 中的圆点样式规则(已内联化,不再需要)
- tsconfig 排除 skills 目录避免构建报错
This commit is contained in:
joeytoday 2026-04-07 11:36:25 +08:00
parent 1c7a37b70f
commit d5adbef870
13 changed files with 87 additions and 154 deletions

4
.gitignore vendored
View file

@ -23,3 +23,7 @@ Thumbs.db
# TypeScript cache
*.tsbuildinfo
# other
dist/
skills/

View file

@ -1,5 +1,20 @@
# Changelog
## [2.3.5] - 2026-04-07
### 🐛 Bug 修复
#### 代码块样式复制到公众号后丢失 (#27)
- **修复代码高亮丢失**:复制/发布到公众号时,代码块的语法高亮颜色现在能正确保留
- **修复仿 macOS 窗口圆点不显示**:使用 `<section>` 标签 + inline style 替代 CSS 伪选择器,确保公众号编辑器不会过滤圆点元素
### 🔧 优化
- **统一使用 `<section>` 标签**将列表标记、Callout 标题/内容等元素从 `<span>`/`<div>` 改为 `<section>`,提升公众号兼容性
- **代码块圆点样式内联化**:圆点颜色直接通过 inline style 设置,不再依赖 CSS `:nth-child` 伪选择器,确保预览与复制/发布样式一致
---
## [2.3.4] - 2026-03-20
### 🐛 Bug 修复

View file

@ -40,13 +40,14 @@ export class MPConverter {
const codeEl = pre.querySelector('code');
if (codeEl) {
// 添加 macOS 风格的窗口按钮
const header = document.createElement('div');
header.className = 'mp-code-header';
// 添加 macOS 风格的窗口按钮(使用 section + inline style 确保公众号复制/发布时样式保留)
const header = document.createElement('section');
header.style.cssText = 'margin-bottom: 1em; display: flex; gap: 6px;';
for (let i = 0; i < 3; i++) {
const dot = document.createElement('span');
dot.className = 'mp-code-dot';
const dotColors = ['#ff5f56', '#ffbd2e', '#27c93f'];
for (const color of dotColors) {
const dot = document.createElement('section');
dot.style.cssText = `display: inline-block; width: 12px; height: 12px; border-radius: 50%; background-color: ${color};`;
header.appendChild(dot);
}
@ -157,15 +158,16 @@ export class MPConverter {
// 添加编号或符号
const marker = isOrdered ? `${itemNumber}. ` : '• ';
const markerSpan = document.createElement('span');
markerSpan.textContent = marker;
markerSpan.style.cssText = 'margin-right: 0.25em;';
p.appendChild(markerSpan);
const markerSection = document.createElement('section');
markerSection.textContent = marker;
markerSection.style.cssText = 'display: inline; margin-right: 0.25em;';
p.appendChild(markerSection);
// 添加内容
const contentSpan = document.createElement('span');
contentSpan.innerHTML = liElement.innerHTML;
p.appendChild(contentSpan);
const contentSection = document.createElement('section');
contentSection.style.cssText = 'display: inline;';
contentSection.innerHTML = liElement.innerHTML;
p.appendChild(contentSection);
section.appendChild(p);
@ -240,26 +242,27 @@ export class MPConverter {
newCallout.style.cssText = `background: ${colors.bg}; border-left: 4px solid ${colors.border}; border-radius: 6px; padding: 12px 16px; margin: 1em 0; box-sizing: border-box;`;
// 标题行
const titleRow = document.createElement('div');
const titleRow = document.createElement('section');
titleRow.className = 'mp-callout-title';
titleRow.style.cssText = `display: flex; align-items: center; gap: 6px; margin-bottom: 8px; font-weight: bold; color: ${colors.title}; font-size: 1em; line-height: 1.5;`;
const iconSpan = document.createElement('span');
iconSpan.className = 'mp-callout-icon';
iconSpan.textContent = colors.icon;
iconSpan.style.cssText = 'font-size: 1.1em;';
const iconSection = document.createElement('section');
iconSection.className = 'mp-callout-icon';
iconSection.textContent = colors.icon;
iconSection.style.cssText = 'display: inline; font-size: 1.1em;';
const titleSpan = document.createElement('span');
titleSpan.className = 'mp-callout-title-text';
titleSpan.textContent = titleText;
const titleSection = document.createElement('section');
titleSection.className = 'mp-callout-title-text';
titleSection.textContent = titleText;
titleSection.style.cssText = 'display: inline;';
titleRow.appendChild(iconSpan);
titleRow.appendChild(titleSpan);
titleRow.appendChild(iconSection);
titleRow.appendChild(titleSection);
newCallout.appendChild(titleRow);
// 内容区域
if (contentHTML.trim()) {
const contentDiv = document.createElement('div');
const contentDiv = document.createElement('section');
contentDiv.className = 'mp-callout-content';
contentDiv.style.cssText = 'color: #4a4a4a; font-size: 0.95em; line-height: 1.7;';
contentDiv.innerHTML = contentHTML;

View file

@ -69,6 +69,38 @@ export class CopyManager {
});
}
/**
* DOM computed style juice
* Obsidian <style> juice
*/
private static applyComputedStylesToCodeBlocks(
sourceElement: HTMLElement,
targetContainer: HTMLElement,
): void {
const sourcePreBlocks = sourceElement.querySelectorAll('pre');
const targetPreBlocks = targetContainer.querySelectorAll('pre');
sourcePreBlocks.forEach((sourcePre, preIndex) => {
const targetPre = targetPreBlocks[preIndex] as HTMLElement | undefined;
if (!targetPre) return;
const sourceCode = sourcePre.querySelector('code');
const targetCode = targetPre.querySelector('code');
if (!sourceCode || !targetCode) return;
const sourceSpans = sourceCode.querySelectorAll('span');
const targetSpans = targetCode.querySelectorAll('span');
sourceSpans.forEach((sourceSpan, spanIndex) => {
const targetSpan = targetSpans[spanIndex] as HTMLElement | undefined;
if (!targetSpan) return;
const color = window.getComputedStyle(sourceSpan).color;
if (color) {
targetSpan.style.color = color;
}
});
});
}
private static async processImages(container: HTMLElement): Promise<void> {
const images = container.querySelectorAll('img');
const imageArray = Array.from(images);
@ -129,12 +161,16 @@ export class CopyManager {
// 4. 使用 juice 将 CSS 内联到每个元素的 style 属性
let inlinedHtml = await this.inlineCSS(rawHtml, themeCSS);
// 5. 清理多余属性data-*、id、class
// 5. 补全 Obsidian 内置的代码高亮样式(不在主题 CSS 中juice 无法内联)
// 从原始预览 DOM 读取 computed style写入克隆体的 inline style
const tempContainer = document.createElement('div');
tempContainer.innerHTML = inlinedHtml;
this.applyComputedStylesToCodeBlocks(previewElement, tempContainer);
// 6. 清理多余属性data-*、id、class
this.cleanupAttributes(tempContainer);
// 6. 处理列表样式
// 7. 处理列表样式
this.processLists(tempContainer);
return tempContainer.innerHTML;

View file

@ -92,22 +92,6 @@
overflow-x: auto;
}
.mp-content-section pre .mp-code-header {
margin-bottom: 0.8em;
display: flex;
gap: 6px;
}
.mp-content-section pre .mp-code-dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.mp-content-section pre .mp-code-dot:nth-child(1) { background-color: #fc8181; }
.mp-content-section pre .mp-code-dot:nth-child(2) { background-color: #f6e05e; }
.mp-content-section pre .mp-code-dot:nth-child(3) { background-color: #68d391; }
.mp-content-section code:not(pre code) {
background: #ebf8ff;
padding: 2px 5px;

View file

@ -95,22 +95,6 @@
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.mp-content-section pre .mp-code-header {
margin-bottom: 1em;
display: flex;
gap: 6px;
}
.mp-content-section pre .mp-code-dot {
width: 12px;
height: 12px;
border-radius: 50%;
}
.mp-content-section pre .mp-code-dot:nth-child(1) { background-color: #ff5f56; }
.mp-content-section pre .mp-code-dot:nth-child(2) { background-color: #ffbd2e; }
.mp-content-section pre .mp-code-dot:nth-child(3) { background-color: #27c93f; }
.mp-content-section pre code {
color: #e2e8f0;
}

View file

@ -106,22 +106,6 @@
overflow-x: auto;
}
.mp-content-section pre .mp-code-header {
margin-bottom: 1em;
display: flex;
gap: 6px;
}
.mp-content-section pre .mp-code-dot {
width: 12px;
height: 12px;
border-radius: 50%;
}
.mp-content-section pre .mp-code-dot:nth-child(1) { background-color: #ff5f56; }
.mp-content-section pre .mp-code-dot:nth-child(2) { background-color: #ffbd2e; }
.mp-content-section pre .mp-code-dot:nth-child(3) { background-color: #27c93f; }
/* 行内代码 */
.mp-content-section code:not(pre code) {
background: #f8f8f8;

View file

@ -94,22 +94,6 @@
overflow-x: auto;
}
.mp-content-section pre .mp-code-header {
margin-bottom: 1em;
display: flex;
gap: 6px;
}
.mp-content-section pre .mp-code-dot {
width: 12px;
height: 12px;
border-radius: 50%;
}
.mp-content-section pre .mp-code-dot:nth-child(1) { background-color: #d2691e; }
.mp-content-section pre .mp-code-dot:nth-child(2) { background-color: #daa520; }
.mp-content-section pre .mp-code-dot:nth-child(3) { background-color: #8fbc8f; }
.mp-content-section code:not(pre code) {
background: #fdf5e6;
padding: 2px 6px;

View file

@ -95,22 +95,6 @@
border: none;
}
.mp-content-section pre .mp-code-header {
margin-bottom: 1em;
display: flex;
gap: 6px;
}
.mp-content-section pre .mp-code-dot {
width: 12px;
height: 12px;
border-radius: 50%;
}
.mp-content-section pre .mp-code-dot:nth-child(1) { background-color: #ff5f56; }
.mp-content-section pre .mp-code-dot:nth-child(2) { background-color: #ffbd2e; }
.mp-content-section pre .mp-code-dot:nth-child(3) { background-color: #27c93f; }
.mp-content-section pre code {
color: #abb2bf;
}

View file

@ -88,22 +88,6 @@
overflow-x: auto;
}
.mp-content-section pre .mp-code-header {
margin-bottom: 0.8em;
display: flex;
gap: 6px;
}
.mp-content-section pre .mp-code-dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.mp-content-section pre .mp-code-dot:nth-child(1) { background-color: #ff5f56; }
.mp-content-section pre .mp-code-dot:nth-child(2) { background-color: #ffbd2e; }
.mp-content-section pre .mp-code-dot:nth-child(3) { background-color: #27c93f; }
.mp-content-section code:not(pre code) {
background: #f5f5f5;
padding: 2px 5px;

View file

@ -92,22 +92,6 @@
overflow-x: auto;
}
.mp-content-section pre .mp-code-header {
margin-bottom: 1em;
display: flex;
gap: 6px;
}
.mp-content-section pre .mp-code-dot {
width: 12px;
height: 12px;
border-radius: 50%;
}
.mp-content-section pre .mp-code-dot:nth-child(1) { background-color: #ff5f56; }
.mp-content-section pre .mp-code-dot:nth-child(2) { background-color: #ffbd2e; }
.mp-content-section pre .mp-code-dot:nth-child(3) { background-color: #27c93f; }
.mp-content-section pre code {
color: #abb2bf;
}

View file

@ -94,22 +94,6 @@
overflow-x: auto;
}
.mp-content-section pre .mp-code-header {
margin-bottom: 1em;
display: flex;
gap: 6px;
}
.mp-content-section pre .mp-code-dot {
width: 12px;
height: 12px;
border-radius: 50%;
}
.mp-content-section pre .mp-code-dot:nth-child(1) { background-color: #ff5f56; }
.mp-content-section pre .mp-code-dot:nth-child(2) { background-color: #ffbd2e; }
.mp-content-section pre .mp-code-dot:nth-child(3) { background-color: #27c93f; }
.mp-content-section pre code {
color: #e5e7eb;
}

View file

@ -20,5 +20,8 @@
},
"include": [
"**/*.ts"
],
"exclude": [
"skills"
]
}