Skip to content

Commit

Permalink
feat: allow html blocks inside code groups (#2719)
Browse files Browse the repository at this point in the history
  • Loading branch information
brc-dd committed Aug 1, 2023
1 parent 0f38eb4 commit 7f0c18e
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 26 deletions.
38 changes: 28 additions & 10 deletions src/client/app/composables/codeGroups.ts
@@ -1,24 +1,42 @@
import { inBrowser } from 'vitepress'
import { inBrowser, onContentUpdated } from 'vitepress'

export function useCodeGroups() {
if (import.meta.env.DEV) {
onContentUpdated(() => {
document.querySelectorAll('.vp-code-group > .blocks').forEach((el) => {
Array.from(el.children).forEach((child) => {
child.classList.remove('active')
})
el.children[0].classList.add('active')
})
})
}

if (inBrowser) {
window.addEventListener('click', (e) => {
const el = e.target as HTMLInputElement

if (el.matches('.vp-code-group input')) {
// input <- .tabs <- .vp-code-group
const group = el.parentElement?.parentElement
const i = Array.from(group?.querySelectorAll('input') || []).indexOf(el)
if (!group) return

const i = Array.from(group.querySelectorAll('input')).indexOf(el)
if (i < 0) return

const blocks = group.querySelector('.blocks')
if (!blocks) return

const current = Array.from(blocks.children).find((child) =>
child.classList.contains('active')
)
if (!current) return

const current = group?.querySelector('div[class*="language-"].active')
const next = group?.querySelectorAll(
'div[class*="language-"]:not(.language-id)'
)?.[i]
const next = blocks.children[i]
if (!next || current === next) return

if (current && next && current !== next) {
current.classList.remove('active')
next.classList.add('active')
}
current.classList.remove('active')
next.classList.add('active')
}
})
}
Expand Down
2 changes: 1 addition & 1 deletion src/client/app/index.ts
Expand Up @@ -59,7 +59,7 @@ const VitePressApp = defineComponent({
useCodeGroups()

if (Theme.setup) Theme.setup()
return () => h(Theme.Layout)
return () => h(Theme.Layout!)
}
})

Expand Down
2 changes: 1 addition & 1 deletion src/client/app/theme.ts
Expand Up @@ -9,7 +9,7 @@ export interface EnhanceAppContext {
}

export interface Theme {
Layout: Component
Layout?: Component
enhanceApp?: (ctx: EnhanceAppContext) => Awaitable<void>
extends?: Theme

Expand Down
10 changes: 8 additions & 2 deletions src/client/theme-default/styles/components/vp-code-group.css
Expand Up @@ -66,13 +66,19 @@
background-color: var(--vp-code-tab-active-bar-color);
}

.vp-code-group div[class*='language-'] {
.vp-code-group div[class*='language-'],
.vp-block {
display: none;
margin-top: 0 !important;
border-top-left-radius: 0 !important;
border-top-right-radius: 0 !important;
}

.vp-code-group div[class*='language-'].active {
.vp-code-group div[class*='language-'].active,
.vp-block.active {
display: block;
}

.vp-block {
padding: 20px 24px;
}
6 changes: 4 additions & 2 deletions src/client/theme-default/styles/components/vp-doc.css
Expand Up @@ -278,7 +278,8 @@
color: var(--vp-c-brand-dark);
}

.vp-doc div[class*='language-'] {
.vp-doc div[class*='language-'],
.vp-block {
position: relative;
margin: 16px -24px;
background-color: var(--vp-code-block-bg);
Expand All @@ -287,7 +288,8 @@
}

@media (min-width: 640px) {
.vp-doc div[class*='language-'] {
.vp-doc div[class*='language-'],
.vp-block {
border-radius: 8px;
margin: 16px 0;
}
Expand Down
21 changes: 15 additions & 6 deletions src/node/markdown/plugins/containers.ts
Expand Up @@ -74,13 +74,22 @@ function createCodeGroup(options: Options): ContainerArgs {
);
++i
) {
if (tokens[i].type === 'fence' && tokens[i].tag === 'code') {
const title = extractTitle(tokens[i].info)
const id = nanoid(7)
tabs += `<input type="radio" name="group-${name}" id="tab-${id}" ${checked}><label for="tab-${id}">${title}</label>`
const isHtml = tokens[i].type === 'html_block'

if (checked) {
tokens[i].info += ' active'
if (
(tokens[i].type === 'fence' && tokens[i].tag === 'code') ||
isHtml
) {
const title = extractTitle(
isHtml ? tokens[i].content : tokens[i].info,
isHtml
)

if (title) {
const id = nanoid(7)
tabs += `<input type="radio" name="group-${name}" id="tab-${id}" ${checked}><label for="tab-${id}">${title}</label>`

if (checked && !isHtml) tokens[i].info += ' active'
checked = ''
}
}
Expand Down
17 changes: 13 additions & 4 deletions src/node/markdown/plugins/preWrapper.ts
Expand Up @@ -9,22 +9,31 @@ export function preWrapperPlugin(md: MarkdownIt, options: Options) {
md.renderer.rules.fence = (...args) => {
const [tokens, idx] = args
const token = tokens[idx]

// remove title from info
token.info = token.info.replace(/\[.*\]/, '')

const active = / active( |$)/.test(token.info) ? ' active' : ''
token.info = token.info.replace(/ active$/, '').replace(/ active /, ' ')

const lang = extractLang(token.info)
const rawCode = fence(...args)
return `<div class="language-${lang}${getAdaptiveThemeMarker(options)}${
/ active( |$)/.test(token.info) ? ' active' : ''
}"><button title="Copy Code" class="copy"></button><span class="lang">${lang}</span>${rawCode}</div>`
return `<div class="language-${lang}${getAdaptiveThemeMarker(
options
)}${active}"><button title="Copy Code" class="copy"></button><span class="lang">${lang}</span>${rawCode}</div>`
}
}

export function getAdaptiveThemeMarker(options: Options) {
return options.hasSingleTheme ? '' : ' vp-adaptive-theme'
}

export function extractTitle(info: string) {
export function extractTitle(info: string, html = false) {
if (html) {
return (
info.replace(/<!--[^]*?-->/g, '').match(/data-title="(.*?)"/)?.[1] || ''
)
}
return info.match(/\[(.*)\]/)?.[1] || extractLang(info) || 'txt'
}

Expand Down

0 comments on commit 7f0c18e

Please sign in to comment.