feat: support code block title

pull/5007/head
bluwy 11 months ago
parent be260fda6e
commit b6384b11f4

@ -426,6 +426,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*/
@ -452,6 +478,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;
@ -512,6 +542,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;

@ -339,6 +339,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);

@ -83,7 +83,7 @@ function createCodeGroup(md: MarkdownItAsync): ContainerArgs {
) { ) {
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,4 +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'
export interface Options { export interface Options {
codeCopyButtonTitle: string codeCopyButtonTitle: string
@ -16,7 +17,12 @@ export function preWrapperPlugin(md: MarkdownItAsync, options: Options) {
const [tokens, idx] = args const [tokens, idx] = 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' : ''
@ -27,21 +33,34 @@ 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="${options.codeCopyButtonTitle}" class="copy"></button>` + `<button title="${options.codeCopyButtonTitle}" class="copy"></button>` +
`<span class="lang">${label}</span>` + `<span class="lang">${label}</span>` +
fence(...args) + fence(...args) +
'</div>' `</div>`
) )
} }
} }
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) { function extractLang(info: string) {
@ -53,3 +72,18 @@ function extractLang(info: string) {
.replace(/^vue-html$/, 'template') .replace(/^vue-html$/, 'template')
.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