mirror of
https://github.com/joeytoday/obsidian-mp-publisher.git
synced 2026-07-22 06:53:08 +00:00
fix: 修复代码块样式复制到公众号后丢失 (#27)
- 修复代码高亮丢失:从预览 DOM 读取 computed style 补全 Obsidian 内置高亮颜色 - 修复仿 macOS 窗口圆点不显示:使用 section 标签 + inline style 替代 CSS 伪选择器 - 统一使用 section 标签:列表标记、Callout 标题/内容等元素改为 section,提升公众号兼容性 - 移除主题 CSS 中的圆点样式规则(已内联化,不再需要) - tsconfig 排除 skills 目录避免构建报错
This commit is contained in:
parent
1c7a37b70f
commit
d5adbef870
13 changed files with 87 additions and 154 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -23,3 +23,7 @@ Thumbs.db
|
|||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# other
|
||||
dist/
|
||||
skills/
|
||||
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -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 修复
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,5 +20,8 @@
|
|||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"skills"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue