835 字
4 分钟
增强Fuwari的代码块功能
2025-02-26
2025-03-26
前言

这是代替旧方案的新方案
Expressive Code是Astro代码块的一个增强插件

重要

2025年3月26日更新

目前Hasenpfote大佬已经将该功能提交到Fwrari的 PR 里(还没有合并到主分支)
可以优先参考他的代码

feat: improve code block feature with Expressive Code

一、注释Fuwari自带的代码块样式#

由于Fuwari自带的代码块样式会干扰到插件的样式,所以先要把以下代码注释掉

src\components\misc\Markdown.astro
<!-- <script>
const observer = new MutationObserver(addPreCopyButton);
observer.observe(document.body, { childList: true, subtree: true });
document.addEventListener("DOMContentLoaded", addPreCopyButton);
function addPreCopyButton() {
observer.disconnect();
let codeBlocks = Array.from(document.querySelectorAll("pre"));
for (let codeBlock of codeBlocks) {
if (codeBlock.parentElement?.nodeName === "DIV" && codeBlock.parentElement?.classList.contains("code-block")) continue
let wrapper = document.createElement("div");
wrapper.className = "relative code-block";
let copyButton = document.createElement("button");
copyButton.className = "copy-btn btn-regular-dark absolute active:scale-90 h-8 w-8 top-2 right-2 opacity-75 text-sm p-1.5 rounded-lg transition-all ease-in-out";
codeBlock.setAttribute("tabindex", "0");
if (codeBlock.parentNode) {
codeBlock.parentNode.insertBefore(wrapper, codeBlock);
}
let copyIcon = `<svg class="copy-btn-icon copy-icon" xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px"><path d="M368.37-237.37q-34.48 0-58.74-24.26-24.26-24.26-24.26-58.74v-474.26q0-34.48 24.26-58.74 24.26-24.26 58.74-24.26h378.26q34.48 0 58.74 24.26 24.26 24.26 24.26 58.74v474.26q0 34.48-24.26 58.74-24.26 24.26-58.74 24.26H368.37Zm0-83h378.26v-474.26H368.37v474.26Zm-155 238q-34.48 0-58.74-24.26-24.26-24.26-24.26-58.74v-515.76q0-17.45 11.96-29.48 11.97-12.02 29.33-12.02t29.54 12.02q12.17 12.03 12.17 29.48v515.76h419.76q17.45 0 29.48 11.96 12.02 11.97 12.02 29.33t-12.02 29.54q-12.03 12.17-29.48 12.17H213.37Zm155-238v-474.26 474.26Z"/></svg>`
let successIcon = `<svg class="copy-btn-icon success-icon" xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px"><path d="m389-377.13 294.7-294.7q12.58-12.67 29.52-12.67 16.93 0 29.61 12.67 12.67 12.68 12.67 29.53 0 16.86-12.28 29.14L419.07-288.41q-12.59 12.67-29.52 12.67-16.94 0-29.62-12.67L217.41-430.93q-12.67-12.68-12.79-29.45-.12-16.77 12.55-29.45 12.68-12.67 29.62-12.67 16.93 0 29.28 12.67L389-377.13Z"/></svg>`
copyButton.innerHTML = `<div>${copyIcon} ${successIcon}</div>
`
wrapper.appendChild(codeBlock);
wrapper.appendChild(copyButton);
let timeout: ReturnType<typeof setTimeout>;
copyButton.addEventListener("click", async () => {
if (timeout) {
clearTimeout(timeout);
}
let text = codeBlock?.querySelector("code")?.innerText;
if (text === undefined) return;
await navigator.clipboard.writeText(text);
copyButton.classList.add("success");
timeout = setTimeout(() => {
copyButton.classList.remove("success");
}, 1000);
});
}
observer.observe(document.body, { childList: true, subtree: true });
}
</script> -->
src\styles\markdown.css
/* pre {
@apply bg-[var(--codeblock-bg)] !important;
@apply rounded-xl px-5;
code {
@apply bg-transparent text-inherit text-sm p-0;
::selection {
@apply bg-[var(--codeblock-selection)];
}
}
} */
src\layouts\Layout.astro
function initCustomScrollbar() {
18 collapsed lines
const bodyElement = document.querySelector('body');
if (!bodyElement) return;
OverlayScrollbars(
// docs say that a initialization to the body element would affect native functionality like window.scrollTo
// but just leave it here for now
{
target: bodyElement,
cancel: {
nativeScrollbarsOverlaid: true, // don't initialize the overlay scrollbar if there is a native one
}
}, {
scrollbars: {
theme: 'scrollbar-base scrollbar-auto py-1',
autoHide: 'move',
autoHideDelay: 500,
autoHideSuspend: false,
},
});
// const preElements = document.querySelectorAll('pre');
// preElements.forEach((ele) => {
// OverlayScrollbars(ele, {
// scrollbars: {
// theme: 'scrollbar-base scrollbar-dark px-2',
// autoHide: 'leave',
// autoHideDelay: 500,
// autoHideSuspend: false
// }
// });
// });
const katexElements = document.querySelectorAll('.katex-display') as NodeListOf<HTMLElement>;
katexElements.forEach((ele) => {
OverlayScrollbars(ele, {
scrollbars: {
theme: 'scrollbar-base scrollbar-auto py-1',
}
});
});
}

