mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
189 lines
5.3 KiB
TypeScript
189 lines
5.3 KiB
TypeScript
import type { ComputedRef, MaybeRefOrGetter, Ref, ShallowRef } from 'vue'
|
|
import { onClickOutside, useEventListener } from '@vueuse/core'
|
|
import { computed, getCurrentInstance, onMounted, ref, toValue, useId, watch } from 'vue'
|
|
import { isPlainObject } from 'vuepress/shared'
|
|
|
|
export interface DemoConfig {
|
|
html: string
|
|
css: string
|
|
script: string
|
|
jsLib: string[]
|
|
cssLib: string[]
|
|
}
|
|
|
|
export function useExpand(defaultExpand = true): readonly [Ref<boolean>, () => void] {
|
|
const expanded = ref(defaultExpand)
|
|
function toggle() {
|
|
expanded.value = !expanded.value
|
|
}
|
|
return [expanded, toggle] as const
|
|
}
|
|
|
|
interface ResourceItem {
|
|
name: string
|
|
items: SubResourceItem[]
|
|
}
|
|
|
|
interface SubResourceItem {
|
|
name: string
|
|
url: string
|
|
}
|
|
|
|
interface UseResourcesResult {
|
|
resources: ComputedRef<ResourceItem[]>
|
|
showResources: Ref<boolean>
|
|
toggleResources: () => void
|
|
}
|
|
|
|
export function useResources(el: ShallowRef<HTMLDivElement | null>, config: MaybeRefOrGetter<DemoConfig | undefined>): UseResourcesResult {
|
|
const resources = computed<ResourceItem[]>(() => {
|
|
const conf = toValue(config)
|
|
if (!conf)
|
|
return []
|
|
return [
|
|
{ name: 'JavaScript', items: conf.jsLib?.map(url => ({ name: normalizeName(url), url })) },
|
|
{ name: 'CSS', items: conf.cssLib?.map(url => ({ name: normalizeName(url), url })) },
|
|
].filter(i => i.items?.length)
|
|
})
|
|
|
|
function normalizeName(url: string) {
|
|
return url.slice(url.lastIndexOf('/') + 1)
|
|
}
|
|
|
|
const showResources = ref(false)
|
|
|
|
function toggleResources(): void {
|
|
showResources.value = !showResources.value
|
|
}
|
|
|
|
onClickOutside(el, () => {
|
|
showResources.value = false
|
|
})
|
|
|
|
return {
|
|
resources,
|
|
showResources,
|
|
toggleResources,
|
|
}
|
|
}
|
|
|
|
interface FenceData {
|
|
js: string
|
|
css: string
|
|
html: string
|
|
jsType: string
|
|
cssType: string
|
|
}
|
|
|
|
export function useFence(fence: ShallowRef<HTMLDivElement | null>, config: MaybeRefOrGetter<DemoConfig | undefined>): Ref<FenceData> {
|
|
const data = ref<FenceData>({ js: '', css: '', html: '', jsType: '', cssType: '' })
|
|
|
|
onMounted(() => {
|
|
if (!fence.value)
|
|
return
|
|
const conf = toValue(config)
|
|
data.value.html = conf?.html ?? ''
|
|
const els = Array.from(fence.value.querySelectorAll('div[class*="language-"]'))
|
|
for (const el of els) {
|
|
const lang = el.className.match(/language-(\w+)/)?.[1] ?? ''
|
|
const content = el.querySelector('pre')?.textContent ?? ''
|
|
if (lang === 'js' || lang === 'javascript') {
|
|
data.value.js = content
|
|
data.value.jsType = 'js'
|
|
}
|
|
if (lang === 'ts' || lang === 'typescript') {
|
|
data.value.js = content
|
|
data.value.jsType = 'ts'
|
|
}
|
|
if (lang === 'css' || lang === 'scss' || lang === 'less' || lang === 'stylus' || lang === 'styl') {
|
|
data.value.css = content
|
|
data.value.cssType = lang === 'styl' ? 'stylus' : lang
|
|
}
|
|
}
|
|
})
|
|
return data
|
|
}
|
|
|
|
export function useNormalDemo(
|
|
draw: ShallowRef<HTMLIFrameElement | null>,
|
|
title: MaybeRefOrGetter<string | undefined>,
|
|
config: MaybeRefOrGetter<DemoConfig | undefined>,
|
|
): { id: string, height: Ref<string> } {
|
|
const current = getCurrentInstance()
|
|
const id = useId()
|
|
const isDark = computed(() => current?.appContext.config.globalProperties.$isDark.value)
|
|
const height = ref('100px')
|
|
|
|
onMounted(() => {
|
|
if (!draw.value)
|
|
return
|
|
const iframeDoc = draw.value.contentDocument || draw.value.contentWindow?.document
|
|
|
|
if (!iframeDoc)
|
|
return
|
|
|
|
const templateId = `VPDemoNormalDraw${id}`
|
|
useEventListener('message', (event) => {
|
|
const data = parseData(event.data)
|
|
if (data.type === templateId) {
|
|
height.value = `${data.height + 5}px`
|
|
}
|
|
})
|
|
watch([config, title], () => {
|
|
iframeDoc.write(createHTMLTemplate(toValue(title) || 'Demo', templateId, toValue(config)))
|
|
}, { immediate: true })
|
|
|
|
watch(isDark, () => {
|
|
iframeDoc.documentElement.dataset.theme = isDark.value ? 'dark' : 'light'
|
|
}, { immediate: true })
|
|
})
|
|
|
|
return { id, height }
|
|
}
|
|
|
|
function createHTMLTemplate(title: string, id: string, config?: DemoConfig): string {
|
|
const { cssLib = [], jsLib = [], html, css, script } = config || {}
|
|
const stylesheet = cssLib.map(url => `<link rel="stylesheet" href="${url}">`).join('')
|
|
const scripts = jsLib.map(url => `<script src="${url}"></script>`).join('')
|
|
return `<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>${title}</title>
|
|
${stylesheet}${scripts}
|
|
<style>${css}</style>
|
|
</head>
|
|
<body>
|
|
${html}
|
|
<script>;(function(){${script}})();</script>
|
|
<script>;(function(){
|
|
const height = Math.ceil(document.documentElement.getBoundingClientRect().height)
|
|
window.parent?.postMessage({ type: '${id}', height }, '*')
|
|
if (typeof window.ResizeObserver === 'undefined')
|
|
return
|
|
const resizeObserver = new ResizeObserver(entries => {
|
|
const height = Math.ceil(document.documentElement.getBoundingClientRect().height)
|
|
window.parent?.postMessage({ type: '${id}', height }, '*')
|
|
})
|
|
resizeObserver.observe(document.documentElement)
|
|
})();</script>
|
|
</body>
|
|
</html>`
|
|
}
|
|
|
|
export function parseData(data: any): any {
|
|
try {
|
|
if (typeof data === 'string') {
|
|
return JSON.parse(data)
|
|
}
|
|
else if (isPlainObject(data)) {
|
|
return data
|
|
}
|
|
return {}
|
|
}
|
|
catch {
|
|
return {}
|
|
}
|
|
}
|