feat: 优化 Markdown 代码块高亮样式及渲染逻辑

This commit is contained in:
seahi committed 2026-08-05 14:19:45 +08:00
1 parent d4f113c77b
commit 81dd62c3df
2 files changed
+50 -12

No files matched your search

+14
View File
@@ -1,3 +1,4 @@
@import "highlight.js/styles/github.css";
:root { :root {
--bg-color: #ffffff; --bg-color: #ffffff;
@@ -601,6 +602,19 @@ body.single-col .post-list {
color: inherit; color: inherit;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center;
line-height: 1;
}
.code-header-actions button span {
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
}
.code-header-actions svg {
display: block;
} }
/* 操作按钮:默认隐藏,展开时显示 */ /* 操作按钮:默认隐藏,展开时显示 */
+36 -12
View File
@@ -15,16 +15,40 @@
renderer: { renderer: {
code(token) { code(token) {
const code = token.text; const code = token.text;
const lang = (token.lang || '').split(' ')[0]; const rawLang = (token.lang || '').trim().toLowerCase().split(' ')[0];
const language = hljs.getLanguage(lang) ? lang : 'plaintext'; let highlighted: string;
const highlighted = hljs.highlight(code, { language }).value; let language = 'plaintext';
const title = lang || 'text';
if (rawLang && hljs.getLanguage(rawLang)) {
language = rawLang;
highlighted = hljs.highlight(code, { language }).value;
} else {
try {
const auto = hljs.highlightAuto(code);
highlighted = auto.value || code;
if (auto.language) language = auto.language;
} catch {
highlighted = code;
}
}
const title = rawLang || language || 'text';
const encodedCode = encodeURIComponent(code); const encodedCode = encodeURIComponent(code);
const wrappedLines = highlighted const lines = highlighted.split('\n');
.split('\n') const openSpans: string[] = [];
.map((line) => `<span class="code-line">${line}</span>`) const wrappedLines = lines.map((line) => {
.join('\n'); const prefix = openSpans.join('');
const matches = line.matchAll(/<span [^>]+>|<\/span>/g);
for (const m of matches) {
if (m[0] === '</span>') {
openSpans.pop();
} else {
openSpans.push(m[0]);
}
}
const suffix = '</span>'.repeat(openSpans.length);
return `<span class="code-line">${prefix}${line}${suffix}</span>`;
}).join('\n');
const chevronRight = '<svg class="chevron-icon" viewBox="0 0 320 512" fill="currentColor" width="0.625em" height="1em"><path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"></path></svg>'; const chevronRight = '<svg class="chevron-icon" viewBox="0 0 320 512" fill="currentColor" width="0.625em" height="1em"><path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"></path></svg>';
const listOlIcon = '<svg viewBox="0 0 512 512" fill="currentColor" width="1em" height="1em"><path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z"></path></svg>'; const listOlIcon = '<svg viewBox="0 0 512 512" fill="currentColor" width="1em" height="1em"><path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z"></path></svg>';
@@ -43,8 +67,8 @@
<button class="action-icon line-number-button is-active" title="Toggle line numbers">${listOlIcon}</button> <button class="action-icon line-number-button is-active" title="Toggle line numbers">${listOlIcon}</button>
<button class="action-icon wrap-code-button" title="Toggle code wrap">${barsIcon}</button> <button class="action-icon wrap-code-button" title="Toggle code wrap">${barsIcon}</button>
<button class="action-icon copy-code-button" title="Copy code" data-code="${encodedCode}"> <button class="action-icon copy-code-button" title="Copy code" data-code="${encodedCode}">
<span class="copy-icon" style="display:block">${copyIcon}</span> <span class="copy-icon" style="display:flex; align-items:center; justify-content:center;">${copyIcon}</span>
<span class="check-icon" style="display:none">${checkIcon}</span> <span class="check-icon" style="display:none; align-items:center; justify-content:center;">${checkIcon}</span>
</button> </button>
<span class="ellipsis-icon" aria-hidden="true">${ellipsisIcon}</span> <span class="ellipsis-icon" aria-hidden="true">${ellipsisIcon}</span>
</div> </div>
@@ -174,9 +198,9 @@
if (codeEl) { if (codeEl) {
navigator.clipboard.writeText(codeEl.textContent || ''); navigator.clipboard.writeText(codeEl.textContent || '');
if (copyIconEl) copyIconEl.style.display = 'none'; if (copyIconEl) copyIconEl.style.display = 'none';
if (checkIconEl) checkIconEl.style.display = 'block'; if (checkIconEl) checkIconEl.style.display = 'flex';
setTimeout(() => { setTimeout(() => {
if (copyIconEl) copyIconEl.style.display = 'block'; if (copyIconEl) copyIconEl.style.display = 'flex';
if (checkIconEl) checkIconEl.style.display = 'none'; if (checkIconEl) checkIconEl.style.display = 'none';
}, 3000); }, 3000);
} }