1153 字
6 分钟
增强Fuwari的代码块功能
🤖AI 摘要

本文介绍如何为 Fuwari 博客引入 astro-expressive-code,实现更美观、强大的代码块展示效果,包括代码高亮、行号显示、折叠功能、主题切换等,提升阅读体验和代码可读性。

增强Fuwari的代码块功能


一、注释原代码块样式#

src\components\misc\Markdown.astro
---
import "@fontsource-variable/jetbrains-mono";
import "@fontsource-variable/jetbrains-mono/wght-italic.css";
import path from 'node:path'
import { getImage } from 'astro:assets'
import { parseHTML } from 'linkedom'
interface Props {
class: string;
basePath?: string
}
const { class: className, basePath = '/' } = Astro.props
/*
*通常,“src”目录下的相对路径由Astro处理。
*然而,插件无法处理的路径可能会出现在这里,并且需要单独处理。
*/
const html = await Astro.slots.render('default')
const { document } = parseHTML(html)
await (async () => {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const modules: Record<string, any> = import.meta.glob(
'../../**/*.{jpeg,jpg,png,tiff,webp,gif,svg,avif}',
)
const re = /^(?![a-zA-Z]+:\/|\/)/
for (const img of document.querySelectorAll('img')) {
const src = img.getAttribute('src')
if (!src || !re.test(src)) continue
const normalizedPath = path
.normalize(path.join('../../', basePath, src))
.replaceAll('\\', '/')
const moduleLoader = modules[normalizedPath]
if (!moduleLoader) continue
try {
const module = await moduleLoader()
const result = await getImage({ src: module.default })
img.setAttribute('src', result.src)
} catch (error) {
console.warn(
`Skipping image "${normalizedPath}" due to processing error:`,
error,
)
}
}
})()
---
<div data-pagefind-body class={`prose dark:prose-invert prose-base !max-w-none custom-md ${className}`}>
<!--<div class="prose dark:prose-invert max-w-none custom-md">-->
<!--<div class="max-w-none custom-md">-->
<slot/>
</div>
<!--禁用原代码块样式-->
<!--<script>-->
<!-- const observer = new MutationObserver(addPreCopyButton);-->
<!-- observer.observe(document.body, { childList: true, subtree: true });-->
<!-- 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',
}
});
});
}

二、引入#

pnpm astro add astro-expressive-code

三、配置文件中添加双主题#

主题:https://expressive-code.com/guides/themes/#available-themes

  1. 在配置文件中添加双主题
astro.config.mjs
export default defineConfig({
// ...
integrations: [
// ...
expressiveCode({
themes: ["catppuccin-frappe", "catppuccin-latte"],
})
]
})
  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");
}
}

四、使用格式方法#

标题#

标题
```js title="标题"
console.log("Line 1");
console.log("Line 2");
let a = 1;
let b = 2;

** 高亮指定行**#

```js {2,4-5} ins={3,7}
console.log("Line 1");
console.log("Line 2");
let a = 1;
let b = 2;
let c = 3;
let d = 4;
let e = 5;
let f = 6;
let g = 7;
console.log(a + b);
let h = 4;
let i = 5;
let j = 6;
let k = 7;
let l = 7;

** 新增/删除指定行**#

```js ins={3,7} del={6}
console.log("Line 1");
console.log("Line 2");
let a = 1;
let b = 2;
let c = 3;
let d = 4;
let e = 5;
let f = 6;
let g = 7;
console.log(a + b);
let h = 4;
let i = 5;
let j = 6;
let k = 7;
let l = 7;

高亮块#

```js "a + b" del="let g = 7"
//{2,4-5}高亮 ins={3,7}新增 del={6}删除
console.log("Line 1");
console.log("Line 2");
let a = 1;
let b = 2;
let c = 3;
let d = 4;
let e = 5;
let f = 6;
let g = 7;
console.log(a + b);
let h = 4;
let i = 5;
let j = 6;
let k = 7;
let l = 7;

折叠代码/代码行数#

需要引入#

pnpm add @expressive-code/plugin-line-numbers //显示行数
pnpm add @expressive-code/plugin-collapsible-sections //折叠
import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers'
import { pluginCollapsibleSections } from '@expressive-code/plugin-collapsible-sections'
...
expressiveCode({
themes: ["catppuccin-frappe", "catppuccin-latte"],
plugins: [pluginLineNumbers(),pluginCollapsibleSections()],
})],

例:#

```js collapse={8-13, 15-16} startLineNumber=7
//{2,4-5}高亮 ins={3,7}新增 del={6}删除
console.log("Line 1");
console.log("Line 2");
let a = 1;
let b = 2;
let c = 3;
let d = 4;
let e = 5;
let f = 6;
let g = 7;
console.log(a + b);
let h = 4;
let i = 5;
let j = 6;
let k = 7;
let l = 7;
增强Fuwari的代码块功能
https://fuwari.vercel.app/posts/bjk/fuwari代码块主题/
作者
Ke.ke
发布于
2024-04-14
许可协议
CC BY-NC-SA 4.0