From 7f0c18e01384d48380b64ba629229ec048f85453 Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Tue, 1 Aug 2023 20:49:08 +0530 Subject: [PATCH] feat: allow html blocks inside code groups (#2719) --- src/client/app/composables/codeGroups.ts | 38 ++++++++++++++----- src/client/app/index.ts | 2 +- src/client/app/theme.ts | 2 +- .../styles/components/vp-code-group.css | 10 ++++- .../styles/components/vp-doc.css | 6 ++- src/node/markdown/plugins/containers.ts | 21 +++++++--- src/node/markdown/plugins/preWrapper.ts | 17 +++++++-- 7 files changed, 70 insertions(+), 26 deletions(-) diff --git a/src/client/app/composables/codeGroups.ts b/src/client/app/composables/codeGroups.ts index e4bd5c5c..e80606ea 100644 --- a/src/client/app/composables/codeGroups.ts +++ b/src/client/app/composables/codeGroups.ts @@ -1,6 +1,17 @@ -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 @@ -8,17 +19,24 @@ export function useCodeGroups() { 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') } }) } diff --git a/src/client/app/index.ts b/src/client/app/index.ts index a766b4da..e613d84e 100644 --- a/src/client/app/index.ts +++ b/src/client/app/index.ts @@ -59,7 +59,7 @@ const VitePressApp = defineComponent({ useCodeGroups() if (Theme.setup) Theme.setup() - return () => h(Theme.Layout) + return () => h(Theme.Layout!) } }) diff --git a/src/client/app/theme.ts b/src/client/app/theme.ts index 155ba1a7..738a06ec 100644 --- a/src/client/app/theme.ts +++ b/src/client/app/theme.ts @@ -9,7 +9,7 @@ export interface EnhanceAppContext { } export interface Theme { - Layout: Component + Layout?: Component enhanceApp?: (ctx: EnhanceAppContext) => Awaitable extends?: Theme diff --git a/src/client/theme-default/styles/components/vp-code-group.css b/src/client/theme-default/styles/components/vp-code-group.css index aa0675e8..0b591872 100644 --- a/src/client/theme-default/styles/components/vp-code-group.css +++ b/src/client/theme-default/styles/components/vp-code-group.css @@ -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; +} diff --git a/src/client/theme-default/styles/components/vp-doc.css b/src/client/theme-default/styles/components/vp-doc.css index 753843dc..23bc6852 100644 --- a/src/client/theme-default/styles/components/vp-doc.css +++ b/src/client/theme-default/styles/components/vp-doc.css @@ -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); @@ -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; } diff --git a/src/node/markdown/plugins/containers.ts b/src/node/markdown/plugins/containers.ts index 11d3fbea..b9397581 100644 --- a/src/node/markdown/plugins/containers.ts +++ b/src/node/markdown/plugins/containers.ts @@ -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 += `` + 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 += `` + + if (checked && !isHtml) tokens[i].info += ' active' checked = '' } } diff --git a/src/node/markdown/plugins/preWrapper.ts b/src/node/markdown/plugins/preWrapper.ts index 6e149bf9..db237dbf 100644 --- a/src/node/markdown/plugins/preWrapper.ts +++ b/src/node/markdown/plugins/preWrapper.ts @@ -9,14 +9,18 @@ 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 `
${lang}${rawCode}
` + return `
${lang}${rawCode}
` } } @@ -24,7 +28,12 @@ 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' }