Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 104 additions & 4 deletions docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,110 @@
import DefaultTheme from 'vitepress/theme'
import type { Theme } from 'vitepress'
import { nextTick } from 'vue'
import './custom.css'
import DaytonaRunner from './components/DaytonaRunner.vue'

export default {
...DefaultTheme,
enhanceApp({ app }) {
const STORAGE_KEY = 'preferred-code-lang'

function getPreferredLang(): string | null {
try {
return localStorage.getItem(STORAGE_KEY)
} catch {
return null
}
}

function setPreferredLang(lang: string): void {
try {
localStorage.setItem(STORAGE_KEY, lang)
} catch {
/* ignore */
}
}

function labelText(label: HTMLLabelElement): string {
return (label.textContent ?? '').trim()
}

function applyLangToGroup(group: HTMLElement, lang: string): boolean {
const inputs = Array.from(
group.querySelectorAll<HTMLInputElement>('.tabs input')
)
const labels = Array.from(
group.querySelectorAll<HTMLLabelElement>('.tabs label')
)
const idx = labels.findIndex((l) => labelText(l) === lang)
if (idx < 0) return false
const target = inputs[idx]
if (!target) return false
if (!target.checked) target.checked = true
const blocks = group.querySelector<HTMLElement>(':scope > .blocks')
if (blocks) {
const children = Array.from(blocks.children)
children.forEach((child) => child.classList.remove('active'))
children[idx]?.classList.add('active')
}
return true
}

function applyLangEverywhere(lang: string, skip?: HTMLElement | null): void {
document
.querySelectorAll<HTMLElement>('.vp-code-group')
.forEach((group) => {
if (group === skip) return
applyLangToGroup(group, lang)
})
}

function applyStoredLang(): void {
const lang = getPreferredLang()
if (lang) applyLangEverywhere(lang)
}

const theme: Theme = {
extends: DefaultTheme,
enhanceApp({ app, router }) {
app.component('DaytonaRunner', DaytonaRunner)
},

if (typeof window === 'undefined') return

document.addEventListener(
'click',
(event) => {
const target = event.target
if (!(target instanceof HTMLInputElement)) return
if (!target.matches('.vp-code-group .tabs input')) return
const group = target.closest<HTMLElement>('.vp-code-group')
if (!group) return
const label = group.querySelector<HTMLLabelElement>(
`label[for="${CSS.escape(target.id)}"]`
)
const lang = label ? labelText(label) : ''
if (!lang) return
setPreferredLang(lang)
applyLangEverywhere(lang, group)
},
true
)

const schedule = () => {
nextTick(() => {
requestAnimationFrame(applyStoredLang)
})
}

if (document.readyState === 'loading') {
window.addEventListener('DOMContentLoaded', schedule, { once: true })
} else {
schedule()
}

const prev = router.onAfterRouteChanged
router.onAfterRouteChanged = (to) => {
prev?.(to)
schedule()
}
}
}

export default theme