|
|
|
@ -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
|
|
|
|
|
|
|
|
}
|
|
|
|
|