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, () => 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 showResources: Ref toggleResources: () => void } export function useResources(el: ShallowRef, config: MaybeRefOrGetter): UseResourcesResult { const resources = computed(() => { 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, config: MaybeRefOrGetter): Ref { const data = ref({ 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, title: MaybeRefOrGetter, config: MaybeRefOrGetter, ): { id: string, height: Ref } { 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 => ``).join('') const scripts = jsLib.map(url => ``).join('') return ` ${title} ${stylesheet}${scripts} ${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 {} } }