mirror of https://github.com/vuejs/vitepress
Merge 9a8aabe550
into 318c14fa7c
commit
c23167a70a
@ -1,46 +1,155 @@
|
|||||||
import { inBrowser, onContentUpdated } from 'vitepress'
|
import { inBrowser, onContentUpdated } from 'vitepress'
|
||||||
|
|
||||||
|
const codeGroupCache = new Map<string, HTMLElement[]>()
|
||||||
|
|
||||||
export function useCodeGroups() {
|
export function useCodeGroups() {
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
onContentUpdated(() => {
|
onContentUpdated(() => {
|
||||||
|
clearCache()
|
||||||
|
|
||||||
document.querySelectorAll('.vp-code-group > .blocks').forEach((el) => {
|
document.querySelectorAll('.vp-code-group > .blocks').forEach((el) => {
|
||||||
Array.from(el.children).forEach((child) => {
|
Array.from(el.children).forEach((child) => {
|
||||||
child.classList.remove('active')
|
child.classList.remove('active')
|
||||||
})
|
})
|
||||||
el.children[0].classList.add('active')
|
el.children[0].classList.add('active')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
handleQueryParamNavigation()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inBrowser) {
|
if (inBrowser) {
|
||||||
|
const handleUrlChange = () => {
|
||||||
|
handleQueryParamNavigation()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleQueryParamNavigation()
|
||||||
|
|
||||||
|
window.addEventListener('popstate', handleUrlChange)
|
||||||
|
|
||||||
window.addEventListener('click', (e) => {
|
window.addEventListener('click', (e) => {
|
||||||
const el = e.target as HTMLInputElement
|
const el = e.target as HTMLInputElement
|
||||||
|
|
||||||
if (el.matches('.vp-code-group input')) {
|
if (el.matches('.vp-code-group input')) {
|
||||||
// input <- .tabs <- .vp-code-group
|
|
||||||
const group = el.parentElement?.parentElement
|
const group = el.parentElement?.parentElement
|
||||||
if (!group) return
|
if (!group) return
|
||||||
|
|
||||||
const i = Array.from(group.querySelectorAll('input')).indexOf(el)
|
const label = group?.querySelector(`label[for="${el.id}"]`)
|
||||||
if (i < 0) return
|
if (!label) return
|
||||||
|
|
||||||
|
if (!activateTab(group, el)) return
|
||||||
|
|
||||||
|
label.scrollIntoView({ block: 'nearest' })
|
||||||
|
|
||||||
|
// Get the group key and tab title for URL update and sync
|
||||||
|
const groupKey = group.getAttribute('data-group-key')
|
||||||
|
const tabTitle = label.getAttribute('data-title')?.toLowerCase()
|
||||||
|
|
||||||
|
if (groupKey && tabTitle) {
|
||||||
|
syncCodeGroupsByKeyAndValue(groupKey, tabTitle, group)
|
||||||
|
updateUrl(groupKey, tabTitle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCodeGroupsByKey(groupKey: string): HTMLElement[] {
|
||||||
|
if (!codeGroupCache.has(groupKey)) {
|
||||||
|
codeGroupCache.set(
|
||||||
|
groupKey,
|
||||||
|
Array.from(
|
||||||
|
document.querySelectorAll(
|
||||||
|
`.vp-code-group[data-group-key="${groupKey}"]`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return codeGroupCache.get(groupKey) || []
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearCache() {
|
||||||
|
codeGroupCache.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateTab(group: HTMLElement, input: HTMLInputElement): boolean {
|
||||||
|
const inputs = Array.from(
|
||||||
|
group.querySelectorAll('input')
|
||||||
|
) as HTMLInputElement[]
|
||||||
|
const index = inputs.indexOf(input)
|
||||||
|
if (index < 0) return false
|
||||||
|
|
||||||
const blocks = group.querySelector('.blocks')
|
const blocks = group.querySelector('.blocks')
|
||||||
if (!blocks) return
|
if (!blocks) return false
|
||||||
|
|
||||||
const current = Array.from(blocks.children).find((child) =>
|
// Update radio input checked state
|
||||||
child.classList.contains('active')
|
inputs.forEach((radioInput, i) => {
|
||||||
|
radioInput.checked = i === index
|
||||||
|
})
|
||||||
|
|
||||||
|
// Remove active class from all blocks and add to the target block
|
||||||
|
Array.from(blocks.children).forEach((child, i) => {
|
||||||
|
child.classList.toggle('active', i === index)
|
||||||
|
})
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function findTabByTitle(
|
||||||
|
group: HTMLElement,
|
||||||
|
tabTitle: string
|
||||||
|
): HTMLInputElement | null {
|
||||||
|
if (!tabTitle) return null
|
||||||
|
const labels = Array.from(group.querySelectorAll('label[data-title]'))
|
||||||
|
const targetLabel = labels.find(
|
||||||
|
(label) =>
|
||||||
|
label.getAttribute('data-title')?.toLowerCase() === tabTitle.toLowerCase()
|
||||||
)
|
)
|
||||||
if (!current) return
|
|
||||||
|
|
||||||
const next = blocks.children[i]
|
if (!targetLabel) return null
|
||||||
if (!next || current === next) return
|
|
||||||
|
|
||||||
current.classList.remove('active')
|
const inputId = targetLabel.getAttribute('for')
|
||||||
next.classList.add('active')
|
if (!inputId) return null
|
||||||
|
|
||||||
const label = group?.querySelector(`label[for="${el.id}"]`)
|
return group.querySelector(`#${inputId}`)
|
||||||
label?.scrollIntoView({ block: 'nearest' })
|
}
|
||||||
|
|
||||||
|
function syncCodeGroupsByKeyAndValue(
|
||||||
|
groupKey: string,
|
||||||
|
tabValue: string,
|
||||||
|
excludeGroup?: HTMLElement
|
||||||
|
) {
|
||||||
|
const groups = getCodeGroupsByKey(groupKey)
|
||||||
|
|
||||||
|
groups.forEach((group) => {
|
||||||
|
// Skip the group that was just clicked
|
||||||
|
if (excludeGroup && group === excludeGroup) return
|
||||||
|
|
||||||
|
const input = findTabByTitle(group, tabValue)
|
||||||
|
if (input) {
|
||||||
|
activateTab(group, input)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateUrl(groupKey: string, tabValue: string) {
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
url.searchParams.set(groupKey, tabValue)
|
||||||
|
window.history.replaceState(null, '', url.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleQueryParamNavigation() {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
|
if (urlParams.size === 0) return
|
||||||
|
|
||||||
|
for (const [groupKey, tabValue] of urlParams.entries()) {
|
||||||
|
const groups = getCodeGroupsByKey(groupKey)
|
||||||
|
|
||||||
|
for (const group of groups) {
|
||||||
|
const input = findTabByTitle(group, tabValue)
|
||||||
|
if (input) {
|
||||||
|
activateTab(group, input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in new issue