refactor: use deconstruct syntax to handle component props (#744)

This commit is contained in:
pengzhanbo 2025-10-31 17:42:28 +08:00 committed by GitHub
parent 0e38265f96
commit d4ad65a1ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
73 changed files with 320 additions and 350 deletions

View File

@ -4,13 +4,13 @@ import { computed, nextTick, ref, useTemplateRef, watch } from 'vue'
import '@vuepress/helper/transition/fade-in.css'
const props = defineProps<{
const { label, total } = defineProps<{
label: string
total: number
}>()
const active = ref(false)
const list = computed(() => Array.from({ length: props.total }, (_, i) => i))
const list = computed(() => Array.from({ length: total }, (_, i) => i))
const position = ref({ x: 0, y: 0 })
const popover = useTemplateRef<HTMLDivElement>('popover')

View File

@ -3,7 +3,7 @@ import { useInterval } from '@vueuse/core'
import { onMounted, toRef, watch } from 'vue'
import { useAudioPlayer } from '../composables/audio.js'
const props = defineProps<{
const { src, autoplay, type, volume, startTime, endTime } = defineProps<{
src: string
autoplay?: boolean
type?: string
@ -13,20 +13,19 @@ const props = defineProps<{
}>()
const { paused, play, pause, seek, setVolume } = useAudioPlayer(
toRef(() => props.src),
toRef(() => src),
{
type: toRef(() => props.type || ''),
autoplay: props.autoplay,
type: toRef(() => type || ''),
autoplay,
oncanplay: () => {
if (props.startTime) {
seek(props.startTime)
}
if (startTime)
seek(startTime)
},
ontimeupdate: (time) => {
if (props.endTime && time >= props.endTime) {
if (endTime && time >= endTime) {
pause()
if (props.startTime) {
seek(props.startTime)
if (startTime) {
seek(startTime)
}
}
},
@ -56,7 +55,7 @@ function toggle() {
}
onMounted(() => {
watch(() => props.volume, (volume) => {
watch(() => volume, (volume) => {
if (typeof volume !== 'undefined') {
setVolume(volume)
}

View File

@ -12,16 +12,12 @@ interface MessageData {
}
}
const props = withDefaults(defineProps<{
const { feature, past = 2, future = 1, meta = '' } = defineProps<{
feature: string
past?: number
future?: number
meta?: string
}>(), {
past: 2,
future: 1,
meta: '',
})
}>()
const url = 'https://caniuse.pengzhanbo.cn/'
@ -29,7 +25,7 @@ const height = ref('330px')
const isDark = useDarkMode()
const source = computed(() => {
const source = `${url}${props.feature}#past=${props.past}&future=${props.future}&meta=${props.meta}&theme=${isDark.value ? 'dark' : 'light'}`
const source = `${url}${feature}#past=${past}&future=${future}&meta=${meta}&theme=${isDark.value ? 'dark' : 'light'}`
return source
})
@ -40,8 +36,8 @@ useEventListener('message', (event) => {
if (
type === 'ciu_embed'
&& payload
&& payload.feature === props.feature
&& payload.meta === props.meta
&& payload.feature === feature
&& payload.meta === meta
) {
height.value = `${Math.ceil(payload.height)}px`
}

View File

@ -2,7 +2,7 @@
import { useDarkMode } from '@vuepress/helper/client'
import { computed } from 'vue'
const props = defineProps<{
const { user, slash, title, preview, editable, tab, theme, width, height } = defineProps<{
user: string
slash: string
title?: string
@ -19,16 +19,16 @@ const CODEPEN_LINK = 'https://codepen.io/'
const isDark = useDarkMode()
const link = computed(() => {
const middle = props.preview ? '/embed/preview/' : '/embed/'
const middle = preview ? '/embed/preview/' : '/embed/'
const params = new URLSearchParams()
props.editable && params.set('editable', 'true')
props.tab && params.set('default-tab', props.tab)
editable && params.set('editable', 'true')
tab && params.set('default-tab', tab)
const theme = props.theme ?? (isDark.value ? 'dark' : 'light')
theme && params.set('theme-id', theme)
const themeMode = theme ?? (isDark.value ? 'dark' : 'light')
themeMode && params.set('theme-id', themeMode)
return `${CODEPEN_LINK}${props.user}${middle}${props.slash}?${params.toString()}`
return `${CODEPEN_LINK}${user}${middle}${slash}?${params.toString()}`
})
</script>

View File

@ -2,7 +2,7 @@
import type { Ref } from 'vue'
import { inject, ref } from 'vue'
const props = defineProps<{
const { type, filename, level, diff, expanded, focus, filepath } = defineProps<{
type: 'file' | 'folder'
filename: string
level: number
@ -18,17 +18,17 @@ const onNodeClick = inject<
(filename: string, type: 'file' | 'folder') => void
>('on-file-tree-node-click', () => {})
const active = ref(props.expanded)
const active = ref(expanded)
function nodeClick() {
if (props.filename === '…' || props.filename === '...')
if (filename === '…' || filename === '...')
return
onNodeClick(props.filepath || props.filename, props.type)
onNodeClick(filepath || filename, type)
}
function toggle(ev: MouseEvent) {
if (props.type === 'folder') {
if (type === 'folder') {
const el = ev.target as HTMLElement
if (!el.matches('.comment, .comment *')) {
active.value = !active.value

View File

@ -2,7 +2,7 @@
import { useDarkMode } from '@vuepress/helper/client'
import { computed } from 'vue'
const props = defineProps<{
const { source, title, tab, theme, width, height } = defineProps<{
source: string
title?: string
tab: string
@ -14,8 +14,8 @@ const props = defineProps<{
const isDark = useDarkMode()
const link = computed(() => {
const theme = props.theme === 'dark' ? '/dark/' : isDark.value ? '/dark/' : ''
return `https://jsfiddle.net/${props.source}/embedded/${props.tab}${theme}`
const themeMode = theme === 'dark' ? '/dark/' : isDark.value ? '/dark/' : ''
return `https://jsfiddle.net/${source}/embedded/${tab}${themeMode}`
})
</script>

View File

@ -8,7 +8,7 @@ defineOptions({
inheritAttrs: false,
})
const props = defineProps<ReplitTokenMeta>()
const { source, theme, width, height: h, title } = defineProps<ReplitTokenMeta>()
// magic height
const height = ref('47px')
@ -19,18 +19,18 @@ const REPLIT_LINK = 'https://replit.com/'
const isDark = useDarkMode()
const link = computed(() => {
const url = new URL(`/${props.source}`, REPLIT_LINK)
const url = new URL(`/${source}`, REPLIT_LINK)
url.searchParams.set('embed', 'true')
const theme = props.theme || (isDark.value ? 'dark' : 'light')
url.searchParams.set('theme', theme)
const themeMode = theme || (isDark.value ? 'dark' : 'light')
url.searchParams.set('theme', themeMode)
return url.toString()
})
function onload() {
loaded.value = true
height.value = props.height || '450px'
height.value = h || '450px'
}
</script>

View File

@ -5,27 +5,27 @@ import { onMounted, ref, shallowRef, watch } from 'vue'
interface TabProps extends Record<string, unknown> {
id: string
}
const props = withDefaults(defineProps<{
const { id, tabId = '', active = 0, data } = defineProps<{
id: string
tabId?: string
active?: number
data: TabProps[]
}>(), { active: 0, tabId: '' })
}>()
const TAB_STORE_NAME = 'VUEPRESS_TAB_STORE'
const tabStore = useStorage<Record<string, string>>(TAB_STORE_NAME, {})
// Index of current active item
const activeIndex = ref(props.active)
const activeIndex = ref(active)
// Refs of the tab buttons
const tabRefs = shallowRef<HTMLUListElement[]>([])
// Update store
function updateStore(): void {
if (props.tabId)
tabStore.value[props.tabId] = props.data[activeIndex.value].id
if (tabId)
tabStore.value[tabId] = data[activeIndex.value]?.id
}
// Activate next tab
@ -59,26 +59,26 @@ function keyboardHandler(event: KeyboardEvent, index: number): void {
}
function getInitialIndex(): number {
if (props.tabId) {
const valueIndex = props.data.findIndex(
({ id }) => tabStore.value[props.tabId] === id,
if (tabId) {
const valueIndex = data.findIndex(
({ id }) => tabStore.value[tabId] === id,
)
if (valueIndex !== -1)
return valueIndex
}
return props.active
return active
}
onMounted(() => {
activeIndex.value = getInitialIndex()
watch(
() => tabStore.value[props.tabId],
() => tabStore.value[tabId],
(newValue, oldValue) => {
if (props.tabId && newValue !== oldValue) {
const index = props.data.findIndex(({ id }) => id === newValue)
if (tabId && newValue !== oldValue) {
const index = data.findIndex(({ id }) => id === newValue)
if (index !== -1)
activeIndex.value = index

View File

@ -1,13 +1,13 @@
<script setup lang="ts">
import { onMounted, provide, ref, useTemplateRef, watch } from 'vue'
const props = withDefaults(defineProps<{
const { title, height = '320px', entryFile } = defineProps<{
title?: string
height?: string
entryFile?: string
}>(), { height: '320px' })
}>()
const activeNode = ref(props.entryFile || '')
const activeNode = ref(entryFile || '')
const isEmpty = ref(true)
const codePanel = useTemplateRef<HTMLDivElement>('codePanel')
@ -44,7 +44,7 @@ onMounted(() => {
<template>
<div class="vp-code-tree">
<div class="code-tree-panel" :style="{ 'max-height': props.height }">
<div class="code-tree-panel" :style="{ 'max-height': height }">
<div v-if="title" class="code-tree-title" :title="title">
<span>{{ title }}</span>
</div>
@ -52,7 +52,7 @@ onMounted(() => {
<slot name="file-tree" />
</div>
</div>
<div ref="codePanel" class="code-panel" :style="{ height: props.height }">
<div ref="codePanel" class="code-panel" :style="{ height }">
<slot />
<div v-if="isEmpty" class="code-tree-empty">
<span class="vpi-code-tree-empty" />

View File

@ -2,15 +2,15 @@
import { provide, ref } from 'vue'
import { INJECT_COLLAPSE_KEY } from '../options.js'
const props = defineProps<{
const { accordion, index } = defineProps<{
accordion?: boolean
index?: number
}>()
const currentIndex = ref<number | undefined>(props.index)
const currentIndex = ref<number | undefined>(index)
provide(INJECT_COLLAPSE_KEY, {
accordion: props.accordion ?? false,
accordion: accordion ?? false,
index: currentIndex,
})
</script>

View File

@ -6,7 +6,7 @@ import { INJECT_COLLAPSE_KEY } from '../options.js'
import '@vuepress/helper/transition/fade-in-height-expand.css'
const props = defineProps<{
const { expand, index } = defineProps<{
expand?: boolean
index: number
}>()
@ -20,36 +20,36 @@ if (__VUEPRESS_DEV__ && !collapse) {
throw new Error('<VPCollapseItem /> must be used inside <VPCollapse />')
}
const expand = ref(
const expanded = ref(
collapse?.accordion && typeof collapse.index.value !== 'undefined'
? props.index === collapse.index.value
: props.expand,
? index === collapse.index.value
: expand,
)
if (collapse?.accordion) {
watch(collapse?.index, () => {
expand.value = collapse?.index.value === props.index
expanded.value = collapse?.index.value === index
})
}
function toggle() {
if (collapse?.accordion) {
if (collapse.index.value === props.index && expand.value) {
expand.value = false
if (collapse.index.value === index && expanded.value) {
expanded.value = false
}
else {
collapse!.index.value = props.index!
expand.value = true
collapse!.index.value = index!
expanded.value = true
}
}
else {
expand.value = !expand.value
expanded.value = !expanded.value
}
}
</script>
<template>
<div class="vp-collapse-item" :class="{ expand }">
<div class="vp-collapse-item" :class="{ expanded }">
<div class="vp-collapse-header" @click="toggle">
<span class="vpi-chevron-right" />
<p class="vp-collapse-title">
@ -57,7 +57,7 @@ function toggle() {
</p>
</div>
<FadeInExpandTransition>
<div v-show="expand" class="vp-collapse-content">
<div v-show="expanded" class="vp-collapse-content">
<div class="vp-collapse-content-inner">
<slot />
</div>
@ -95,7 +95,7 @@ function toggle() {
transform: rotate(0deg);
}
.vp-collapse-item.expand .vpi-chevron-right {
.vp-collapse-item.expanded .vpi-chevron-right {
transform: rotate(90deg);
}

View File

@ -8,14 +8,14 @@ import { useExpand } from '../composables/demo.js'
import '@vuepress/helper/transition/fade-in-height-expand.css'
import '../styles/demo.css'
const props = defineProps<{
const { type, title, desc, expanded } = defineProps<{
type?: 'vue' | 'markdown'
title?: string
desc?: string
expanded?: boolean
}>()
const [showCode, toggleCode] = useExpand(props.expanded)
const [showCode, toggleCode] = useExpand(expanded)
const draw = useTemplateRef<HTMLIFrameElement>('draw')
const vueDraw = useTemplateRef<HTMLIFrameElement>('draw-vue')
@ -30,7 +30,7 @@ function resizeAndPositionVueDraw() {
vueDraw.value.style.left = `${rect.x + scrollLeft}px`
}
if (props.type === 'vue' && !__VUEPRESS_SSR__) {
if (type === 'vue' && !__VUEPRESS_SSR__) {
watch([draw, vueDraw], () => {
resizeAndPositionVueDraw()
if (draw.value && vueDraw.value) {

View File

@ -8,29 +8,29 @@ import '@vuepress/helper/transition/fade-in.css'
import '@vuepress/helper/transition/fade-in-height-expand.css'
import '../styles/demo.css'
const props = defineProps<{
const { title, desc, expanded, config } = defineProps<{
title?: string
desc?: string
expanded?: boolean
config?: DemoConfig
}>()
const [showCode, toggleCode] = useExpand(props.expanded)
const [showCode, toggleCode] = useExpand(expanded)
const { resources, showResources, toggleResources } = useResources(
useTemplateRef<HTMLDivElement>('resourcesEl'),
() => props.config,
() => config,
)
const { id, height } = useNormalDemo(
useTemplateRef<HTMLIFrameElement>('draw'),
() => props.title,
() => props.config,
() => title,
() => config,
)
const data = useFence(
useTemplateRef<HTMLDivElement>('fence'),
() => props.config,
() => config,
)
</script>

View File

@ -3,7 +3,7 @@ import { decodeData } from '@vuepress/helper/client'
import { useClipboard, useToggle } from '@vueuse/core'
import { computed, useTemplateRef } from 'vue'
const props = defineProps<{
const { title, align = 'left', copy, maxContent, fullWidth, markdown } = defineProps<{
/** 表格标题 */
title?: string
/** 对其方式 */
@ -19,7 +19,7 @@ const props = defineProps<{
}>()
const tableEl = useTemplateRef('table')
const rawContent = computed(() => props.markdown ? decodeData(props.markdown) : '')
const rawContent = computed(() => markdown ? decodeData(markdown) : '')
const [isHTMLCopied, toggleHTMLCopy] = useToggle()
const [isMDCopied, toggleMDCopy] = useToggle()
@ -35,7 +35,7 @@ function onCopy(type: 'html' | 'md') {
</script>
<template>
<div class="vp-table" :class="{ [align || 'left']: true, full: fullWidth }">
<div class="vp-table" :class="{ [align]: true, full: fullWidth }">
<div class="table-container">
<div class="table-content">
<div v-if="copy" class="table-toolbar">

View File

@ -2,7 +2,7 @@
import { computed, provide } from 'vue'
import { INJECT_TIMELINE_KEY } from '../options.js'
const props = defineProps<{
const { horizontal, card, placement, line } = defineProps<{
horizontal?: boolean
card?: boolean
placement?: 'left' | 'right' | 'between'
@ -10,10 +10,10 @@ const props = defineProps<{
}>()
provide(INJECT_TIMELINE_KEY, computed(() => ({
line: props.line || 'solid',
card: props.card ?? false,
horizontal: props.horizontal ?? false,
placement: props.placement || 'left',
line: line || 'solid',
card: card ?? false,
horizontal: horizontal ?? false,
placement: placement || 'left',
})))
</script>

View File

@ -4,7 +4,7 @@ import { useMediaQuery } from '@vueuse/core'
import { computed, inject } from 'vue'
import { INJECT_TIMELINE_KEY } from '../options.js'
const props = defineProps<{
const { time, type, card, line, icon, color, placement } = defineProps<{
time?: string
type?: 'info' | 'tip' | 'success' | 'warning' | 'danger' | 'caution' | 'important' | (string & {})
card?: boolean
@ -25,17 +25,17 @@ const defaultOptions = inject<ComputedRef<{
const timeline = computed(() => {
const between = defaultOptions?.value.placement === 'between' && !is639.value
const placement = defaultOptions?.value.placement === 'between' ? 'left' : defaultOptions?.value.placement
const defaultPlacement = defaultOptions?.value.placement === 'between' ? 'left' : defaultOptions?.value.placement
return {
time: props.time,
type: props.type || 'info',
line: props.line || defaultOptions?.value.line || 'solid',
icon: props.icon,
color: props.color,
time,
type: type || 'info',
line: line || defaultOptions?.value.line || 'solid',
icon,
color,
horizontal: defaultOptions?.value.horizontal ?? false,
between: between ? props.placement || 'left' : false,
placement: between ? '' : (placement || 'left'),
card: props.card ?? defaultOptions?.value.card ?? false,
between: between ? placement || 'left' : false,
placement: between ? '' : (defaultPlacement || 'left'),
card: card ?? defaultOptions?.value.card ?? false,
}
})
</script>

View File

@ -32,7 +32,7 @@ import BackIcon from './icons/BackIcon.vue'
import ClearIcon from './icons/ClearIcon.vue'
import SearchIcon from './icons/SearchIcon.vue'
const props = defineProps<{
const { locales, options } = defineProps<{
locales: SearchBoxLocales
options: SearchOptions
}>()
@ -42,7 +42,7 @@ const emit = defineEmits<{
}>()
const routeLocale = useRouteLocale()
const locale = useLocale(toRef(props.locales))
const locale = useLocale(toRef(() => locales))
const el = shallowRef<HTMLElement>()
const resultsEl = shallowRef<HTMLElement>()
@ -71,15 +71,15 @@ const searchIndex = computedAsync(async () =>
prefix: true,
boost: { title: 4, text: 2, titles: 1 },
},
...props.options.miniSearch?.searchOptions,
...props.options.miniSearch?.options,
...options.miniSearch?.searchOptions,
...options.miniSearch?.options,
},
),
),
)
const disableQueryPersistence = computed(() =>
props.options?.disableQueryPersistence === true,
options?.disableQueryPersistence === true,
)
const filterText = disableQueryPersistence.value
? ref('')

View File

@ -3,11 +3,11 @@ import type { SearchBoxLocales } from '../../shared/index.js'
import { toRef } from 'vue'
import { useLocale } from '../composables/index.js'
const props = defineProps<{
const { locales } = defineProps<{
locales: SearchBoxLocales
}>()
const locale = useLocale(toRef(props.locales))
const locale = useLocale(toRef(() => locales))
</script>
<template>

View File

@ -6,14 +6,14 @@ import { withBase } from 'vuepress/client'
import { isLinkHttp } from 'vuepress/shared'
import { useData } from '../../composables/index.js'
const props = defineProps<ThemeHomeBanner>()
const { banner, bannerMask, hero } = defineProps<ThemeHomeBanner>()
const DEFAULT_BANNER = 'https://api.pengzhanbo.cn/wallpaper/bing'
const { isDark, frontmatter: matter } = useData<'home'>()
const mask = computed(() => {
const mask = props.bannerMask ?? matter.value.bannerMask
const mask = bannerMask ?? matter.value.bannerMask
if (typeof mask !== 'object')
return mask || 0
@ -21,17 +21,17 @@ const mask = computed(() => {
})
const bannerStyle = computed(() => {
const banner = props.banner ?? matter.value.banner
const link = banner ? isLinkHttp(banner) ? banner : withBase(banner) : DEFAULT_BANNER
const _banner = banner ?? matter.value.banner
const link = _banner ? isLinkHttp(_banner) ? _banner : withBase(_banner) : DEFAULT_BANNER
return {
'background-image': `url(${link})`,
}
})
const name = computed(() => props.hero?.name ?? matter.value.hero?.name ?? 'Plume')
const tagline = computed(() => props.hero?.tagline ?? matter.value.hero?.tagline ?? 'A VuePress Theme')
const text = computed(() => props.hero?.text ?? matter.value.hero?.text)
const actions = computed(() => props.hero?.actions ?? matter.value.hero?.actions ?? [])
const name = computed(() => hero?.name ?? matter.value.hero?.name ?? 'Plume')
const tagline = computed(() => hero?.tagline ?? matter.value.hero?.tagline ?? 'A VuePress Theme')
const text = computed(() => hero?.text ?? matter.value.hero?.text)
const actions = computed(() => hero?.actions ?? matter.value.hero?.actions ?? [])
</script>
<template>

View File

@ -5,38 +5,38 @@ import { withBase } from 'vuepress/client'
import { isLinkHttp } from 'vuepress/shared'
import { useDarkMode } from '../../composables/index.js'
const props = defineProps<ThemeHomeConfigBase & {
const { backgroundAttachment, backgroundImage, containerClass, full } = defineProps<ThemeHomeConfigBase & {
containerClass?: any
}>()
const isDark = useDarkMode()
const styles = computed(() => {
if (!props.backgroundImage)
if (!backgroundImage)
return null
const image = typeof props.backgroundImage === 'string' ? props.backgroundImage : (props.backgroundImage[isDark.value ? 'dark' : 'light'] ?? props.backgroundImage.light)
const image = typeof backgroundImage === 'string' ? backgroundImage : (backgroundImage[isDark.value ? 'dark' : 'light'] ?? backgroundImage.light)
if (!image)
return null
const link = isLinkHttp(image) ? props.backgroundImage : withBase(image)
const link = isLinkHttp(image) ? backgroundImage : withBase(image)
return {
'background-image': `url(${link})`,
'background-size': 'cover',
'background-position': 'center',
'background-repeat': 'no-repeat',
'background-attachment': props.backgroundAttachment || '',
'background-attachment': backgroundAttachment || '',
}
})
const containerClass = computed(() => normalizeClass(props.containerClass || ''))
const containerClasses = computed(() => normalizeClass(containerClass || ''))
</script>
<template>
<div class="vp-home-box" :class="{ full: props.full }" :style="styles">
<div class="vp-home-box" :class="{ full }" :style="styles">
<slot name="before" />
<div class="container" :class="containerClass">
<div class="container" :class="containerClasses">
<slot />
</div>
<slot name="after" />

View File

@ -6,15 +6,15 @@ import VPLink from '@theme/VPLink.vue'
import { isLinkAbsolute, isLinkHttp } from '@vuepress/helper/client'
import { computed } from 'vue'
const props = defineProps<ThemeHomeFeature>()
const { icon, link, linkText, rel, target, title, details } = defineProps<ThemeHomeFeature>()
const ICONIFY_NAME = /^[\w-]+:[\w-]+$/
const isIconify = computed(() => {
if (typeof props.icon !== 'string' || isLinkAbsolute(props.icon) || isLinkHttp(props.icon)) {
if (typeof icon !== 'string' || isLinkAbsolute(icon) || isLinkHttp(icon)) {
return false
}
return ICONIFY_NAME.test(props.icon)
return ICONIFY_NAME.test(icon)
})
</script>

View File

@ -4,10 +4,10 @@ import VPHomeBox from '@theme/Home/VPHomeBox.vue'
import VPHomeFeature from '@theme/Home/VPHomeFeature.vue'
import { computed } from 'vue'
const props = defineProps<ThemeHomeFeatures>()
const { features, title, description, type, backgroundImage, backgroundAttachment, full, index } = defineProps<ThemeHomeFeatures>()
const grid = computed(() => {
const length = props.features?.length
const length = features?.length
if (!length)
return undefined
@ -32,11 +32,13 @@ const grid = computed(() => {
<VPHomeBox
v-if="features"
class="vp-home-features"
:type="type"
:background-image="backgroundImage"
:background-attachment="backgroundAttachment"
:full="full"
:index="index"
v-bind="{
type,
backgroundAttachment,
backgroundImage,
full,
index,
}"
>
<h2 v-if="title" class="title" v-html="title" />
<p v-if="description" class="description" v-html="description" />
@ -48,15 +50,7 @@ const grid = computed(() => {
class="item"
:class="[grid]"
>
<VPHomeFeature
:icon="feature.icon"
:title="feature.title"
:details="feature.details"
:link="feature.link"
:link-text="feature.linkText"
:rel="feature.rel"
:target="feature.target"
/>
<VPHomeFeature v-bind="feature" />
</div>
</div>
</VPHomeBox>

View File

@ -3,7 +3,8 @@ import type { ThemeHomeHero } from '../../../shared/index.js'
import { effectComponents, effects } from '@internal/home-hero-effects'
import ImageBg from '@theme/background/ImageBg.vue'
import VPButton from '@theme/VPButton.vue'
import { computed, nextTick, onMounted, onUnmounted, watch } from 'vue'
import { hasGlobalComponent } from '@vuepress/helper/client'
import { computed, markRaw, nextTick, onMounted, onUnmounted, resolveComponent, watch } from 'vue'
import { isPlainObject } from 'vuepress/shared'
import { useData } from '../../composables/index.js'
import { inBrowser } from '../../utils/index.js'
@ -15,10 +16,11 @@ const hero = computed(() => props.hero ?? frontmatter.value.hero ?? {})
const actions = computed(() => hero.value.actions ?? [])
const effect = computed(() => {
const effect = props.effect || props.background
if (!effect || !effects.includes(effect))
return null
return effect as typeof effects[number]
if (props.effect)
return props.effect
if (props.background && effects.includes(props.background))
return props.background
return null
})
const effectConfig = computed(() => {
@ -37,6 +39,17 @@ const effectConfig = computed(() => {
return props.effectConfig
})
const realEffectComponent = computed(() => {
if (!effect.value)
return null
if (effectComponents[effect.value])
return markRaw(effectComponents[effect.value])
if (hasGlobalComponent(effect.value))
return resolveComponent(effect.value)
return null
})
function noTransition() {
document.documentElement.classList.add('no-transition')
setTimeout(() => {
@ -85,7 +98,7 @@ onUnmounted(() => {
[effect ?? '']: !!effect,
}"
>
<component :is="effectComponents[effect]" v-if="effect" v-bind="effectConfig" />
<component :is="realEffectComponent" v-if="realEffectComponent" v-bind="effectConfig" />
<ImageBg v-else v-bind="props" />
<div class="hero-container">

View File

@ -5,7 +5,7 @@ import VPImage from '@theme/VPImage.vue'
import { computed } from 'vue'
import { useData } from '../../composables/index.js'
const props = defineProps<ThemeHomeProfile>()
const { name, description, avatar, circle, type, backgroundImage, backgroundAttachment, full, index } = defineProps<ThemeHomeProfile>()
const { theme } = useData()
@ -13,10 +13,10 @@ const rawProfile = computed(() => theme.value.profile)
const profile = computed(() => {
return {
name: props.name || rawProfile.value?.name,
description: props.description || rawProfile.value?.description,
avatar: props.avatar || rawProfile.value?.avatar || rawProfile.value?.url,
circle: props.circle || rawProfile.value?.circle,
name: name || rawProfile.value?.name,
description: description || rawProfile.value?.description,
avatar: avatar || rawProfile.value?.avatar || rawProfile.value?.url,
circle: circle || rawProfile.value?.circle,
}
})
</script>
@ -24,11 +24,7 @@ const profile = computed(() => {
<template>
<VPHomeBox
class="vp-home-profile"
:type="type"
:background-image="backgroundImage"
:background-attachment="backgroundAttachment"
:full="full"
:index="index"
v-bind="{ type, backgroundAttachment, backgroundImage, full, index }"
>
<VPImage v-if="profile.avatar" :image="profile.avatar" :class="{ circle: profile.circle }" />

View File

@ -4,11 +4,9 @@ import VPHomeBox from '@theme/Home/VPHomeBox.vue'
import VPImage from '@theme/VPImage.vue'
import { computed } from 'vue'
const props = defineProps<ThemeHomeTextImage>()
const { width, title, description, list, image, type, backgroundImage, backgroundAttachment, full, index } = defineProps<ThemeHomeTextImage>()
const maxWidth = computed(() => {
const width = props.width
if (typeof width === 'number')
return `${width}px`
@ -19,12 +17,8 @@ const maxWidth = computed(() => {
<template>
<VPHomeBox
class="vp-home-text-image"
:type="type"
:background-image="backgroundImage"
:background-attachment="backgroundAttachment"
:full="full"
:container-class="{ reverse: type === 'text-image' }"
:index="index"
v-bind="{ type, backgroundAttachment, backgroundImage, full, index }"
>
<div class="content-image">
<VPImage :image="image" :style="{ maxWidth }" />
@ -39,7 +33,7 @@ const maxWidth = computed(() => {
<p v-if="description" class="description" v-html="description" />
<ul v-if="list && list.length" class="list">
<li v-for="(item, index) in list" :key="index">
<li v-for="(item, i) in list" :key="i">
<template v-if="typeof item === 'object'">
<h3 v-if="item.title" v-html="item.title" />
<p v-if="item.description" v-html="item.description" />

View File

@ -11,7 +11,7 @@ import { useWindowScroll } from '@vueuse/core'
import { ref, watchPostEffect } from 'vue'
import { useData, useSidebar } from '../../composables/index.js'
const props = defineProps<{
const { isScreenOpen } = defineProps<{
isScreenOpen: boolean
}>()
defineEmits<(e: 'toggleScreen') => void>()
@ -27,7 +27,7 @@ watchPostEffect(() => {
'has-sidebar': hasSidebar.value,
'home': frontmatter.value.pageLayout === 'home',
'top': y.value === 0,
'screen-open': props.isScreenOpen,
'screen-open': isScreenOpen,
}
})
</script>

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
defineProps<{
const { active } = defineProps<{
active: boolean
}>()

View File

@ -9,7 +9,7 @@ import { resolveRouteFullPath } from 'vuepress/client'
import { useData } from '../../composables/index.js'
import { isActive } from '../../utils/index.js'
const props = defineProps<{
const { item } = defineProps<{
item: ResolvedNavItemWithChildren
}>()
@ -20,14 +20,14 @@ function isChildActive(navItem: ResolvedNavItem): boolean {
return isActive(
page.value.path,
resolveRouteFullPath(navItem.link),
!!props.item.activeMatch,
!!item.activeMatch,
)
}
else {
return navItem.items.some(isChildActive)
}
}
const childrenActive = computed(() => isChildActive(props.item))
const childrenActive = computed(() => isChildActive(item))
</script>
<template>

View File

@ -7,7 +7,7 @@ import { resolveRouteFullPath } from 'vuepress/client'
import { useData } from '../../composables/index.js'
import { isActive } from '../../utils/index.js'
defineProps<{
const { item } = defineProps<{
item: ResolvedNavItemWithLink
}>()

View File

@ -8,7 +8,7 @@ import { inBrowser } from '../../utils/index.js'
import '@vuepress/helper/transition/fade-in.css'
defineProps<{
const { open } = defineProps<{
open: boolean
}>()

View File

@ -9,7 +9,7 @@ import { computed, ref } from 'vue'
import '@vuepress/helper/transition/fade-in-height-expand.css'
const props = defineProps<{
const { text, icon, badge, items } = defineProps<{
text: string
icon?: ThemeIcon
badge?: string | ThemeBadge
@ -19,7 +19,7 @@ const props = defineProps<{
const isOpen = ref(false)
const groupId = computed(
() => `nav-screen-menu-group-${props.text.replace(' ', '-').toLowerCase()}`,
() => `nav-screen-menu-group-${text.replace(' ', '-').toLowerCase()}`,
)
function toggle() {

View File

@ -5,7 +5,7 @@ import VPIcon from '@theme/VPIcon.vue'
import VPLink from '@theme/VPLink.vue'
import { inject } from 'vue'
defineProps<{
const { item } = defineProps<{
item: ResolvedNavItemWithLink
}>()

View File

@ -3,7 +3,7 @@ import type { NavItemWithLink, ThemeIcon } from '../../../shared/index.js'
import VPNavScreenMenuGroupLink from '@theme/Nav/VPNavScreenMenuGroupLink.vue'
import VPIcon from '@theme/VPIcon.vue'
defineProps<{
const { icon, text, items } = defineProps<{
icon?: ThemeIcon
text?: string
items: NavItemWithLink[]

View File

@ -5,7 +5,7 @@ import VPIcon from '@theme/VPIcon.vue'
import VPLink from '@theme/VPLink.vue'
import { inject } from 'vue'
defineProps<{
const { item } = defineProps<{
item: ResolvedNavItemWithLink
}>()

View File

@ -3,12 +3,10 @@ import type { CategoryItem, CategoryItemWithPost } from '../../composables/index
import VPCategoriesGroup from '@theme/Posts/VPCategoriesGroup.vue'
import VPLink from '@theme/VPLink.vue'
withDefaults(defineProps<{
const { items, depth = 0 } = defineProps<{
items: (CategoryItem | CategoryItemWithPost)[]
depth?: number
}>(), {
depth: 0,
})
}>()
</script>
<template>

View File

@ -5,12 +5,11 @@ import { computed, onMounted, ref, watch } from 'vue'
import { useRoute } from 'vuepress/client'
import { useData } from '../../composables/index.js'
const props = withDefaults(defineProps<{
const { item, depth = 0 } = defineProps<{
item: CategoryItem
depth?: number
}>(), {
depth: 0,
})
}>()
const { collection } = useData<'page', 'post'>()
const route = useRoute()
const el = ref<HTMLDivElement | null>(null)
@ -28,16 +27,16 @@ const expandDepth = computed(() => {
})
watch(
() => [route.query, props.item, expandDepth.value],
() => [route.query, item, expandDepth.value],
() => {
const id = route.query.id as string
if (!id) {
expand.value = props.depth <= expandDepth.value
expand.value = depth <= expandDepth.value
}
else {
expand.value = hasExpand(props.item, id)
expand.value = hasExpand(item, id)
}
isExpand.value = id ? props.item.id === id : false
isExpand.value = id ? item.id === id : false
},
{ immediate: true },
)

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { useData } from '../../composables/index.js'
defineProps<{
const { page, isFirstPage, isLastPage, pageRange } = defineProps<{
page: number
totalPage: number
isFirstPage: boolean

View File

@ -6,7 +6,7 @@ import { computed, onMounted, ref } from 'vue'
import { withBase } from 'vuepress/client'
import { useData, useInternalLink, useTagColors } from '../../composables/index.js'
const props = defineProps<{
const { post, index } = defineProps<{
post: ThemePostsItem
index: number
}>()
@ -14,9 +14,9 @@ const props = defineProps<{
const isMobile = ref(false)
onMounted(() => {
isMobile.value = _isMobile(navigator.userAgent)
isMobile.value = _isMobile()
window.addEventListener('resize', () => {
isMobile.value = _isMobile(navigator.userAgent)
isMobile.value = _isMobile()
})
})
@ -24,15 +24,15 @@ const { collection } = useData<'page', 'post'>()
const colors = useTagColors()
const { categories: categoriesLink, tags: tagsLink } = useInternalLink()
const createTime = computed(() => props.post.createTime?.split(/\s|T/)[0].replace(/\//g, '-'))
const categoryList = computed(() => props.post.categoryList ?? [])
const createTime = computed(() => post.createTime?.split(/\s|T/)[0].replace(/\//g, '-'))
const categoryList = computed(() => post.categoryList ?? [])
const sticky = computed(() => {
if (typeof props.post.sticky === 'boolean') {
return props.post.sticky
if (typeof post.sticky === 'boolean') {
return post.sticky
}
else if (typeof props.post.sticky === 'number') {
return props.post.sticky >= 0
else if (typeof post.sticky === 'number') {
return post.sticky >= 0
}
return false
})
@ -40,7 +40,7 @@ const sticky = computed(() => {
const tags = computed(() => {
const tagTheme = collection.value?.tagsTheme ?? 'colored'
return (props.post.tags ?? [])
return (post.tags ?? [])
.slice(0, 4)
.map(tag => ({
name: tag,
@ -49,18 +49,18 @@ const tags = computed(() => {
})
const cover = computed<PostsCoverStyle | null>(() => {
if (!props.post.cover)
if (!post.cover)
return null
const opt = collection.value?.postCover ?? 'right'
const options = typeof opt === 'string' ? { layout: opt } : opt
return { layout: 'right', ratio: '4:3', ...options, ...props.post.coverStyle }
return { layout: 'right', ratio: '4:3', ...options, ...post.coverStyle }
})
const coverLayout = computed(() => {
if (isMobile.value)
return 'top'
const layout = cover.value?.layout ?? 'right'
const odd = (props.index + 1) % 2 === 1
const odd = (index + 1) % 2 === 1
if (layout === 'odd-left')
return odd ? 'left' : 'right'
if (layout === 'odd-right')
@ -69,7 +69,7 @@ const coverLayout = computed(() => {
})
const coverCompact = computed(() => {
if (props.post.excerpt || coverLayout.value === 'top')
if (post.excerpt || coverLayout.value === 'top')
return false
return cover.value?.compact ?? false
})

View File

@ -5,7 +5,7 @@ import VPTransitionDrop from '@theme/VPTransitionDrop.vue'
import { computed } from 'vue'
import { usePostListControl } from '../../composables/index.js'
const props = defineProps<{
const { homePosts } = defineProps<{
homePosts?: boolean
}>()
@ -18,7 +18,7 @@ const {
isFirstPage,
isPaginationEnabled,
changePage,
} = usePostListControl(computed(() => !!props.homePosts))
} = usePostListControl(computed(() => !!homePosts))
</script>
<template>

View File

@ -10,7 +10,7 @@ import VPTransitionFadeSlideY from '@theme/VPTransitionFadeSlideY.vue'
import { onBeforeUnmount, watch } from 'vue'
import { forceUpdateCollection, useData } from '../../composables/index.js'
const props = defineProps<{
const { homePosts, collection } = defineProps<{
homePosts?: boolean
type?: string
onlyOnce?: boolean
@ -20,8 +20,8 @@ const props = defineProps<{
const { theme, page } = useData()
watch(
() => [props.homePosts, props.collection],
() => forceUpdateCollection(props.homePosts ? (props.collection || true) : undefined),
() => [homePosts, collection],
() => forceUpdateCollection(homePosts ? (collection || true) : undefined),
{ immediate: true },
)

View File

@ -3,7 +3,7 @@ import VPLink from '@theme/VPLink.vue'
import { useRoute } from 'vuepress/client'
import { usePostsExtract } from '../../composables/index.js'
const props = defineProps<{
const { isLocal } = defineProps<{
isLocal?: boolean
}>()
@ -13,7 +13,7 @@ const { hasPostsExtract, tags, archives, categories } = usePostsExtract()
</script>
<template>
<div v-if="hasPostsExtract" class="vp-posts-nav" :class="{ local: props.isLocal }">
<div v-if="hasPostsExtract" class="vp-posts-nav" :class="{ local: isLocal }">
<VPLink
v-if="tags.link"
class="nav-link"

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import VPLink from '@theme/VPLink.vue'
defineProps<{
const { postList } = defineProps<{
postList: {
title: string
path: string

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import '@vuepress/helper/transition/fade-in.css'
defineProps<{
const { show } = defineProps<{
show: boolean
}>()
</script>

View File

@ -9,7 +9,7 @@ import { useRoute } from 'vuepress/client'
import { useData, usePostsPageData, useSidebar } from '../composables/index.js'
import { inBrowser } from '../utils/index.js'
const props = defineProps<{
const { isNotFound } = defineProps<{
isNotFound?: boolean
}>()
@ -43,7 +43,7 @@ watch(
<template>
<div
id="VPContent" vp-content class="vp-content" :class="{
'has-sidebar': hasSidebar && !props.isNotFound,
'has-sidebar': hasSidebar && !isNotFound,
'is-home': frontmatter.pageLayout === 'home',
}"
>

View File

@ -2,7 +2,7 @@
import { computed } from 'vue'
import { useData } from '../composables/index.js'
defineProps<{
const { title, anchor } = defineProps<{
title?: string
anchor: string
}>()

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import type { MenuItem } from '../composables/index.js'
defineProps<{
const { headers, root } = defineProps<{
headers: MenuItem[]
root?: boolean
}>()

View File

@ -2,7 +2,7 @@
import { ref } from 'vue'
import { useData, useEncryptCompare } from '../composables/index.js'
const props = defineProps<{
const { global, info } = defineProps<{
global?: boolean
info?: string
}>()
@ -18,7 +18,7 @@ async function onSubmit() {
if (unlocking.value)
return
const compare = props.global ? compareGlobal : comparePage
const compare = global ? compareGlobal : comparePage
unlocking.value = true
const result = await compare(password.value)
unlocking.value = false

View File

@ -6,7 +6,7 @@ import VPMenu from '@theme/VPMenu.vue'
import { ref } from 'vue'
import { useFlyout } from '../composables/index.js'
defineProps<{
const { prefixIcon, icon, button, label, items, badge } = defineProps<{
prefixIcon?: ThemeIcon
icon?: any
button?: string

View File

@ -2,7 +2,7 @@
import type { FriendGroup } from '../../shared/index.js'
import VPFriendsItem from '@theme/VPFriendsItem.vue'
defineProps<{
const { group } = defineProps<{
group: FriendGroup
}>()
</script>

View File

@ -6,7 +6,7 @@ import { computed } from 'vue'
import { useDarkMode } from '../composables/index.js'
import VPSocialLinks from './VPSocialLinks.vue'
const props = defineProps<{
const { friend } = defineProps<{
friend: FriendsItem
}>()
@ -21,9 +21,9 @@ function getStyle(name: string, color?: string | { light: string, dark: string }
const friendStyle = computed(() => {
return {
...getStyle('--vp-friends-text-color', props.friend.color),
...getStyle('--vp-friends-bg-color', props.friend.backgroundColor),
...getStyle('--vp-friends-name-color', props.friend.nameColor),
...getStyle('--vp-friends-text-color', friend.color),
...getStyle('--vp-friends-bg-color', friend.backgroundColor),
...getStyle('--vp-friends-name-color', friend.nameColor),
}
})
</script>

View File

@ -6,7 +6,7 @@ import VPIconImage from '@theme/VPIconImage.vue'
import { computed } from 'vue'
import { isLinkHttp } from 'vuepress/shared'
const props = defineProps<{
const { provider, name, size, color, extra } = defineProps<{
provider?: 'iconify' | 'iconfont' | 'fontawesome'
name: string | { svg: string }
size?: string | number
@ -18,20 +18,20 @@ declare const __MD_POWER_ICON_PROVIDER__: 'iconify' | 'iconfont' | 'fontawesome'
declare const __MD_POWER_ICON_PREFIX__: string
const type = computed(() => {
const provider = props.provider || __MD_POWER_ICON_PROVIDER__
// name -> https://example.com/icon.svg
// name -> /icon.svg
if (typeof props.name === 'string' && (isLinkHttp(props.name) || props.name[0] === '/')) {
if (typeof name === 'string' && (isLinkHttp(name) || name[0] === '/')) {
return 'link'
}
// name -> { svg: '<svg></svg>' }
if (typeof props.name === 'object' && !!props.name.svg) {
if (typeof name === 'object' && !!name.svg) {
return 'svg'
}
if (provider === 'iconfont' || provider === 'fontawesome') {
return provider
const _provider = provider || __MD_POWER_ICON_PROVIDER__
if (_provider === 'iconfont' || _provider === 'fontawesome') {
return _provider
}
return 'iconify'
@ -43,8 +43,7 @@ function parseSize(size: string | number): string {
return String(size)
}
const size = computed(() => {
const size = props.size
const rect = computed(() => {
if (!size)
return undefined
@ -56,9 +55,9 @@ const size = computed(() => {
return { width, height: height || width }
})
const binding = computed(() => ({
name: props.name as string,
color: props.color,
size: size.value,
name: name as string,
color,
size: rect.value,
prefix: __MD_POWER_ICON_PREFIX__ as any,
}))
</script>
@ -66,7 +65,7 @@ const binding = computed(() => ({
<template>
<VPIconImage
v-if="type === 'link' || type === 'svg'"
:type="type" :name="name" :color="color" :size="size"
:type="type" :name="name" :color="color" :size="rect"
/>
<VPIconfont
v-else-if="type === 'iconfont'"

View File

@ -2,7 +2,7 @@
import type { FontAwesomePrefix } from 'vuepress-plugin-md-power/client'
import { computed } from 'vue'
const props = defineProps<{
const { name, size, color, prefix, extra } = defineProps<{
name: string
size?: { width?: string, height?: string }
color?: string
@ -25,20 +25,19 @@ const configs: Record<string, FontAwesomePrefix[]> = {
}
const iconName = computed(() => {
const icon = props.name.includes(':') ? props.name : `${props.prefix || 'fas'}:${props.name}`
const [type, name] = icon.split(':')
let prefix = 'solid'
const icon = name.includes(':') ? name : `${prefix || 'fas'}:${name}`
const [type, iconName] = icon.split(':')
let _prefix = 'solid'
for (const [key, alias] of Object.entries(configs)) {
if (alias.includes(type as FontAwesomePrefix)) {
prefix = key
_prefix = key
break
}
}
return `${prefix.split(' ').map(v => `fa-${v.trim()}`).join(' ')} fa-${name}`
return `${_prefix.split(' ').map(v => `fa-${v.trim()}`).join(' ')} fa-${iconName}`
})
const extraClasses = computed(() => {
const extra = props.extra
if (!extra)
return []
return extra.split(' ').map(v => v.trim().startsWith('fa-') ? v : `fa-${v}`)

View File

@ -3,7 +3,7 @@ import { computed } from 'vue'
import { withBase } from 'vuepress/client'
import { isLinkHttp } from 'vuepress/shared'
const props = defineProps<{
const { type, name, color, size } = defineProps<{
type: 'link' | 'svg'
name: string | { svg: string }
color?: string
@ -11,14 +11,14 @@ const props = defineProps<{
}>()
const svg = computed(() => {
if (props.type === 'svg' && typeof props.name === 'object' && 'svg' in props.name) {
return props.name.svg
if (type === 'svg' && typeof name === 'object' && 'svg' in name) {
return name.svg
}
return ''
})
const link = computed(() => {
if (props.type === 'link') {
const link = props.name as string
if (type === 'link') {
const link = name as string
return isLinkHttp(link) ? link : withBase(link)
}
return ''

View File

@ -1,20 +1,16 @@
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{
const { name, size, color, prefix } = defineProps<{
name: string
size?: { width?: string, height?: string }
color?: string
prefix?: string
}>()
const prefix = computed(() => props.prefix || 'iconfont icon-')
</script>
<template>
<i
class="vp-icon"
:class="`${prefix}${name}`"
:class="`${prefix || 'iconfont icon-'}${name}`"
:style="{ color, 'font-size': size?.height || '1em' }"
data-provider="iconfont"
aria-hidden

View File

@ -9,7 +9,7 @@ defineOptions({
inheritAttrs: false,
})
const props = defineProps<{
const { name, size, color, prefix, extra } = defineProps<{
name: string
size?: { width?: string, height?: string }
color?: string
@ -23,10 +23,9 @@ const loaded = ref(false)
const iconsData = useIconsData()
const iconName = computed(() => {
const name = props.name
if (name.includes(':'))
return name
return props.prefix ? `${props.prefix}:${name}` : name
return prefix ? `${prefix}:${name}` : name
})
const localIconName = computed(() => iconsData.value[iconName.value])
@ -37,13 +36,13 @@ async function loadRemoteIcon() {
if (!localIconName.value) {
loaded.value = false
icon.value = await loadIcon(props.name)
icon.value = await loadIcon(name)
}
loaded.value = true
}
if (!__VUEPRESS_SSR__)
watch(() => props.name, loadRemoteIcon, { immediate: true })
watch(() => name, loadRemoteIcon, { immediate: true })
</script>
<template>

View File

@ -4,13 +4,12 @@ import { computed } from 'vue'
import { withBase } from 'vuepress/client'
import { numToUnit } from '../utils/index.js'
const props = defineProps<{
const { image, alt } = defineProps<{
image: ThemeImage
alt?: string
}>()
const styles = computed(() => {
const image = props.image
if (!image || typeof image === 'string')
return ''
if (!image.width || !image.height)

View File

@ -4,7 +4,7 @@ import { useWindowScroll } from '@vueuse/core'
import { computed, onMounted, ref } from 'vue'
import { useData, useHeaders, usePostsPageData, useSidebar } from '../composables/index.js'
const props = defineProps<{
const { open, showOutline } = defineProps<{
open: boolean
showOutline: boolean
}>()
@ -39,7 +39,7 @@ const classes = computed(() => {
'fixed': empty.value,
'reached-top': y.value >= navHeight.value,
'is-posts': isPosts.value && !isPostsLayout.value,
'with-outline': !props.showOutline,
'with-outline': !showOutline,
}
})

View File

@ -7,7 +7,7 @@ import { useData } from '../composables/index.js'
import '@vuepress/helper/transition/fade-in-scale-up.css'
const props = defineProps<{
const { headers, navHeight } = defineProps<{
headers: MenuItem[]
navHeight: number
}>()
@ -18,7 +18,7 @@ const vh = ref(0)
const items = ref<HTMLDivElement>()
const btn = ref<HTMLButtonElement>()
watch(() => props.headers, () => {
watch(() => headers, () => {
open.value = false
})
@ -28,7 +28,7 @@ onClickOutside(items, () => {
function toggle() {
open.value = !open.value
vh.value = window.innerHeight + Math.min(window.scrollY - props.navHeight, 0)
vh.value = window.innerHeight + Math.min(window.scrollY - navHeight, 0)
}
function onItemClick(e: Event) {

View File

@ -2,7 +2,7 @@
import VPMenuGroup from '@theme/VPMenuGroup.vue'
import VPMenuLink from '@theme/VPMenuLink.vue'
defineProps<{
const { items } = defineProps<{
items?: any[]
}>()
</script>

View File

@ -2,7 +2,7 @@
import VPIcon from '@theme/VPIcon.vue'
import VPMenuLink from '@theme/VPMenuLink.vue'
defineProps<{
const { text, icon, items } = defineProps<{
text?: string
icon?: string | { svg: string }
items: any[]

View File

@ -6,7 +6,7 @@ import { resolveRouteFullPath } from 'vuepress/client'
import { useData } from '../composables/index.js'
import { isActive } from '../utils/index.js'
defineProps<{
const { item } = defineProps<{
item: any
}>()

View File

@ -7,7 +7,7 @@ import { useRoutePath } from 'vuepress/client'
import { useData, useSidebar } from '../composables/index.js'
import { inBrowser } from '../utils/index.js'
const props = defineProps<{
const { open } = defineProps<{
open: boolean
}>()
@ -20,9 +20,9 @@ const navEl = ref<HTMLElement | null>(null)
const isLocked = useScrollLock(inBrowser ? document.body : null)
watch(
[() => props.open, navEl],
[() => open, navEl],
() => {
if (props.open) {
if (open) {
isLocked.value = true
navEl.value?.focus()
}

View File

@ -3,7 +3,7 @@ import type { ResolvedSidebarItem } from '../../shared/index.js'
import VPSidebarItem from '@theme/VPSidebarItem.vue'
import { onBeforeUnmount, onMounted, ref } from 'vue'
defineProps<{
const { items } = defineProps<{
items: ResolvedSidebarItem[]
}>()

View File

@ -9,7 +9,7 @@ import { useSidebarControl } from '../composables/index.js'
import '@vuepress/helper/transition/fade-in-height-expand.css'
const props = defineProps<{
const { item, depth } = defineProps<{
item: ResolvedSidebarItem
depth: number
}>()
@ -22,7 +22,7 @@ const {
hasActiveLink,
hasChildren,
toggle,
} = useSidebarControl(computed(() => props.item))
} = useSidebarControl(computed(() => item))
const sectionTag = computed(() => (hasChildren.value ? 'section' : `div`))
@ -31,17 +31,17 @@ const linkTag = computed(() => (isLink.value ? 'a' : 'div'))
const textTag = computed(() => {
return !hasChildren.value
? 'p'
: props.depth + 2 === 7
: depth + 2 === 7
? 'p'
: `h${props.depth + 2}`
: `h${depth + 2}`
})
const itemRole = computed(() => (isLink.value ? undefined : 'button'))
const isSeparator = computed(() => props.item.link?.startsWith('---'))
const isSeparator = computed(() => item.link?.startsWith('---'))
const classes = computed(() => [
[`level-${props.depth}`],
[`level-${depth}`],
{ collapsible: collapsible.value },
{ collapsed: collapsed.value },
{ 'is-link': isLink.value },
@ -53,13 +53,13 @@ function onItemInteraction(e: MouseEvent | Event) {
if ('key' in e && e.key !== 'Enter')
return
if (!props.item.link) {
if (!item.link) {
toggle()
}
}
function onCaretClick() {
if (props.item.link) {
if (item.link) {
toggle()
}
}

View File

@ -2,16 +2,16 @@
import type { SocialLinkIcon } from '../../shared/index.js'
import { computed } from 'vue'
const props = defineProps<{
const { icon, link, ariaLabel } = defineProps<{
icon: SocialLinkIcon
link: string
ariaLabel?: string
}>()
const svg = computed(() => {
if (typeof props.icon === 'object')
return props.icon.svg
return `<span class="vpi-social-${props.icon}" />`
if (typeof icon === 'object')
return icon.svg
return `<span class="vpi-social-${icon}" />`
})
</script>

View File

@ -2,7 +2,7 @@
import type { SocialLink as SocialLinkType } from '../../shared/index.js'
import VPSocialLink from '@theme/VPSocialLink.vue'
defineProps<{
const { links } = defineProps<{
links: SocialLinkType[]
}>()
</script>

View File

@ -7,10 +7,7 @@ interface Props {
duration?: number
appear?: boolean
}
const props = withDefaults(defineProps<Props>(), {
delay: 0,
duration: 0.25,
})
const { delay = 0, duration = 0.25, appear } = defineProps<Props>()
const { theme } = useData()
@ -36,7 +33,7 @@ function setStyle(item: Element) {
_transition = value && !value.includes('all') ? `${value || ''}, ` : ' '
}
el.style.transition = `${_transition}transform ${props.duration}s ease-in-out ${props.delay}s, opacity ${props.duration}s ease-in-out ${props.delay}s`
el.style.transition = `${_transition}transform ${duration}s ease-in-out ${delay}s, opacity ${duration}s ease-in-out ${delay}s`
}
function unsetStyle(item: Element) {

View File

@ -2,17 +2,14 @@
import type { ThemeBadge } from '../../../shared/index.js'
import { computed } from 'vue'
const props = withDefaults(defineProps<ThemeBadge>(), {
type: 'tip',
borderColor: 'transparent',
})
const { type = 'tip', text, color, bgColor, borderColor = 'transparent' } = defineProps<ThemeBadge>()
const customStyle = computed(() => {
if (props.color || props.bgColor) {
if (color || bgColor) {
return {
color: props.color,
backgroundColor: props.bgColor,
borderColor: props.borderColor,
color,
backgroundColor: bgColor,
borderColor,
}
}
return {}

View File

@ -2,19 +2,19 @@
import VPIcon from '@theme/VPIcon.vue'
import { computed } from 'vue'
const props = defineProps<{
const { title, icon = '' } = defineProps<{
title?: string
icon?: string | { svg: string }
}>()
const icon = computed<string | { svg: string } | undefined>(() => {
if (props.icon?.[0] === '{') {
const iconName = computed<string | { svg: string }>(() => {
if (typeof icon === 'string' && icon?.[0] === '{') {
try {
return JSON.parse(icon) as { svg: string }
}
catch {}
}
return props.icon
return icon
})
</script>
@ -22,7 +22,7 @@ const icon = computed<string | { svg: string } | undefined>(() => {
<article class="vp-card-wrapper">
<slot name="title">
<p v-if="title || icon" class="title">
<VPIcon v-if="icon" :name="icon" />
<VPIcon v-if="icon" :name="iconName" />
<span v-if="title" class="text" v-html="title" />
</p>
</slot>

View File

@ -2,7 +2,7 @@
import { useMediaQuery } from '@vueuse/core'
import { onMounted, ref, toValue, watch } from 'vue'
const props = defineProps<{
const { cols } = defineProps<{
cols?: string | number | { sm?: number, md?: number, lg?: number }
}>()
@ -12,13 +12,13 @@ const repeat = ref(1)
function resolveCols() {
const reset = { sm: 1, md: 2, lg: 2 }
if (!props.cols)
if (!cols)
return reset
if (typeof props.cols === 'number' || typeof props.cols === 'string') {
const cols = Number(props.cols)
return { sm: cols, md: cols, lg: cols }
if (typeof cols === 'number' || typeof cols === 'string') {
const res = Number(cols)
return { sm: res, md: res, lg: res }
}
return { ...reset, ...toValue(props.cols) }
return { ...reset, ...toValue(cols) }
}
function getRepeat() {
@ -30,7 +30,7 @@ function getRepeat() {
return cols.sm
}
watch(() => [md.value, lg.value, props.cols], () => {
watch([md, lg, () => cols], () => {
repeat.value = getRepeat()
})

View File

@ -3,13 +3,10 @@ import type { VNode } from 'vue'
import { useDebounceFn, useMediaQuery, useResizeObserver } from '@vueuse/core'
import { cloneVNode, computed, markRaw, mergeProps, nextTick, onMounted, ref, shallowRef, useId, watch } from 'vue'
const props = withDefaults(defineProps<{
const { cols = { sm: 2, md: 2, lg: 3 }, gap = 16 } = defineProps<{
cols?: number | { sm?: number, md?: number, lg?: number }
gap?: number
}>(), {
cols: () => ({ sm: 2, md: 2, lg: 3 }),
gap: 16,
})
}>()
const slots = defineSlots<{ default: () => VNode[] | null }>()
const uuid = useId()
@ -29,16 +26,16 @@ const rawList = computed(() => {
function resolveColumnsLength() {
let length = 1
if (typeof props.cols === 'number') {
length = props.cols
if (typeof cols === 'number') {
length = cols
}
else if (typeof props.cols === 'object') {
else if (typeof cols === 'object') {
if (isLg.value)
length = props.cols.lg || 3
length = cols.lg || 3
else if (isMd.value)
length = props.cols.md || 2
length = cols.md || 2
else
length = props.cols.sm || 2
length = cols.sm || 2
}
columnsLength.value = Number(length)
@ -62,7 +59,7 @@ async function drawColumns() {
const index = heights.indexOf(Math.min(...heights))
columns[index].push(item)
heights[index] += height + props.gap
heights[index] += height + gap
}
columnsList.value = columns
}
@ -71,7 +68,7 @@ onMounted(() => {
if (__VUEPRESS_SSR__)
return
watch(() => [isMd.value, isLg.value, props.cols], resolveColumnsLength, { immediate: true })
watch([isMd, isLg, () => cols], resolveColumnsLength, { immediate: true })
drawColumns()
const debounceDraw = useDebounceFn(drawColumns)
@ -81,9 +78,9 @@ onMounted(() => {
</script>
<template>
<div ref="masonry" class="vp-card-masonry" :class="[`cols-${columnsLength}`]" :style="{ 'grid-gap': `${props.gap}px`, '--card-masonry-cols': columnsLength }" data-allow-mismatch>
<div ref="masonry" class="vp-card-masonry" :class="[`cols-${columnsLength}`]" :style="{ 'grid-gap': `${gap}px`, '--card-masonry-cols': columnsLength }" data-allow-mismatch>
<ClientOnly>
<div v-for="(column, index) in columnsList" :key="`${uuid}-${index}`" class="card-masonry-item" :style="{ gap: `${props.gap}px` }">
<div v-for="(column, index) in columnsList" :key="`${uuid}-${index}`" class="card-masonry-item" :style="{ gap: `${gap}px` }">
<component :is="item" v-for="item in column" :key="item.props!.class" />
</div>
</ClientOnly>

View File

@ -2,7 +2,7 @@
import { computed } from 'vue'
import { usePageLang, withBase } from 'vuepress/client'
const props = defineProps<{
const { image, title, description, href, author, date, width, center } = defineProps<{
image: string
title?: string
description?: string
@ -15,42 +15,40 @@ const props = defineProps<{
const lang = usePageLang()
const date = computed(() => {
if (!props.date)
const dateStr = computed(() => {
if (!date)
return ''
const date = props.date instanceof Date ? props.date : new Date(props.date)
const instance = date instanceof Date ? date : new Date(date)
const intl = new Intl.DateTimeFormat(
lang.value,
{ year: 'numeric', month: 'short', day: 'numeric' },
)
return intl.format(date)
return intl.format(instance)
})
const styles = computed(() => {
const width = props.width
? String(Number(props.width)) === String(props.width)
? `${props.width}px`
: props.width
: undefined
return { width }
})
const styles = computed(() => ({
width: width
? String(Number(width)) === String(width)
? `${width}px`
: width
: undefined,
}))
</script>
<template>
<div class="vp-image-card" :style="styles" :class="{ center }">
<div class="image-container">
<img :src="withBase(image)" :alt="title" loading="lazy">
<div v-if="title || author || date || description" class="image-info">
<div v-if="title || author || dateStr || description" class="image-info">
<h3 v-if="title" class="title">
<a v-if="href" :href="href" target="_blank" rel="noopener noreferrer" class="no-icon">{{ title }}</a>
<span v-else>{{ title }}</span>
</h3>
<p v-if="author || date" class="copyright">
<span v-if="author">{{ author }}</span>
<span v-if="author && date"> | </span>
<span v-if="date">{{ date }}</span>
<span v-if="author && dateStr"> | </span>
<span v-if="dateStr">{{ dateStr }}</span>
</p>
<p v-if="description" class="description">
{{ description }}

View File

@ -2,7 +2,7 @@
import VPIcon from '@theme/VPIcon.vue'
import VPLink from '@theme/VPLink.vue'
defineProps<{
const { href, title, icon, description, target, rel } = defineProps<{
href: string
title?: string
icon?: string | { svg: string }