feat(config, theme): add clientOnly marker

pull/4663/head
Yuxuan Zhang 6 months ago
parent 0267dcafa2
commit 82e59e763d
No known key found for this signature in database
GPG Key ID: 6910B04F3351EF7D

@ -1,7 +1,18 @@
import { defineComponent, onMounted, ref } from 'vue' import { defineComponent, onMounted, ref } from 'vue'
export const ClientOnly = defineComponent({ export const ClientOnly = defineComponent({
setup(_, { slots }) { props: {
isClientOnly: {
type: Boolean,
default: true
}
},
setup({ isClientOnly }, { slots }) {
if (isClientOnly) console.log({ isClientOnly, slots })
// Programmatically determine if this component should be
// client-only based on the presence of the isClientOnly attribute.
if (!isClientOnly) return () => slots.default?.() || null
const show = ref(false) const show = ref(false)
onMounted(() => { onMounted(() => {

@ -2,29 +2,32 @@
import { useData } from '../composables/data' import { useData } from '../composables/data'
import VPNavBarMenuLink from './VPNavBarMenuLink.vue' import VPNavBarMenuLink from './VPNavBarMenuLink.vue'
import VPNavBarMenuGroup from './VPNavBarMenuGroup.vue' import VPNavBarMenuGroup from './VPNavBarMenuGroup.vue'
import { isClientOnly } from '../../shared'
const { theme } = useData() const { theme } = useData()
</script> </script>
<template> <template>
<nav <ClientOnly :isClientOnly="isClientOnly(theme.nav)">
v-if="theme.nav" <nav
aria-labelledby="main-nav-aria-label" v-if="theme.nav"
class="VPNavBarMenu" aria-labelledby="main-nav-aria-label"
> class="VPNavBarMenu"
<span id="main-nav-aria-label" class="visually-hidden"> >
Main Navigation <span id="main-nav-aria-label" class="visually-hidden">
</span> Main Navigation
<template v-for="item in theme.nav" :key="JSON.stringify(item)"> </span>
<VPNavBarMenuLink v-if="'link' in item" :item="item" /> <template v-for="item in theme.nav" :key="JSON.stringify(item)">
<component <VPNavBarMenuLink v-if="'link' in item" :item="item" />
v-else-if="'component' in item" <component
:is="item.component" v-else-if="'component' in item"
v-bind="item.props" :is="item.component"
/> v-bind="item.props"
<VPNavBarMenuGroup v-else :item="item" /> />
</template> <VPNavBarMenuGroup v-else :item="item" />
</nav> </template>
</nav>
</ClientOnly>
</template> </template>
<style scoped> <style scoped>

@ -4,8 +4,9 @@ import { inBrowser } from 'vitepress'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { useSidebar } from '../composables/sidebar' import { useSidebar } from '../composables/sidebar'
import VPSidebarGroup from './VPSidebarGroup.vue' import VPSidebarGroup from './VPSidebarGroup.vue'
import { isClientOnly } from '../../shared'
const { sidebarGroups, hasSidebar } = useSidebar() const { sidebar, sidebarGroups, hasSidebar, isSidebarClientOnly } = useSidebar()
const props = defineProps<{ const props = defineProps<{
open: boolean open: boolean
@ -38,27 +39,18 @@ watch(
</script> </script>
<template> <template>
<aside <aside v-if="hasSidebar" class="VPSidebar" :class="{ open }" ref="navEl" @click.stop>
v-if="hasSidebar"
class="VPSidebar"
:class="{ open }"
ref="navEl"
@click.stop
>
<div class="curtain" /> <div class="curtain" />
<nav <nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1">
class="nav"
id="VPSidebarNav"
aria-labelledby="sidebar-aria-label"
tabindex="-1"
>
<span class="visually-hidden" id="sidebar-aria-label"> <span class="visually-hidden" id="sidebar-aria-label">
Sidebar Navigation Sidebar Navigation
</span> </span>
<slot name="sidebar-nav-before" /> <slot name="sidebar-nav-before" />
<VPSidebarGroup :items="sidebarGroups" :key="key" /> <ClientOnly :is-client-only="isSidebarClientOnly || isClientOnly(sidebar) || isClientOnly(sidebarGroups)">
<VPSidebarGroup :items="sidebarGroups" :key="key" />
</ClientOnly>
<slot name="sidebar-nav-after" /> <slot name="sidebar-nav-after" />
</nav> </nav>
</aside> </aside>

@ -2,6 +2,7 @@
import type { DefaultTheme } from 'vitepress/theme' import type { DefaultTheme } from 'vitepress/theme'
import { onBeforeUnmount, onMounted, ref } from 'vue' import { onBeforeUnmount, onMounted, ref } from 'vue'
import VPSidebarItem from './VPSidebarItem.vue' import VPSidebarItem from './VPSidebarItem.vue'
import { isClientOnly } from '../../shared'
defineProps<{ defineProps<{
items: DefaultTheme.SidebarItem[] items: DefaultTheme.SidebarItem[]
@ -33,7 +34,9 @@ onBeforeUnmount(() => {
class="group" class="group"
:class="{ 'no-transition': disableTransition }" :class="{ 'no-transition': disableTransition }"
> >
<VPSidebarItem :item="item" :depth="0" /> <ClientOnly :isClientOnly="isClientOnly(item)">
<VPSidebarItem :item="item" :depth="0" />
</ClientOnly>
</div> </div>
</template> </template>

@ -3,6 +3,7 @@ import type { DefaultTheme } from 'vitepress/theme'
import { computed } from 'vue' import { computed } from 'vue'
import { useSidebarControl } from '../composables/sidebar' import { useSidebarControl } from '../composables/sidebar'
import VPLink from './VPLink.vue' import VPLink from './VPLink.vue'
import { isClientOnly } from '../../shared'
const props = defineProps<{ const props = defineProps<{
item: DefaultTheme.SidebarItem item: DefaultTheme.SidebarItem
@ -55,56 +56,32 @@ function onCaretClick() {
</script> </script>
<template> <template>
<component :is="sectionTag" class="VPSidebarItem" :class="classes"> <ClientOnly :isClientOnly="isClientOnly(item)">
<div <component :is="sectionTag" class="VPSidebarItem" :class="classes">
v-if="item.text" <div v-if="item.text" class="item" :role="itemRole" v-on="item.items
class="item"
:role="itemRole"
v-on="
item.items
? { click: onItemInteraction, keydown: onItemInteraction } ? { click: onItemInteraction, keydown: onItemInteraction }
: {} : {}
" " :tabindex="item.items && 0">
:tabindex="item.items && 0" <div class="indicator" />
>
<div class="indicator" /> <VPLink v-if="item.link" :tag="linkTag" class="link" :href="item.link" :rel="item.rel" :target="item.target">
<component :is="textTag" class="text" v-html="item.text" />
<VPLink </VPLink>
v-if="item.link" <component v-else :is="textTag" class="text" v-html="item.text" />
:tag="linkTag"
class="link" <div v-if="item.collapsed != null && item.items && item.items.length" class="caret" role="button"
:href="item.link" aria-label="toggle section" @click="onCaretClick" @keydown.enter="onCaretClick" tabindex="0">
:rel="item.rel" <span class="vpi-chevron-right caret-icon" />
:target="item.target" </div>
>
<component :is="textTag" class="text" v-html="item.text" />
</VPLink>
<component v-else :is="textTag" class="text" v-html="item.text" />
<div
v-if="item.collapsed != null && item.items && item.items.length"
class="caret"
role="button"
aria-label="toggle section"
@click="onCaretClick"
@keydown.enter="onCaretClick"
tabindex="0"
>
<span class="vpi-chevron-right caret-icon" />
</div> </div>
</div>
<div v-if="item.items && item.items.length" class="items">
<div v-if="item.items && item.items.length" class="items"> <template v-if="depth < 5">
<template v-if="depth < 5"> <VPSidebarItem v-for="i in item.items" :key="i.text" :item="i" :depth="depth + 1" />
<VPSidebarItem </template>
v-for="i in item.items" </div>
:key="i.text" </component>
:item="i" </ClientOnly>
:depth="depth + 1"
/>
</template>
</div>
</component>
</template> </template>
<style scoped> <style scoped>
@ -122,7 +99,7 @@ function onCaretClick() {
width: 100%; width: 100%;
} }
.VPSidebarItem.collapsible > .item { .VPSidebarItem.collapsible>.item {
cursor: pointer; cursor: pointer;
} }
@ -136,10 +113,10 @@ function onCaretClick() {
transition: background-color 0.25s; transition: background-color 0.25s;
} }
.VPSidebarItem.level-2.is-active > .item > .indicator, .VPSidebarItem.level-2.is-active>.item>.indicator,
.VPSidebarItem.level-3.is-active > .item > .indicator, .VPSidebarItem.level-3.is-active>.item>.indicator,
.VPSidebarItem.level-4.is-active > .item > .indicator, .VPSidebarItem.level-4.is-active>.item>.indicator,
.VPSidebarItem.level-5.is-active > .item > .indicator { .VPSidebarItem.level-5.is-active>.item>.indicator {
background-color: var(--vp-c-brand-1); background-color: var(--vp-c-brand-1);
} }
@ -171,36 +148,36 @@ function onCaretClick() {
color: var(--vp-c-text-2); color: var(--vp-c-text-2);
} }
.VPSidebarItem.level-0.is-link > .item > .link:hover .text, .VPSidebarItem.level-0.is-link>.item>.link:hover .text,
.VPSidebarItem.level-1.is-link > .item > .link:hover .text, .VPSidebarItem.level-1.is-link>.item>.link:hover .text,
.VPSidebarItem.level-2.is-link > .item > .link:hover .text, .VPSidebarItem.level-2.is-link>.item>.link:hover .text,
.VPSidebarItem.level-3.is-link > .item > .link:hover .text, .VPSidebarItem.level-3.is-link>.item>.link:hover .text,
.VPSidebarItem.level-4.is-link > .item > .link:hover .text, .VPSidebarItem.level-4.is-link>.item>.link:hover .text,
.VPSidebarItem.level-5.is-link > .item > .link:hover .text { .VPSidebarItem.level-5.is-link>.item>.link:hover .text {
color: var(--vp-c-brand-1); color: var(--vp-c-brand-1);
} }
.VPSidebarItem.level-0.has-active > .item > .text, .VPSidebarItem.level-0.has-active>.item>.text,
.VPSidebarItem.level-1.has-active > .item > .text, .VPSidebarItem.level-1.has-active>.item>.text,
.VPSidebarItem.level-2.has-active > .item > .text, .VPSidebarItem.level-2.has-active>.item>.text,
.VPSidebarItem.level-3.has-active > .item > .text, .VPSidebarItem.level-3.has-active>.item>.text,
.VPSidebarItem.level-4.has-active > .item > .text, .VPSidebarItem.level-4.has-active>.item>.text,
.VPSidebarItem.level-5.has-active > .item > .text, .VPSidebarItem.level-5.has-active>.item>.text,
.VPSidebarItem.level-0.has-active > .item > .link > .text, .VPSidebarItem.level-0.has-active>.item>.link>.text,
.VPSidebarItem.level-1.has-active > .item > .link > .text, .VPSidebarItem.level-1.has-active>.item>.link>.text,
.VPSidebarItem.level-2.has-active > .item > .link > .text, .VPSidebarItem.level-2.has-active>.item>.link>.text,
.VPSidebarItem.level-3.has-active > .item > .link > .text, .VPSidebarItem.level-3.has-active>.item>.link>.text,
.VPSidebarItem.level-4.has-active > .item > .link > .text, .VPSidebarItem.level-4.has-active>.item>.link>.text,
.VPSidebarItem.level-5.has-active > .item > .link > .text { .VPSidebarItem.level-5.has-active>.item>.link>.text {
color: var(--vp-c-text-1); color: var(--vp-c-text-1);
} }
.VPSidebarItem.level-0.is-active > .item .link > .text, .VPSidebarItem.level-0.is-active>.item .link>.text,
.VPSidebarItem.level-1.is-active > .item .link > .text, .VPSidebarItem.level-1.is-active>.item .link>.text,
.VPSidebarItem.level-2.is-active > .item .link > .text, .VPSidebarItem.level-2.is-active>.item .link>.text,
.VPSidebarItem.level-3.is-active > .item .link > .text, .VPSidebarItem.level-3.is-active>.item .link>.text,
.VPSidebarItem.level-4.is-active > .item .link > .text, .VPSidebarItem.level-4.is-active>.item .link>.text,
.VPSidebarItem.level-5.is-active > .item .link > .text { .VPSidebarItem.level-5.is-active>.item .link>.text {
color: var(--vp-c-brand-1); color: var(--vp-c-brand-1);
} }
@ -233,7 +210,9 @@ function onCaretClick() {
} }
.VPSidebarItem.collapsed .caret-icon { .VPSidebarItem.collapsed .caret-icon {
transform: rotate(0)/*rtl:rotate(180deg)*/; transform: rotate(0)
/*rtl:rotate(180deg)*/
;
} }
.VPSidebarItem.level-1 .items, .VPSidebarItem.level-1 .items,

@ -11,7 +11,7 @@ import {
type ComputedRef, type ComputedRef,
type Ref type Ref
} from 'vue' } from 'vue'
import { isActive } from '../../shared' import { isActive, isClientOnly } from '../../shared'
import { import {
hasActiveLink as containsActiveLink, hasActiveLink as containsActiveLink,
getSidebar, getSidebar,
@ -96,6 +96,7 @@ export function useSidebar() {
hasAside, hasAside,
leftAside, leftAside,
isSidebarEnabled, isSidebarEnabled,
isSidebarClientOnly: isClientOnly(theme.value.sidebar),
open, open,
close, close,
toggle toggle

@ -17,6 +17,7 @@ import type { RawConfigExports, SiteConfig, UserConfig } from './siteConfig'
export { resolvePages } from './plugins/dynamicRoutesPlugin' export { resolvePages } from './plugins/dynamicRoutesPlugin'
export * from './siteConfig' export * from './siteConfig'
export { clientOnly, isClientOnly } from './shared'
const debug = _debug('vitepress:config') const debug = _debug('vitepress:config')

@ -230,3 +230,14 @@ export function escapeHtml(str: string): string {
.replace(/"/g, '&quot;') .replace(/"/g, '&quot;')
.replace(/&(?![\w#]+;)/g, '&amp;') .replace(/&(?![\w#]+;)/g, '&amp;')
} }
const CLIENT_ONLY = '[VP_CLIENT_ONLY]'
export function clientOnly<T extends object>(obj: T): T {
;(obj as any)[CLIENT_ONLY] = true
return obj
}
export function isClientOnly<T = any>(obj: T) {
return Boolean((obj as any)?.[CLIENT_ONLY])
}

Loading…
Cancel
Save