二、导入Expressive Code#

直接参考 https://expressive-code.com/installation/#astro

三、添加代码块的黑暗模式#

官方主题列表:https://expressive-code.com/guides/themes/#available-themes

  1. 在配置文件中添加双主题
astro.config.mjs
export default defineConfig({
// ...
integrations: [
// ...
expressiveCode({
themes: ["catppuccin-frappe", "light-plus"],
})
]
})
  1. 修改LightDarkSwitch.svelte文件中的onMountswitchScheme方法
src\components\LightDarkSwitch.svelte
onMount(() => {
mode = getStoredTheme()
if (mode === DARK_MODE) {
document.documentElement.setAttribute("data-theme", "catppuccin-frappe");
} else {
document.documentElement.setAttribute("data-theme", "light-plus");
}
const darkModePreference = window.matchMedia('(prefers-color-scheme: dark)')
const changeThemeWhenSchemeChanged: Parameters<
typeof darkModePreference.addEventListener<'change'>
>[1] = e => {
applyThemeToDocument(mode)
}
darkModePreference.addEventListener('change', changeThemeWhenSchemeChanged)
return () => {
darkModePreference.removeEventListener(
'change',
changeThemeWhenSchemeChanged,
)
}
})
src\components\LightDarkSwitch.svelte
function switchScheme(newMode: LIGHT_DARK_MODE) {
mode = newMode
setTheme(newMode)
if (mode === DARK_MODE) {
document.documentElement.setAttribute("data-theme", "catppuccin-frappe");
} else {
document.documentElement.setAttribute("data-theme", "light-plus");
}
}
NOTE

需要重启项目才能应用

四、部分功能测试#

4.1 代码块标题#

```js title="demo.js"
function demo() {
}
```
demo.js
function demo() {
}

4.2 行标记#

```text {1, 5-6} ins={2-3} del={8}
第1行
第2行
第3行
第4行
第5行
第6行
第7行
第8行
```
第1行
第2行
第3行
第4行
第5行
第6行
第7行
第8行

4.3 文本标记#

```text "c c++" ins="csharp" del="ruby"
c c++ java
javascript python csharp
rust ruby golang
```
c c++ java
javascript python csharp
rust ruby golang

4.4 代码折叠#

NOTE

需要额外安装 https://expressive-code.com/plugins/collapsible-sections/

```csharp collapse={4-5, 6-8} title="Demo.cs"
public int Demo()
{
var i = 1 + 1;
//折叠区域
//折叠区域
//折叠区域
return i;
}
```
Demo.cs
public int Demo()
{
var i = 1 + 1;
2 collapsed lines
//折叠区域
3 collapsed lines
//折叠区域
//折叠区域
return i;
}

4.5 显示代码行数#

NOTE

需要额外安装 https://expressive-code.com/plugins/line-numbers/

```text startLineNumber=5
第5行
第6行
```
第5行
第6行
增强Fuwari的代码块功能
https://ikamusume7.org/posts/frontend/code_block_ex/
作者
伊卡
发布于
2025-02-26
许可协议
CC BY-NC-SA 4.0