pull/5007/merge
Bjorn Lu 2 weeks ago committed by GitHub
commit b3a9a46eeb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -132,6 +132,12 @@ const line1 = 'This is line 1'
const line2 = 'This is line 2' const line2 = 'This is line 2'
``` ```
## Title Bar
```js [main.js]
console.log('Hello, VitePress!')
```
## Import Code Snippets ## Import Code Snippets
### Basic Code Snippet ### Basic Code Snippet

@ -84,6 +84,7 @@ describe('Table of Contents', () => {
"Multiple single lines, ranges", "Multiple single lines, ranges",
"Comment Highlight", "Comment Highlight",
"Line Numbers", "Line Numbers",
"Title Bar",
"Import Code Snippets", "Import Code Snippets",
"Basic Code Snippet", "Basic Code Snippet",
"Specify Region", "Specify Region",
@ -215,6 +216,16 @@ describe('Line Numbers', () => {
}) })
}) })
describe('Title bar', () => {
test('render title bar', async () => {
const div = page.locator('#title-bar + div')
const titleBar = div.locator('.title-bar')
expect(titleBar).toBeTruthy()
const titleText = titleBar.locator('.title-text')
expect(await titleText.textContent()).toBe('main.js')
})
})
describe('Import Code Snippets', () => { describe('Import Code Snippets', () => {
test('basic', async () => { test('basic', async () => {
const lines = page.locator('#basic-code-snippet + div code > span') const lines = page.locator('#basic-code-snippet + div code > span')

@ -745,6 +745,24 @@ const line3 = 'This is line 3'
const line4 = 'This is line 4' const line4 = 'This is line 4'
``` ```
## Title Bar
You can add a title bar to your code blocks by adding `[title]` in the fenced code block:
**Input**
````md
```js [main.js]
console.log('Hello, VitePress!')
```
````
**Output**
```js [main.js]
console.log('Hello, VitePress!')
```
## Import Code Snippets ## Import Code Snippets
You can import code snippets from existing files via following syntax: You can import code snippets from existing files via following syntax:

@ -0,0 +1,23 @@
diff --git a/dist/index.mjs b/dist/index.mjs
index 7146607f6eb82fe3e58634d0be5acfaa929cf526..a1cdfa79be761a0efc9880a35ec12016b7aea837 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -200,7 +200,7 @@ function groupIconMdPlugin(md, options) {
});
};
const fenceRule = md.renderer.rules.fence;
- if (fenceRule) md.renderer.rules.fence = (...args) => {
+ if (false) md.renderer.rules.fence = (...args) => {
const [tokens, idx] = args;
const token = tokens[idx];
let isOnCodeGroup = false;
@@ -232,7 +232,8 @@ const HTTP_URL_RE = /^https?:\/\//;
async function generateCSS(labels, options) {
const baseCSS = `
.vp-code-block-title [data-title]::before,
-.vp-code-group [data-title]::before {
+.vp-code-group [data-title]::before,
+.vp-doc [class*='language-'] [data-title]::before {
display: inline-block;
width: 1em;
height: 1em;

@ -8,6 +8,7 @@ overrides:
esbuild: '-' esbuild: '-'
patchedDependencies: patchedDependencies:
vitepress-plugin-group-icons: c379c3737bf09a62f52fdff21e0d71647ab2bdd813a34ac32a4d8aa1edca0e7f
vue-sfc-transformer: 06dbce7d98fac77faf5e66a94aa6e04f34c0cc7ec0910ec10848e5f2e08b8b68 vue-sfc-transformer: 06dbce7d98fac77faf5e66a94aa6e04f34c0cc7ec0910ec10848e5f2e08b8b68
importers: importers:
@ -308,7 +309,7 @@ importers:
version: link:.. version: link:..
vitepress-plugin-group-icons: vitepress-plugin-group-icons:
specifier: ^1.7.6 specifier: ^1.7.6
version: 1.7.6(vite@8.2.1(@types/node@26.2.0)(jiti@1.21.7)(yaml@2.9.0)) version: 1.7.6(patch_hash=c379c3737bf09a62f52fdff21e0d71647ab2bdd813a34ac32a4d8aa1edca0e7f)(vite@8.2.1(@types/node@26.2.0)(jiti@1.21.7)(yaml@2.9.0))
vitepress-plugin-llms: vitepress-plugin-llms:
specifier: ^1.13.4 specifier: ^1.13.4
version: 1.13.4(supports-color@7.2.0) version: 1.13.4(supports-color@7.2.0)
@ -5082,7 +5083,7 @@ snapshots:
jiti: 1.21.7 jiti: 1.21.7
yaml: 2.9.0 yaml: 2.9.0
vitepress-plugin-group-icons@1.7.6(vite@8.2.1(@types/node@26.2.0)(jiti@1.21.7)(yaml@2.9.0)): vitepress-plugin-group-icons@1.7.6(patch_hash=c379c3737bf09a62f52fdff21e0d71647ab2bdd813a34ac32a4d8aa1edca0e7f)(vite@8.2.1(@types/node@26.2.0)(jiti@1.21.7)(yaml@2.9.0)):
dependencies: dependencies:
'@iconify-json/logos': 1.2.12 '@iconify-json/logos': 1.2.12
'@iconify-json/vscode-icons': 1.2.70 '@iconify-json/vscode-icons': 1.2.70

@ -17,6 +17,7 @@ overrides:
patchedDependencies: patchedDependencies:
vue-sfc-transformer: patches/vue-sfc-transformer.patch vue-sfc-transformer: patches/vue-sfc-transformer.patch
vitepress-plugin-group-icons: patches/vitepress-plugin-group-icons.patch
shellEmulator: true shellEmulator: true

@ -495,6 +495,32 @@
border-color 0.5s, border-color 0.5s,
color 0.5s; color 0.5s;
} }
.vp-doc [class*='language-'] > .title-bar {
position: relative;
display: flex;
margin-right: -24px;
margin-left: -24px;
padding: 0 12px;
background-color: var(--vp-code-block-title-bg);
box-shadow: inset 0 -1px var(--vp-code-block-divider-color);
}
@media (min-width: 640px) {
.vp-doc [class*='language-'] > .title-bar {
margin-right: 0;
margin-left: 0;
border-radius: 8px 8px 0 0;
}
}
.vp-doc [class*='language-'] > .title-bar > .title-text {
padding: 0 12px;
line-height: 48px;
font-size: 14px;
font-weight: 500;
color: var(--vp-code-block-title-color);
white-space: nowrap;
}
.vp-doc [class*='language-'] > button.copy { .vp-doc [class*='language-'] > button.copy {
/*rtl:ignore*/ /*rtl:ignore*/
@ -521,6 +547,10 @@
opacity 0.25s; opacity 0.25s;
} }
.vp-doc [class*='language-'] > .title-bar ~ button.copy {
top: 60px;
}
.vp-doc [class*='language-']:hover > button.copy, .vp-doc [class*='language-']:hover > button.copy,
.vp-doc [class*='language-'] > button.copy:focus { .vp-doc [class*='language-'] > button.copy:focus {
opacity: 1; opacity: 1;
@ -581,6 +611,10 @@
opacity 0.4s; opacity 0.4s;
} }
.vp-doc [class*='language-'] > .title-bar ~ span.lang {
top: 50px;
}
.vp-doc [class*='language-']:hover > button.copy + span.lang, .vp-doc [class*='language-']:hover > button.copy + span.lang,
.vp-doc [class*='language-'] > button.copy:focus + span.lang { .vp-doc [class*='language-'] > button.copy:focus + span.lang {
opacity: 0; opacity: 0;

@ -367,6 +367,8 @@
--vp-code-block-color: var(--vp-c-text-2); --vp-code-block-color: var(--vp-c-text-2);
--vp-code-block-bg: var(--vp-c-bg-alt); --vp-code-block-bg: var(--vp-c-bg-alt);
--vp-code-block-divider-color: var(--vp-c-gutter); --vp-code-block-divider-color: var(--vp-c-gutter);
--vp-code-block-title-color: var(--vp-c-text-2);
--vp-code-block-title-bg: var(--vp-code-block-bg);
--vp-code-lang-color: var(--vp-c-text-2); --vp-code-lang-color: var(--vp-c-text-2);

@ -172,7 +172,7 @@ function createCodeGroupOpenRender(md: MarkdownItAsync): RenderRule {
if ((tokens[i].type === 'fence' && tokens[i].tag === 'code') || isHtml) { if ((tokens[i].type === 'fence' && tokens[i].tag === 'code') || isHtml) {
const title = extractTitle( const title = extractTitle(
isHtml ? tokens[i].content : tokens[i].info, isHtml ? tokens[i].content : tokens[i].info,
isHtml { html: isHtml, fallbackToLang: true }
) )
if (title) { if (title) {

@ -1,5 +1,5 @@
import type { MarkdownItAsync } from 'markdown-it-async' import type { MarkdownItAsync } from 'markdown-it-async'
import type Token from 'markdown-it/lib/token.mjs'
import type { MarkdownEnv, MarkdownLocaleOptions } from '../../shared' import type { MarkdownEnv, MarkdownLocaleOptions } from '../../shared'
export interface Options { export interface Options {
@ -22,7 +22,12 @@ export function preWrapperPlugin(md: MarkdownItAsync, options: Options) {
const [tokens, idx, , env] = args const [tokens, idx, , env] = args
const token = tokens[idx] const token = tokens[idx]
// remove title from info // @ts-ignore
const isFromSnippet = !!token.src
const title =
isFromSnippet || isInCodeGroup(tokens, idx)
? ''
: extractTitle(token.info)
token.info = token.info.replace(/\[.*\]/, '') token.info = token.info.replace(/\[.*\]/, '')
const active = / active( |$)/.test(token.info) ? ' active' : '' const active = / active( |$)/.test(token.info) ? ' active' : ''
@ -43,6 +48,11 @@ export function preWrapperPlugin(md: MarkdownItAsync, options: Options) {
return ( return (
`<div class="language-${lang}${active}">` + `<div class="language-${lang}${active}">` +
(title
? `<div class="title-bar">` +
`<span class="title-text" data-title="${md.utils.escapeHtml(title)}">${title}</span>` +
`</div>`
: '') +
`<button title="${tooltipText}" data-copied="${copiedText}" class="copy"></button>` + `<button title="${tooltipText}" data-copied="${copiedText}" class="copy"></button>` +
`<span class="lang">${label}</span>` + `<span class="lang">${label}</span>` +
fence(...args) + fence(...args) +
@ -51,13 +61,21 @@ export function preWrapperPlugin(md: MarkdownItAsync, options: Options) {
} }
} }
export function extractTitle(info: string, html = false) { export interface ExtractTitleOptions {
if (html) { html?: boolean
fallbackToLang?: boolean
}
export function extractTitle(info: string, options?: ExtractTitleOptions) {
if (options?.html) {
return ( return (
info.replace(/<!--[^]*?-->/g, '').match(/data-title="(.*?)"/)?.[1] || '' info.replace(/<!--[^]*?-->/g, '').match(/data-title="(.*?)"/)?.[1] || ''
) )
} }
return info.match(/\[(.*)\]/)?.[1] || extractLang(info) || 'txt' return (
info.match(/\[(.*)\]/)?.[1] ||
(options?.fallbackToLang ? extractLang(info) || 'txt' : '')
)
} }
function extractLang(info: string): string { function extractLang(info: string): string {
@ -69,3 +87,18 @@ function extractLang(info: string): string {
.replace(/^ansi$/, '') || '' .replace(/^ansi$/, '') || ''
) )
} }
/**
* Whether the `idx` within `tokens` is inside a code-group container
*/
function isInCodeGroup(tokens: Token[], idx: number): boolean {
for (let i = idx - 1; i >= 0; --i) {
if (tokens[i].type === 'container_code-group_open') {
return true
}
if (tokens[i].type === 'container_code-group_close') {
return false
}
}
return false
}

Loading…
Cancel
Save