feat: introduce Expressive Code for code block rendering (#476)

* feat: improve code block feature with Expressive Code

* docs: add example Markdown file for visual review

* feat: improve styles of Expressive Code

* fix: errors from the merging

* chore: formatting

* chore: update config

* chore: update dependencies

* fix: fix build error

* fix: minor fixes

* fix: style tweaks

* docs: update example post

* fix: isolate copy button timeouts to avoid cross-button interference

---------

Co-authored-by: Hasenpfote <Hasenpfote36@gmail.com>
This commit is contained in:
Saica
2025-06-03 22:41:36 +08:00
committed by GitHub
parent e640964e5e
commit ee48c2f09d
15 changed files with 1384 additions and 648 deletions

View File

@@ -1,7 +1,10 @@
import sitemap from "@astrojs/sitemap"; import sitemap from "@astrojs/sitemap";
import svelte from "@astrojs/svelte"; import svelte from "@astrojs/svelte";
import tailwind from "@astrojs/tailwind"; import tailwind from "@astrojs/tailwind";
import { pluginCollapsibleSections } from "@expressive-code/plugin-collapsible-sections";
import { pluginLineNumbers } from "@expressive-code/plugin-line-numbers";
import swup from "@swup/astro"; import swup from "@swup/astro";
import expressiveCode from "astro-expressive-code";
import icon from "astro-icon"; import icon from "astro-icon";
import { defineConfig } from "astro/config"; import { defineConfig } from "astro/config";
import rehypeAutolinkHeadings from "rehype-autolink-headings"; import rehypeAutolinkHeadings from "rehype-autolink-headings";
@@ -12,11 +15,14 @@ import remarkDirective from "remark-directive"; /* Handle directives */
import remarkGithubAdmonitionsToDirectives from "remark-github-admonitions-to-directives"; import remarkGithubAdmonitionsToDirectives from "remark-github-admonitions-to-directives";
import remarkMath from "remark-math"; import remarkMath from "remark-math";
import remarkSectionize from "remark-sectionize"; import remarkSectionize from "remark-sectionize";
import { expressiveCodeConfig } from "./src/config.ts";
import { pluginLanguageBadge } from "./src/plugins/expressive-code/language-badge.ts";
import { AdmonitionComponent } from "./src/plugins/rehype-component-admonition.mjs"; import { AdmonitionComponent } from "./src/plugins/rehype-component-admonition.mjs";
import { GithubCardComponent } from "./src/plugins/rehype-component-github-card.mjs"; import { GithubCardComponent } from "./src/plugins/rehype-component-github-card.mjs";
import { parseDirectiveNode } from "./src/plugins/remark-directive-rehype.js"; import { parseDirectiveNode } from "./src/plugins/remark-directive-rehype.js";
import { remarkExcerpt } from "./src/plugins/remark-excerpt.js"; import { remarkExcerpt } from "./src/plugins/remark-excerpt.js";
import { remarkReadingTime } from "./src/plugins/remark-reading-time.mjs"; import { remarkReadingTime } from "./src/plugins/remark-reading-time.mjs";
import { pluginCustomCopyButton } from "./src/plugins/expressive-code/custom-copy-button.js";
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
@@ -49,7 +55,51 @@ export default defineConfig({
"fa6-solid": ["*"], "fa6-solid": ["*"],
}, },
}), }),
svelte(), expressiveCode({
themes: [expressiveCodeConfig.theme, expressiveCodeConfig.theme],
plugins: [
pluginCollapsibleSections(),
pluginLineNumbers(),
pluginLanguageBadge(),
pluginCustomCopyButton()
],
defaultProps: {
wrap: true,
overridesByLang: {
'shellsession': {
showLineNumbers: false,
},
},
},
styleOverrides: {
codeBackground: "var(--codeblock-bg)",
borderRadius: "0.75rem",
borderColor: "none",
codeFontSize: "0.875rem",
codeFontFamily: "'JetBrains Mono Variable', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
codeLineHeight: "1.5rem",
frames: {
editorBackground: "var(--codeblock-bg)",
terminalBackground: "var(--codeblock-bg)",
terminalTitlebarBackground: "var(--codeblock-topbar-bg)",
editorTabBarBackground: "var(--codeblock-topbar-bg)",
editorActiveTabBackground: "none",
editorActiveTabIndicatorBottomColor: "var(--primary)",
editorActiveTabIndicatorTopColor: "none",
editorTabBarBorderBottomColor: "var(--codeblock-topbar-bg)",
terminalTitlebarBorderBottomColor: "none"
},
textMarkers: {
delHue: 0,
insHue: 180,
markHue: 250
}
},
frames: {
showCopyToClipboardButton: false,
}
}),
svelte(),
sitemap(), sitemap(),
], ],
markdown: { markdown: {

View File

@@ -21,6 +21,9 @@
"@astrojs/sitemap": "^3.4.0", "@astrojs/sitemap": "^3.4.0",
"@astrojs/svelte": "7.1.0", "@astrojs/svelte": "7.1.0",
"@astrojs/tailwind": "^6.0.2", "@astrojs/tailwind": "^6.0.2",
"@expressive-code/core": "^0.41.2",
"@expressive-code/plugin-collapsible-sections": "^0.41.2",
"@expressive-code/plugin-line-numbers": "^0.41.2",
"@fontsource-variable/jetbrains-mono": "^5.2.5", "@fontsource-variable/jetbrains-mono": "^5.2.5",
"@fontsource/roboto": "^5.2.5", "@fontsource/roboto": "^5.2.5",
"@iconify-json/fa6-brands": "^1.2.5", "@iconify-json/fa6-brands": "^1.2.5",
@@ -31,6 +34,7 @@
"@swup/astro": "^1.6.0", "@swup/astro": "^1.6.0",
"@tailwindcss/typography": "^0.5.16", "@tailwindcss/typography": "^0.5.16",
"astro": "5.8.1", "astro": "5.8.1",
"astro-expressive-code": "^0.41.2",
"astro-icon": "^1.1.5", "astro-icon": "^1.1.5",
"hastscript": "^9.0.1", "hastscript": "^9.0.1",
"katex": "^0.16.22", "katex": "^0.16.22",
@@ -61,6 +65,7 @@
"@astrojs/ts-plugin": "^1.10.4", "@astrojs/ts-plugin": "^1.10.4",
"@biomejs/biome": "1.9.4", "@biomejs/biome": "1.9.4",
"@rollup/plugin-yaml": "^4.1.2", "@rollup/plugin-yaml": "^4.1.2",
"@types/hast": "^3.0.4",
"@types/markdown-it": "^14.1.2", "@types/markdown-it": "^14.1.2",
"@types/mdast": "^4.0.4", "@types/mdast": "^4.0.4",
"@types/sanitize-html": "^2.16.0", "@types/sanitize-html": "^2.16.0",

1361
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,51 +14,30 @@ const className = Astro.props.class;
</div> </div>
<script> <script>
const observer = new MutationObserver(addPreCopyButton); document.addEventListener("click", function (e: MouseEvent) {
observer.observe(document.body, { childList: true, subtree: true }); const target = e.target as Element | null;
if (target && target.classList.contains("copy-btn")) {
function addPreCopyButton() { const preEle = target.closest("pre");
observer.disconnect(); const codeEle = preEle?.querySelector("code");
const code = Array.from(codeEle?.querySelectorAll(".code:not(summary *)") ?? [])
let codeBlocks = Array.from(document.querySelectorAll("pre")); .map(el => el.textContent)
.map(t => t === "\n" ? "" : t)
.join("\n");
navigator.clipboard.writeText(code);
for (let codeBlock of codeBlocks) { const timeoutId = target.getAttribute("data-timeout-id");
if (codeBlock.parentElement?.nodeName === "DIV" && codeBlock.parentElement?.classList.contains("code-block")) continue if (timeoutId) {
clearTimeout(parseInt(timeoutId));
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; target.classList.add("success");
await navigator.clipboard.writeText(text);
copyButton.classList.add("success"); // 设置新的timeout并保存ID到按钮的自定义属性中
timeout = setTimeout(() => { const newTimeoutId = setTimeout(() => {
copyButton.classList.remove("success"); target.classList.remove("success");
}, 1000); }, 1000);
});
target.setAttribute("data-timeout-id", newTimeoutId.toString());
} }
});
observer.observe(document.body, { childList: true, subtree: true });
}
</script> </script>

View File

@@ -1,4 +1,5 @@
import type { import type {
ExpressiveCodeConfig,
LicenseConfig, LicenseConfig,
NavBarConfig, NavBarConfig,
ProfileConfig, ProfileConfig,
@@ -81,3 +82,9 @@ export const licenseConfig: LicenseConfig = {
name: "CC BY-NC-SA 4.0", name: "CC BY-NC-SA 4.0",
url: "https://creativecommons.org/licenses/by-nc-sa/4.0/", url: "https://creativecommons.org/licenses/by-nc-sa/4.0/",
}; };
export const expressiveCodeConfig: ExpressiveCodeConfig = {
// Note: Some styles (such as background color) are being overridden, see the astro.config.mjs file.
// Please select a dark theme, as this blog theme currently only supports dark background color
theme: "github-dark",
};

View File

@@ -0,0 +1,311 @@
---
title: Expressive Code Example
published: 2024-04-10
description: How code blocks look in Markdown using Expressive Code.
tags: [Markdown, Blogging, Demo]
category: Examples
draft: false
---
Here, we'll explore how code blocks look using [Expressive Code](https://expressive-code.com/). The provided examples are based on the official documentation, which you can refer to for further details.
## Expressive Code
### Syntax Highlighting
[Syntax Highlighting](https://expressive-code.com/key-features/syntax-highlighting/)
#### Regular syntax highlighting
```js
console.log('This code is syntax highlighted!')
```
#### Rendering ANSI escape sequences
```ansi
ANSI colors:
- Regular: Red Green Yellow Blue Magenta Cyan
- Bold: Red Green Yellow Blue Magenta Cyan
- Dimmed: Red Green Yellow Blue Magenta Cyan
256 colors (showing colors 160-177):
160 161 162 163 164 165
166 167 168 169 170 171
172 173 174 175 176 177
Full RGB colors:
ForestGreen - RGB(34, 139, 34)
Text formatting: Bold Dimmed Italic Underline
```
### Editor & Terminal Frames
[Editor & Terminal Frames](https://expressive-code.com/key-features/frames/)
#### Code editor frames
```js title="my-test-file.js"
console.log('Title attribute example')
```
---
```html
<!-- src/content/index.html -->
<div>File name comment example</div>
```
#### Terminal frames
```bash
echo "This terminal frame has no title"
```
---
```powershell title="PowerShell terminal example"
Write-Output "This one has a title!"
```
#### Overriding frame types
```sh frame="none"
echo "Look ma, no frame!"
```
---
```ps frame="code" title="PowerShell Profile.ps1"
# Without overriding, this would be a terminal frame
function Watch-Tail { Get-Content -Tail 20 -Wait $args }
New-Alias tail Watch-Tail
```
### Text & Line Markers
[Text & Line Markers](https://expressive-code.com/key-features/text-markers/)
#### Marking full lines & line ranges
```js {1, 4, 7-8}
// Line 1 - targeted by line number
// Line 2
// Line 3
// Line 4 - targeted by line number
// Line 5
// Line 6
// Line 7 - targeted by range "7-8"
// Line 8 - targeted by range "7-8"
```
#### Selecting line marker types (mark, ins, del)
```js title="line-markers.js" del={2} ins={3-4} {6}
function demo() {
console.log('this line is marked as deleted')
// This line and the next one are marked as inserted
console.log('this is the second inserted line')
return 'this line uses the neutral default marker type'
}
```
#### Adding labels to line markers
```jsx {"1":5} del={"2":7-8} ins={"3":10-12}
// labeled-line-markers.jsx
<button
role="button"
{...props}
value={value}
className={buttonClassName}
disabled={disabled}
active={active}
>
{children &&
!active &&
(typeof children === 'string' ? <span>{children}</span> : children)}
</button>
```
#### Adding long labels on their own lines
```jsx {"1. Provide the value prop here:":5-6} del={"2. Remove the disabled and active states:":8-10} ins={"3. Add this to render the children inside the button:":12-15}
// labeled-line-markers.jsx
<button
role="button"
{...props}
value={value}
className={buttonClassName}
disabled={disabled}
active={active}
>
{children &&
!active &&
(typeof children === 'string' ? <span>{children}</span> : children)}
</button>
```
#### Using diff-like syntax
```diff
+this line will be marked as inserted
-this line will be marked as deleted
this is a regular line
```
---
```diff
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+this is an actual diff file
-all contents will remain unmodified
no whitespace will be removed either
```
#### Combining syntax highlighting with diff-like syntax
```diff lang="js"
function thisIsJavaScript() {
// This entire block gets highlighted as JavaScript,
// and we can still add diff markers to it!
- console.log('Old code to be removed')
+ console.log('New and shiny code!')
}
```
#### Marking individual text inside lines
```js "given text"
function demo() {
// Mark any given text inside lines
return 'Multiple matches of the given text are supported';
}
```
#### Regular expressions
```ts /ye[sp]/
console.log('The words yes and yep will be marked.')
```
#### Escaping forward slashes
```sh /\/ho.*\//
echo "Test" > /home/test.txt
```
#### Selecting inline marker types (mark, ins, del)
```js "return true;" ins="inserted" del="deleted"
function demo() {
console.log('These are inserted and deleted marker types');
// The return statement uses the default marker type
return true;
}
```
### Word Wrap
[Word Wrap](https://expressive-code.com/key-features/word-wrap/)
#### Configuring word wrap per block
```js wrap
// Example with wrap
function getLongString() {
return 'This is a very long string that will most probably not fit into the available space unless the container is extremely wide'
}
```
---
```js wrap=false
// Example with wrap=false
function getLongString() {
return 'This is a very long string that will most probably not fit into the available space unless the container is extremely wide'
}
```
#### Configuring indentation of wrapped lines
```js wrap preserveIndent
// Example with preserveIndent (enabled by default)
function getLongString() {
return 'This is a very long string that will most probably not fit into the available space unless the container is extremely wide'
}
```
---
```js wrap preserveIndent=false
// Example with preserveIndent=false
function getLongString() {
return 'This is a very long string that will most probably not fit into the available space unless the container is extremely wide'
}
```
## Collapsible Sections
[Collapsible Sections](https://expressive-code.com/plugins/collapsible-sections/)
```js collapse={1-5, 12-14, 21-24}
// All this boilerplate setup code will be collapsed
import { someBoilerplateEngine } from '@example/some-boilerplate'
import { evenMoreBoilerplate } from '@example/even-more-boilerplate'
const engine = someBoilerplateEngine(evenMoreBoilerplate())
// This part of the code will be visible by default
engine.doSomething(1, 2, 3, calcFn)
function calcFn() {
// You can have multiple collapsed sections
const a = 1
const b = 2
const c = a + b
// This will remain visible
console.log(`Calculation result: ${a} + ${b} = ${c}`)
return c
}
// All this code until the end of the block will be collapsed again
engine.closeConnection()
engine.freeMemory()
engine.shutdown({ reason: 'End of example boilerplate code' })
```
## Line Numbers
[Line Numbers](https://expressive-code.com/plugins/line-numbers/)
### Displaying line numbers per block
```js showLineNumbers
// This code block will show line numbers
console.log('Greetings from line 2!')
console.log('I am on line 3')
```
---
```js showLineNumbers=false
// Line numbers are disabled for this block
console.log('Hello?')
console.log('Sorry, do you know what line I am on?')
```
### Changing the starting line number
```js showLineNumbers startLineNumber=5
console.log('Greetings from line 5!')
console.log('I am on line 6')
```

View File

@@ -305,17 +305,6 @@ function initCustomScrollbar() {
autoHideSuspend: false, 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>; const katexElements = document.querySelectorAll('.katex-display') as NodeListOf<HTMLElement>;
@@ -328,14 +317,14 @@ function initCustomScrollbar() {
const processKatexElement = (element: HTMLElement) => { const processKatexElement = (element: HTMLElement) => {
if (!element.parentNode) return; if (!element.parentNode) return;
if (element.hasAttribute('data-scrollbar-initialized')) return; if (element.hasAttribute('data-scrollbar-initialized')) return;
const container = document.createElement('div'); const container = document.createElement('div');
container.className = 'katex-display-container'; container.className = 'katex-display-container';
container.setAttribute('aria-label', 'scrollable container for formulas'); container.setAttribute('aria-label', 'scrollable container for formulas');
element.parentNode.insertBefore(container, element); element.parentNode.insertBefore(container, element);
container.appendChild(element); container.appendChild(element);
OverlayScrollbars(container, { OverlayScrollbars(container, {
scrollbars: { scrollbars: {
theme: 'scrollbar-base scrollbar-auto', theme: 'scrollbar-base scrollbar-auto',
@@ -344,7 +333,7 @@ function initCustomScrollbar() {
autoHideSuspend: false autoHideSuspend: false
} }
}); });
element.setAttribute('data-scrollbar-initialized', 'true'); element.setAttribute('data-scrollbar-initialized', 'true');
}; };
@@ -364,13 +353,13 @@ function initCustomScrollbar() {
function showBanner() { function showBanner() {
if (!siteConfig.banner.enable) return; if (!siteConfig.banner.enable) return;
const banner = document.getElementById('banner'); const banner = document.getElementById('banner');
if (!banner) { if (!banner) {
console.error('Banner element not found'); console.error('Banner element not found');
return; return;
} }
banner.classList.remove('opacity-0', 'scale-105'); banner.classList.remove('opacity-0', 'scale-105');
} }

View File

@@ -0,0 +1,90 @@
import { definePlugin } from "@expressive-code/core";
import type { Element } from "hast";
export function pluginCustomCopyButton() {
return definePlugin({
name: "Custom Copy Button",
hooks: {
postprocessRenderedBlock: (context) => {
function traverse(node: Element) {
if (node.type === "element" && node.tagName === "pre") {
processCodeBlock(node);
return;
}
if (node.children) {
for (const child of node.children) {
if (child.type === "element") traverse(child);
}
}
}
function processCodeBlock(node: Element) {
const copyButton = {
type: "element" as const,
tagName: "button",
properties: {
className: ["copy-btn"],
"aria-label": "Copy code",
},
children: [
{
type: "element" as const,
tagName: "div",
properties: {
className: ["copy-btn-icon"],
},
children: [
{
type: "element" as const,
tagName: "svg",
properties: {
viewBox: "0 -960 960 960",
xmlns: "http://www.w3.org/2000/svg",
className: ["copy-btn-icon", "copy-icon"],
},
children: [
{
type: "element" as const,
tagName: "path",
properties: {
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",
},
children: [],
},
],
},
{
type: "element" as const,
tagName: "svg",
properties: {
viewBox: "0 -960 960 960",
xmlns: "http://www.w3.org/2000/svg",
className: ["copy-btn-icon", "success-icon"],
},
children: [
{
type: "element" as const,
tagName: "path",
properties: {
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",
},
children: [],
},
],
},
],
},
],
} as Element;
if (!node.children) {
node.children = [];
}
node.children.push(copyButton);
}
traverse(context.renderData.blockAst);
},
},
});
}

View File

@@ -0,0 +1,48 @@
/**
* Based on the discussion at https://github.com/expressive-code/expressive-code/issues/153#issuecomment-2282218684
*/
import { definePlugin } from "@expressive-code/core";
export function pluginLanguageBadge() {
return definePlugin({
name: "Language Badge",
baseStyles: ({ cssVar }) => `
[data-language]::before {
position: absolute;
z-index: 2;
right: 0.5rem;
top: 0.5rem;
padding: 0.1rem 0.5rem;
content: attr(data-language);
font-size: 0.75rem;
font-weight: bold;
text-transform: uppercase;
color: oklch(0.75 0.1 var(--hue));
background: oklch(0.33 0.035 var(--hue));
border-radius: 0.5rem;
pointer-events: none;
transition: opacity 0.3s;
opacity: 0;
}
.frame:not(.has-title):not(.is-terminal) {
@media (hover: none) {
& [data-language]::before {
opacity: 1;
margin-right: 3rem;
}
& [data-language]:active::before {
opacity: 0;
}
}
@media (hover: hover) {
& [data-language]::before {
opacity: 1;
}
&:hover [data-language]::before {
opacity: 0;
}
}
}
`,
});
}

View File

@@ -0,0 +1,3 @@
.expressive-code .frame {
@apply !shadow-none;
}

View File

@@ -110,22 +110,6 @@
.btn-regular-dark.success { .btn-regular-dark.success {
@apply bg-[oklch(0.75_0.14_var(--hue))] dark:bg-[oklch(0.75_0.14_var(--hue))] @apply bg-[oklch(0.75_0.14_var(--hue))] dark:bg-[oklch(0.75_0.14_var(--hue))]
} }
.copy-btn-icon {
@apply absolute top-1/2 left-1/2 transition -translate-x-1/2 -translate-y-1/2
}
.copy-btn .copy-icon {
@apply opacity-100 fill-white dark:fill-white/75
}
.copy-btn.success .copy-icon {
@apply opacity-0 fill-[var(--deep-text)]
}
.copy-btn .success-icon {
@apply opacity-0
}
.copy-btn.success .success-icon {
@apply opacity-100
}
} }
.custom-md img, #post-cover img { .custom-md img, #post-cover img {
@@ -154,4 +138,4 @@
.collapsed { .collapsed {
height: var(--collapsedHeight); height: var(--collapsedHeight);
} }

View File

@@ -58,19 +58,38 @@
} }
} }
pre { .copy-btn {
@apply bg-[var(--codeblock-bg)] !important; all: initial;
@apply rounded-xl px-5; @apply btn-regular-dark opacity-0 shadow-lg shadow-black/50 absolute active:scale-90 h-8 w-8 top-3 right-3 text-sm rounded-lg transition-all ease-in-out z-20 cursor-pointer;
}
.frame:hover .copy-btn {
opacity: 1;
}
code { .copy-btn-icon {
@apply bg-transparent text-inherit text-sm p-0; @apply absolute top-1/2 left-1/2 transition -translate-x-1/2 -translate-y-1/2 w-4 h-4 fill-white pointer-events-none;
}
.copy-btn .copy-icon {
@apply opacity-100 fill-white dark:fill-white/75;
}
.copy-btn.success .copy-icon {
@apply opacity-0 fill-[var(--deep-text)]
}
.copy-btn .success-icon {
@apply opacity-0 fill-white;
}
.copy-btn.success .success-icon {
@apply opacity-100
}
::selection { .expressive-code {
@apply bg-[var(--codeblock-selection)]; @apply my-4;
} ::selection {
@apply bg-[var(--codeblock-selection)];
} }
} }
ul, ol { ul, ol {
li::marker { li::marker {
@apply text-[var(--primary)]; @apply text-[var(--primary)];

View File

@@ -56,7 +56,8 @@ define({
--inline-code-color: var(--btn-content) --inline-code-color: var(--btn-content)
--selection-bg: oklch(0.90 0.05 var(--hue)) oklch(0.40 0.08 var(--hue)) --selection-bg: oklch(0.90 0.05 var(--hue)) oklch(0.40 0.08 var(--hue))
--codeblock-selection: oklch(0.40 0.08 var(--hue)) --codeblock-selection: oklch(0.40 0.08 var(--hue))
--codeblock-bg: oklch(0.2 0.015 var(--hue)) oklch(0.17 0.015 var(--hue)) --codeblock-bg: oklch(0.17 0.015 var(--hue)) oklch(0.17 0.015 var(--hue))
--codeblock-topbar-bg: oklch(0.3 0.02 var(--hue)) oklch(0.12 0.015 var(--hue))
--license-block-bg: black(0.03) var(--codeblock-bg) --license-block-bg: black(0.03) var(--codeblock-bg)

View File

@@ -86,3 +86,7 @@ export type BlogPostData = {
nextTitle?: string; nextTitle?: string;
nextSlug?: string; nextSlug?: string;
}; };
export type ExpressiveCodeConfig = {
theme: string;
};

View File

@@ -1,3 +1,4 @@
import { expressiveCodeConfig } from "@/config";
import type { LIGHT_DARK_MODE } from "@/types/config"; import type { LIGHT_DARK_MODE } from "@/types/config";
import { import {
AUTO_MODE, AUTO_MODE,
@@ -42,6 +43,12 @@ export function applyThemeToDocument(theme: LIGHT_DARK_MODE) {
} }
break; break;
} }
// Set the theme for Expressive Code
document.documentElement.setAttribute(
"data-theme",
expressiveCodeConfig.theme,
);
} }
export function setTheme(theme: LIGHT_DARK_MODE): void { export function setTheme(theme: LIGHT_DARK_MODE): void {