feat: support code block title

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

@ -426,6 +426,32 @@
border-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 {
/*rtl:ignore*/
@ -452,6 +478,10 @@
opacity 0.25s;
}
.vp-doc [class*='language-'] > .title-bar ~ button.copy {
top: 60px;
}
.vp-doc [class*='language-']:hover > button.copy,
.vp-doc [class*='language-'] > button.copy:focus {
opacity: 1;
@ -512,6 +542,10 @@
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-'] > button.copy:focus + span.lang {
opacity: 0;

@ -339,6 +339,8 @@
--vp-code-block-color: var(--vp-c-text-2);
--vp-code-block-bg: var(--vp-c-bg-alt);
--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);

@ -83,7 +83,7 @@ function createCodeGroup(md: MarkdownItAsync): ContainerArgs {
) {
const title = extractTitle(
isHtml ? tokens[i].content : tokens[i].info,
isHtml
{ html: isHtml, fallbackToLang: true }
)
if (title) {

@ -1,4 +1,5 @@
import type { MarkdownItAsync } from 'markdown-it-async'
import type Token from 'markdown-it/lib/token.mjs'
export interface Options {
codeCopyButtonTitle: string
@ -16,7 +17,12 @@ export function preWrapperPlugin(md: MarkdownItAsync, options: Options) {
const [tokens, idx] = args
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(/\[.*\]/, '')
const active = / active( |$)/.test(token.info) ? ' active' : ''
@ -27,21 +33,34 @@ export function preWrapperPlugin(md: MarkdownItAsync, options: Options) {
return (
`<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>` +
`<span class="lang">${label}</span>` +
fence(...args) +
'</div>'
`</div>`
)
}
}
export function extractTitle(info: string, html = false) {
if (html) {
export interface ExtractTitleOptions {
html?: boolean
fallbackToLang?: boolean
}
export function extractTitle(info: string, options?: ExtractTitleOptions) {
if (options?.html) {
return (
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) {
@ -53,3 +72,18 @@ function extractLang(info: string) {
.replace(/^vue-html$/, 'template')
.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