parent
d2b4654ae3
commit
3e68b44771
@ -9,23 +9,22 @@ import VPNavBarTitle from '@theme/Nav/VPNavBarTitle.vue'
|
|||||||
import VPNavBarTranslations from '@theme/Nav/VPNavBarTranslations.vue'
|
import VPNavBarTranslations from '@theme/Nav/VPNavBarTranslations.vue'
|
||||||
import { useWindowScroll } from '@vueuse/core'
|
import { useWindowScroll } from '@vueuse/core'
|
||||||
import { ref, watchPostEffect } from 'vue'
|
import { ref, watchPostEffect } from 'vue'
|
||||||
import { useData, useSidebar } from '../../composables/index.js'
|
import { useLayout, useSidebarControl } from '../../composables/index.js'
|
||||||
|
|
||||||
const { isScreenOpen } = defineProps<{
|
const { isScreenOpen } = defineProps<{
|
||||||
isScreenOpen: boolean
|
isScreenOpen: boolean
|
||||||
}>()
|
}>()
|
||||||
defineEmits<(e: 'toggleScreen') => void>()
|
defineEmits<(e: 'toggleScreen') => void>()
|
||||||
|
|
||||||
const { frontmatter } = useData()
|
|
||||||
|
|
||||||
const { y } = useWindowScroll()
|
const { y } = useWindowScroll()
|
||||||
const { hasSidebar } = useSidebar()
|
const { hasSidebar, isHome } = useLayout()
|
||||||
|
const { isSidebarCollapsed } = useSidebarControl()
|
||||||
|
|
||||||
const classes = ref<Record<string, boolean>>({})
|
const classes = ref<Record<string, boolean>>({})
|
||||||
watchPostEffect(() => {
|
watchPostEffect(() => {
|
||||||
classes.value = {
|
classes.value = {
|
||||||
'has-sidebar': hasSidebar.value,
|
'has-sidebar': hasSidebar.value && !isSidebarCollapsed.value,
|
||||||
'home': frontmatter.value.pageLayout === 'home',
|
'home': isHome.value,
|
||||||
'top': y.value === 0,
|
'top': y.value === 0,
|
||||||
'screen-open': isScreenOpen,
|
'screen-open': isScreenOpen,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,15 +2,16 @@
|
|||||||
import VPImage from '@theme/VPImage.vue'
|
import VPImage from '@theme/VPImage.vue'
|
||||||
import VPLink from '@theme/VPLink.vue'
|
import VPLink from '@theme/VPLink.vue'
|
||||||
import { useRouteLocale } from 'vuepress/client'
|
import { useRouteLocale } from 'vuepress/client'
|
||||||
import { useData, useSidebar } from '../../composables/index.js'
|
import { useData, useLayout, useSidebarControl } from '../../composables/index.js'
|
||||||
|
|
||||||
const { theme, site } = useData()
|
const { theme, site } = useData()
|
||||||
const { hasSidebar } = useSidebar()
|
const { hasSidebar } = useLayout()
|
||||||
const routeLocale = useRouteLocale()
|
const routeLocale = useRouteLocale()
|
||||||
|
const { isSidebarCollapsed } = useSidebarControl()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="vp-navbar-title" :class="{ 'has-sidebar': hasSidebar }">
|
<div class="vp-navbar-title" :class="{ 'has-sidebar': hasSidebar && !isSidebarCollapsed }">
|
||||||
<VPLink class="title" :href="theme.home ?? routeLocale" no-icon>
|
<VPLink class="title" :href="theme.home ?? routeLocale" no-icon>
|
||||||
<slot name="nav-bar-title-before" />
|
<slot name="nav-bar-title-before" />
|
||||||
|
|
||||||
|
|||||||
@ -6,14 +6,15 @@ import VPFriends from '@theme/VPFriends.vue'
|
|||||||
import VPPage from '@theme/VPPage.vue'
|
import VPPage from '@theme/VPPage.vue'
|
||||||
import { nextTick, watch } from 'vue'
|
import { nextTick, watch } from 'vue'
|
||||||
import { useRoute } from 'vuepress/client'
|
import { useRoute } from 'vuepress/client'
|
||||||
import { useData, usePostsPageData, useSidebar } from '../composables/index.js'
|
import { useData, useLayout, usePostsPageData, useSidebarControl } from '../composables/index.js'
|
||||||
import { inBrowser } from '../utils/index.js'
|
import { inBrowser } from '../utils/index.js'
|
||||||
|
|
||||||
const { isNotFound } = defineProps<{
|
const { isNotFound } = defineProps<{
|
||||||
isNotFound?: boolean
|
isNotFound?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { hasSidebar } = useSidebar()
|
const { hasSidebar, isHome } = useLayout()
|
||||||
|
const { isSidebarCollapsed } = useSidebarControl()
|
||||||
const { frontmatter, collection } = useData()
|
const { frontmatter, collection } = useData()
|
||||||
const { isPostsLayout } = usePostsPageData()
|
const { isPostsLayout } = usePostsPageData()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@ -43,8 +44,8 @@ watch(
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
id="VPContent" vp-content class="vp-content" :class="{
|
id="VPContent" vp-content class="vp-content" :class="{
|
||||||
'has-sidebar': hasSidebar && !isNotFound,
|
'has-sidebar': hasSidebar && !isSidebarCollapsed && !isNotFound,
|
||||||
'is-home': frontmatter.pageLayout === 'home',
|
'is-home': isHome,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<VPPosts
|
<VPPosts
|
||||||
@ -220,6 +221,8 @@ watch(
|
|||||||
@media (min-width: 960px) {
|
@media (min-width: 960px) {
|
||||||
.vp-content {
|
.vp-content {
|
||||||
padding-top: var(--vp-nav-height);
|
padding-top: var(--vp-nav-height);
|
||||||
|
padding-left: 0;
|
||||||
|
transition: padding-left var(--vp-t-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vp-content.has-sidebar {
|
.vp-content.has-sidebar {
|
||||||
|
|||||||
@ -14,14 +14,16 @@ import {
|
|||||||
useData,
|
useData,
|
||||||
useEncrypt,
|
useEncrypt,
|
||||||
useHeaders,
|
useHeaders,
|
||||||
|
useLayout,
|
||||||
usePostsPageData,
|
usePostsPageData,
|
||||||
useSidebar,
|
useSidebarControl,
|
||||||
} from '../composables/index.js'
|
} from '../composables/index.js'
|
||||||
|
|
||||||
const { page, theme, frontmatter } = useData()
|
const { page, theme, frontmatter } = useData()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const { hasSidebar, hasAside, leftAside } = useSidebar()
|
const { hasSidebar, hasAside, leftAside } = useLayout()
|
||||||
|
const { isSidebarCollapsed } = useSidebarControl()
|
||||||
const { isPosts } = usePostsPageData()
|
const { isPosts } = usePostsPageData()
|
||||||
const headers = useHeaders()
|
const headers = useHeaders()
|
||||||
const { isPageDecrypted } = useEncrypt()
|
const { isPageDecrypted } = useEncrypt()
|
||||||
@ -76,7 +78,7 @@ watch(
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="vp-doc-container" :class="{
|
class="vp-doc-container" :class="{
|
||||||
'has-sidebar': hasSidebar,
|
'has-sidebar': hasSidebar && !isSidebarCollapsed,
|
||||||
'has-aside': enableAside,
|
'has-aside': enableAside,
|
||||||
'is-posts': isPosts,
|
'is-posts': isPosts,
|
||||||
'with-encrypt': !isPageDecrypted,
|
'with-encrypt': !isPageDecrypted,
|
||||||
@ -263,7 +265,7 @@ watch(
|
|||||||
padding: 48px 32px 0;
|
padding: 48px 32px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vp-doc-container:not(.has-sidebar) .container {
|
.vp-doc-container:not(.has-sidebar) .container, {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
max-width: 992px;
|
max-width: 992px;
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCssVar } from '@vueuse/core'
|
import { useCssVar } from '@vueuse/core'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { useData, useSidebar } from '../composables/index.js'
|
import { useData, useLayout, useSidebarControl } from '../composables/index.js'
|
||||||
import { inBrowser } from '../utils/index.js'
|
import { inBrowser } from '../utils/index.js'
|
||||||
|
|
||||||
const { theme, frontmatter } = useData()
|
const { theme, frontmatter } = useData()
|
||||||
const { hasSidebar } = useSidebar()
|
const { hasSidebar } = useLayout()
|
||||||
|
const { isSidebarCollapsed } = useSidebarControl()
|
||||||
|
|
||||||
const footerHeight = useCssVar('--vp-footer-height', inBrowser ? document.body : null)
|
const footerHeight = useCssVar('--vp-footer-height', inBrowser ? document.body : null)
|
||||||
const footer = ref<HTMLElement | null>(null)
|
const footer = ref<HTMLElement | null>(null)
|
||||||
@ -21,7 +22,7 @@ onMounted(() => {
|
|||||||
v-if="theme.footer && frontmatter.footer !== false"
|
v-if="theme.footer && frontmatter.footer !== false"
|
||||||
ref="footer"
|
ref="footer"
|
||||||
class="vp-footer"
|
class="vp-footer"
|
||||||
:class="{ 'has-sidebar': hasSidebar }"
|
:class="{ 'has-sidebar': hasSidebar && !isSidebarCollapsed }"
|
||||||
vp-footer
|
vp-footer
|
||||||
>
|
>
|
||||||
<slot name="footer-content">
|
<slot name="footer-content">
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
import VPLocalNavOutlineDropdown from '@theme/VPLocalNavOutlineDropdown.vue'
|
import VPLocalNavOutlineDropdown from '@theme/VPLocalNavOutlineDropdown.vue'
|
||||||
import { useWindowScroll } from '@vueuse/core'
|
import { useWindowScroll } from '@vueuse/core'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useData, useHeaders, usePostsPageData, useSidebar } from '../composables/index.js'
|
import { useData, useHeaders, useLayout, usePostsPageData, useSidebarControl } from '../composables/index.js'
|
||||||
|
|
||||||
const { open, showOutline } = defineProps<{
|
const { open, showOutline } = defineProps<{
|
||||||
open: boolean
|
open: boolean
|
||||||
@ -14,7 +14,8 @@ defineEmits<(e: 'openMenu') => void>()
|
|||||||
const { theme } = useData()
|
const { theme } = useData()
|
||||||
const { isPosts, isPostsLayout } = usePostsPageData()
|
const { isPosts, isPostsLayout } = usePostsPageData()
|
||||||
|
|
||||||
const { hasSidebar } = useSidebar()
|
const { hasSidebar, hasLocalNav } = useLayout()
|
||||||
|
const { isSidebarCollapsed } = useSidebarControl()
|
||||||
const { y } = useWindowScroll()
|
const { y } = useWindowScroll()
|
||||||
|
|
||||||
const navHeight = ref(0)
|
const navHeight = ref(0)
|
||||||
@ -22,7 +23,7 @@ const navHeight = ref(0)
|
|||||||
const headers = useHeaders()
|
const headers = useHeaders()
|
||||||
|
|
||||||
const empty = computed(() => {
|
const empty = computed(() => {
|
||||||
return headers.value.length === 0 && !hasSidebar.value
|
return !hasLocalNav.value && !hasSidebar.value
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -40,6 +41,7 @@ const classes = computed(() => {
|
|||||||
'reached-top': y.value >= navHeight.value,
|
'reached-top': y.value >= navHeight.value,
|
||||||
'is-posts': isPosts.value && !isPostsLayout.value,
|
'is-posts': isPosts.value && !isPostsLayout.value,
|
||||||
'with-outline': !showOutline,
|
'with-outline': !showOutline,
|
||||||
|
'has-sidebar': hasSidebar.value && !isSidebarCollapsed.value,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -98,9 +100,12 @@ const showLocalNav = computed(() => {
|
|||||||
@media (min-width: 960px) {
|
@media (min-width: 960px) {
|
||||||
.vp-local-nav {
|
.vp-local-nav {
|
||||||
top: var(--vp-nav-height);
|
top: var(--vp-nav-height);
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-local-nav.has-sidebar {
|
||||||
width: calc(100% - var(--vp-sidebar-width));
|
width: calc(100% - var(--vp-sidebar-width));
|
||||||
margin-left: var(--vp-sidebar-width);
|
margin-left: var(--vp-sidebar-width);
|
||||||
border-top: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vp-local-nav.is-posts {
|
.vp-local-nav.is-posts {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import VPTransitionFadeSlideY from '@theme/VPTransitionFadeSlideY.vue'
|
|||||||
import { useScrollLock } from '@vueuse/core'
|
import { useScrollLock } from '@vueuse/core'
|
||||||
import { nextTick, onMounted, ref, watch } from 'vue'
|
import { nextTick, onMounted, ref, watch } from 'vue'
|
||||||
import { useRoutePath } from 'vuepress/client'
|
import { useRoutePath } from 'vuepress/client'
|
||||||
import { useData, useSidebar } from '../composables/index.js'
|
import { useData, useLayout, useSidebar, useSidebarControl } from '../composables/index.js'
|
||||||
import { inBrowser } from '../utils/index.js'
|
import { inBrowser } from '../utils/index.js'
|
||||||
|
|
||||||
const { open } = defineProps<{
|
const { open } = defineProps<{
|
||||||
@ -12,7 +12,9 @@ const { open } = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { theme } = useData()
|
const { theme } = useData()
|
||||||
const { sidebarGroups, hasSidebar, sidebarKey } = useSidebar()
|
const { hasSidebar } = useLayout()
|
||||||
|
const { sidebarGroups, sidebarKey } = useSidebar()
|
||||||
|
const { isSidebarCollapsed, toggleSidebarCollapse } = useSidebarControl()
|
||||||
const routePath = useRoutePath()
|
const routePath = useRoutePath()
|
||||||
|
|
||||||
// a11y: focus Nav element when menu has opened
|
// a11y: focus Nav element when menu has opened
|
||||||
@ -65,7 +67,11 @@ onMounted(() => {
|
|||||||
v-if="hasSidebar"
|
v-if="hasSidebar"
|
||||||
ref="navEl"
|
ref="navEl"
|
||||||
class="vp-sidebar"
|
class="vp-sidebar"
|
||||||
:class="{ open, 'hide-scrollbar': !(theme.sidebarScrollbar ?? true) }"
|
:class="{
|
||||||
|
open,
|
||||||
|
'hide-scrollbar': !(theme.sidebarScrollbar ?? true),
|
||||||
|
'collapsed': isSidebarCollapsed,
|
||||||
|
}"
|
||||||
vp-sidebar
|
vp-sidebar
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
@ -92,6 +98,15 @@ onMounted(() => {
|
|||||||
</VPTransitionFadeSlideY>
|
</VPTransitionFadeSlideY>
|
||||||
</aside>
|
</aside>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
<div v-if="hasSidebar" class="vp-sidebar-control" :class="{ collapsed: isSidebarCollapsed }">
|
||||||
|
<button
|
||||||
|
type="button" class="toggle-sidebar-btn"
|
||||||
|
aria-label="Toggle sidebar"
|
||||||
|
@click="toggleSidebarCollapse()"
|
||||||
|
>
|
||||||
|
<span :class="`vpi-sidebar-${isSidebarCollapsed ? 'open' : 'close'}`" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -152,6 +167,11 @@ onMounted(() => {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vp-sidebar:not(.open).collapsed {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1440px) {
|
@media (min-width: 1440px) {
|
||||||
@ -187,4 +207,68 @@ onMounted(() => {
|
|||||||
.nav {
|
.nav {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vp-sidebar-control {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: calc(var(--vp-z-index-sidebar) + 1);
|
||||||
|
display: none;
|
||||||
|
width: calc(100vw - 64px);
|
||||||
|
max-width: 320px;
|
||||||
|
transition: transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-sidebar-control .toggle-sidebar-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 0 transparent;
|
||||||
|
transition: background-color var(--vp-t-color), box-shadow var(--vp-t-color), border-color var(--vp-t-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-sidebar-control [class^="vpi-sidebar-"] {
|
||||||
|
font-size: 20px;
|
||||||
|
color: var(--vp-c-text-3);
|
||||||
|
transition: color var(--vp-t-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.vp-sidebar-control {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: var(--vp-sidebar-width);
|
||||||
|
max-width: 100%;
|
||||||
|
padding-right: 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1440px) {
|
||||||
|
.vp-sidebar-control {
|
||||||
|
width:
|
||||||
|
calc(
|
||||||
|
(100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) -
|
||||||
|
32px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-sidebar-control.collapsed {
|
||||||
|
transform: translateX(calc(-100% + 54px));
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-sidebar-control.collapsed .toggle-sidebar-btn {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
background-color: var(--vp-c-bg-safe);
|
||||||
|
border-color: var(--vp-c-divider);
|
||||||
|
box-shadow: var(--vp-shadow-2);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import VPIcon from '@theme/VPIcon.vue'
|
|||||||
import VPLink from '@theme/VPLink.vue'
|
import VPLink from '@theme/VPLink.vue'
|
||||||
import { FadeInExpandTransition } from '@vuepress/helper/client'
|
import { FadeInExpandTransition } from '@vuepress/helper/client'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useSidebarControl } from '../composables/index.js'
|
import { useSidebarItemControl } from '../composables/index.js'
|
||||||
|
|
||||||
import '@vuepress/helper/transition/fade-in-height-expand.css'
|
import '@vuepress/helper/transition/fade-in-height-expand.css'
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ const {
|
|||||||
hasActiveLink,
|
hasActiveLink,
|
||||||
hasChildren,
|
hasChildren,
|
||||||
toggle,
|
toggle,
|
||||||
} = useSidebarControl(computed(() => item))
|
} = useSidebarItemControl(computed(() => item))
|
||||||
|
|
||||||
const sectionTag = computed(() => (hasChildren.value ? 'section' : `div`))
|
const sectionTag = computed(() => (hasChildren.value ? 'section' : `div`))
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ThemeHomeConfig } from 'theme/src/shared/index.js'
|
import type { ThemeHomeConfig } from 'theme/src/shared/index.js'
|
||||||
import { useElementSize, useMediaQuery, useWindowSize } from '@vueuse/core'
|
import { useElementSize, useWindowSize } from '@vueuse/core'
|
||||||
import { computed, onMounted, shallowRef } from 'vue'
|
import { computed, onMounted, shallowRef } from 'vue'
|
||||||
import { useData } from '../composables/index.js'
|
import { useData, useLayout } from '../composables/index.js'
|
||||||
|
|
||||||
const body = shallowRef<HTMLElement | null>()
|
const body = shallowRef<HTMLElement | null>()
|
||||||
const { height: bodyHeight } = useElementSize(body)
|
const { height: bodyHeight } = useElementSize(body)
|
||||||
@ -31,7 +31,8 @@ const show = computed(() => {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
const is960 = useMediaQuery('(min-width: 960px)')
|
const { is960 } = useLayout()
|
||||||
|
|
||||||
function onClick() {
|
function onClick() {
|
||||||
document.documentElement.scrollTo({
|
document.documentElement.scrollTo({
|
||||||
top: document.documentElement.clientHeight - (is960.value ? 64 : 0),
|
top: document.documentElement.clientHeight - (is960.value ? 64 : 0),
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
import type { ComputedRef } from 'vue'
|
|
||||||
import { useMediaQuery } from '@vueuse/core'
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useSidebar } from './sidebar.js'
|
|
||||||
|
|
||||||
export function useAside(): {
|
|
||||||
isAsideEnabled: ComputedRef<boolean>
|
|
||||||
} {
|
|
||||||
const { hasSidebar } = useSidebar()
|
|
||||||
const is960 = useMediaQuery('(min-width: 960px)')
|
|
||||||
const is1280 = useMediaQuery('(min-width: 1280px)')
|
|
||||||
|
|
||||||
const isAsideEnabled = computed(() => {
|
|
||||||
if (!is1280.value && !is960.value)
|
|
||||||
return false
|
|
||||||
|
|
||||||
return hasSidebar.value ? is1280.value : is960.value
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
isAsideEnabled,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,10 @@
|
|||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import type { ThemeBaseCollection, ThemeCollectionItem, ThemeDocCollection, ThemePostCollection } from '../../shared/index.js'
|
import type {
|
||||||
|
ThemeBaseCollection,
|
||||||
|
ThemeCollectionItem,
|
||||||
|
ThemeDocCollection,
|
||||||
|
ThemePostCollection,
|
||||||
|
} from '../../shared/index.js'
|
||||||
import { collections as collectionsRaw } from '@internal/collectionsData'
|
import { collections as collectionsRaw } from '@internal/collectionsData'
|
||||||
import { ref, watchEffect } from 'vue'
|
import { ref, watchEffect } from 'vue'
|
||||||
import { useRouteLocale } from 'vuepress/client'
|
import { useRouteLocale } from 'vuepress/client'
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
export * from './aside.js'
|
|
||||||
export * from './bulletin.js'
|
export * from './bulletin.js'
|
||||||
export * from './collections.js'
|
export * from './collections.js'
|
||||||
export * from './contributors.js'
|
export * from './contributors.js'
|
||||||
@ -14,6 +13,7 @@ export * from './icons.js'
|
|||||||
export * from './internal-link.js'
|
export * from './internal-link.js'
|
||||||
export * from './langs.js'
|
export * from './langs.js'
|
||||||
export * from './latest-updated.js'
|
export * from './latest-updated.js'
|
||||||
|
export * from './layout.js'
|
||||||
export * from './link.js'
|
export * from './link.js'
|
||||||
export * from './nav.js'
|
export * from './nav.js'
|
||||||
export * from './outline.js'
|
export * from './outline.js'
|
||||||
|
|||||||
96
theme/src/client/composables/layout.ts
Normal file
96
theme/src/client/composables/layout.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { computed, shallowRef, watch } from 'vue'
|
||||||
|
import { useRoute } from 'vuepress/client'
|
||||||
|
import { inBrowser } from '../utils/index.js'
|
||||||
|
import { useData } from './data.js'
|
||||||
|
import { useEncrypt } from './encrypt.js'
|
||||||
|
import { useHeaders } from './outline.js'
|
||||||
|
import { useSidebarData } from './sidebar-data.js'
|
||||||
|
import { useCloseSidebarOnEscape, useSidebarControl } from './sidebar.js'
|
||||||
|
|
||||||
|
const is960 = shallowRef(false)
|
||||||
|
const is1280 = shallowRef(false)
|
||||||
|
|
||||||
|
export function useLayout() {
|
||||||
|
const { frontmatter, theme } = useData()
|
||||||
|
const { isPageDecrypted } = useEncrypt()
|
||||||
|
|
||||||
|
const sidebar = useSidebarData()
|
||||||
|
const headers = useHeaders()
|
||||||
|
|
||||||
|
const isHome = computed(() => frontmatter.value.home ?? frontmatter.value.pageLayout === 'home')
|
||||||
|
|
||||||
|
const hasSidebar = computed(() => {
|
||||||
|
return (
|
||||||
|
frontmatter.value.sidebar !== false
|
||||||
|
&& sidebar.value.length > 0
|
||||||
|
&& frontmatter.value.pageLayout !== 'home'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isSidebarEnabled = computed(() => hasSidebar.value && is960.value)
|
||||||
|
|
||||||
|
const hasAside = computed(() => {
|
||||||
|
if (frontmatter.value.pageLayout === 'home' || frontmatter.value.home)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if (frontmatter.value.pageLayout === 'friends' || frontmatter.value.friends)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if (!isPageDecrypted.value)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if (frontmatter.value.aside != null)
|
||||||
|
return !!frontmatter.value.aside
|
||||||
|
return theme.value.aside !== false
|
||||||
|
})
|
||||||
|
|
||||||
|
const leftAside = computed(() => {
|
||||||
|
if (hasAside.value) {
|
||||||
|
return frontmatter.value.aside == null
|
||||||
|
? theme.value.aside === 'left'
|
||||||
|
: frontmatter.value.aside === 'left'
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
const hasLocalNav = computed(() => headers.value.length > 0)
|
||||||
|
|
||||||
|
const isAsideEnabled = computed(() => {
|
||||||
|
if (!is1280.value && !is960.value)
|
||||||
|
return false
|
||||||
|
|
||||||
|
return hasSidebar.value ? is1280.value : is960.value
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
isHome,
|
||||||
|
hasAside,
|
||||||
|
hasSidebar,
|
||||||
|
leftAside,
|
||||||
|
hasLocalNav,
|
||||||
|
isSidebarEnabled,
|
||||||
|
isAsideEnabled,
|
||||||
|
is960,
|
||||||
|
is1280,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerWatchers() {
|
||||||
|
if (inBrowser) {
|
||||||
|
is960.value = window.innerWidth >= 960
|
||||||
|
is1280.value = window.innerWidth >= 1280
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
is960.value = window.innerWidth >= 960
|
||||||
|
is1280.value = window.innerWidth >= 1280
|
||||||
|
}, { passive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const { disableSidebar, toggleSidebarCollapse } = useSidebarControl()
|
||||||
|
watch(() => route.path, () => {
|
||||||
|
disableSidebar()
|
||||||
|
toggleSidebarCollapse(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
useCloseSidebarOnEscape()
|
||||||
|
}
|
||||||
@ -1,11 +1,11 @@
|
|||||||
import type { InjectionKey, Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import type { Router } from 'vuepress/client'
|
import type { Router } from 'vuepress/client'
|
||||||
import type { ThemeOutline } from '../../shared/index.js'
|
import type { ThemeOutline } from '../../shared/index.js'
|
||||||
import { useThrottleFn, watchDebounced } from '@vueuse/core'
|
import { useThrottleFn, watchDebounced } from '@vueuse/core'
|
||||||
import { inject, onMounted, onUnmounted, onUpdated, provide, ref } from 'vue'
|
import { onMounted, onUnmounted, onUpdated, ref } from 'vue'
|
||||||
import { onContentUpdated, useRouter } from 'vuepress/client'
|
import { onContentUpdated, useRouter } from 'vuepress/client'
|
||||||
import { useAside } from './aside.js'
|
|
||||||
import { useData } from './data.js'
|
import { useData } from './data.js'
|
||||||
|
import { useLayout } from './layout.js'
|
||||||
|
|
||||||
export interface Header {
|
export interface Header {
|
||||||
/**
|
/**
|
||||||
@ -45,28 +45,19 @@ export type MenuItem = Omit<Header, 'slug' | 'children'> & {
|
|||||||
lowLevel?: number
|
lowLevel?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const headersSymbol: InjectionKey<Ref<MenuItem[]>> = Symbol(
|
const headers = ref<MenuItem[]>([])
|
||||||
__VUEPRESS_DEV__ ? 'headers' : '',
|
|
||||||
)
|
|
||||||
|
|
||||||
export function setupHeaders(): Ref<MenuItem[]> {
|
export function setupHeaders(): Ref<MenuItem[]> {
|
||||||
const { frontmatter, theme } = useData()
|
const { frontmatter, theme } = useData()
|
||||||
const headers = ref<MenuItem[]>([])
|
|
||||||
|
|
||||||
onContentUpdated(() => {
|
onContentUpdated(() => {
|
||||||
headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline)
|
headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline)
|
||||||
})
|
})
|
||||||
|
|
||||||
provide(headersSymbol, headers)
|
|
||||||
|
|
||||||
return headers
|
return headers
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useHeaders(): Ref<MenuItem[]> {
|
export function useHeaders(): Ref<MenuItem[]> {
|
||||||
const headers = inject(headersSymbol)
|
|
||||||
if (!headers) {
|
|
||||||
throw new Error('useHeaders() is called without provider.')
|
|
||||||
}
|
|
||||||
return headers
|
return headers
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +204,7 @@ function resolveSubRangeHeader(headers: MenuItem[], low: number): MenuItem[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useActiveAnchor(container: Ref<HTMLElement | null>, marker: Ref<HTMLElement | null>): void {
|
export function useActiveAnchor(container: Ref<HTMLElement | null>, marker: Ref<HTMLElement | null>): void {
|
||||||
const { isAsideEnabled } = useAside()
|
const { isAsideEnabled } = useLayout()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const routeHash = ref<string>(router.currentRoute.value.hash)
|
const routeHash = ref<string>(router.currentRoute.value.hash)
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import type { ComputedRef, Ref } from 'vue'
|
import type { ComputedRef, Ref } from 'vue'
|
||||||
import type { ThemePostsItem } from '../../shared/index.js'
|
import type { ThemePostsItem } from '../../shared/index.js'
|
||||||
import { useMediaQuery } from '@vueuse/core'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useData } from './data.js'
|
import { useData } from './data.js'
|
||||||
|
import { useLayout } from './layout.js'
|
||||||
import { useLocalePostList } from './posts-data.js'
|
import { useLocalePostList } from './posts-data.js'
|
||||||
import { useRouteQuery } from './route-query.js'
|
import { useRouteQuery } from './route-query.js'
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ export function usePostListControl(homePage: Ref<boolean>): UsePostListControlRe
|
|||||||
const { collection } = useData<'page', 'post'>()
|
const { collection } = useData<'page', 'post'>()
|
||||||
|
|
||||||
const list = useLocalePostList()
|
const list = useLocalePostList()
|
||||||
const is960 = useMediaQuery('(max-width: 960px)')
|
const { is960 } = useLayout()
|
||||||
|
|
||||||
const postCollection = computed(() => {
|
const postCollection = computed(() => {
|
||||||
if (collection.value?.type === 'post')
|
if (collection.value?.type === 'post')
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { InjectionKey, Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import type { ResolvedSidebarItem, ThemeSidebar, ThemeSidebarItem } from '../../shared/index.js'
|
import type { ResolvedSidebarItem, ThemeSidebar, ThemeSidebarItem } from '../../shared/index.js'
|
||||||
import { sidebar as sidebarRaw } from '@internal/sidebar'
|
import { sidebar as sidebarRaw } from '@internal/sidebar'
|
||||||
import {
|
import {
|
||||||
@ -7,12 +7,7 @@ import {
|
|||||||
isString,
|
isString,
|
||||||
removeLeadingSlash,
|
removeLeadingSlash,
|
||||||
} from '@vuepress/helper/client'
|
} from '@vuepress/helper/client'
|
||||||
import {
|
import { computed, ref, watch } from 'vue'
|
||||||
computed,
|
|
||||||
inject,
|
|
||||||
provide,
|
|
||||||
ref,
|
|
||||||
} from 'vue'
|
|
||||||
import { useRouteLocale } from 'vuepress/client'
|
import { useRouteLocale } from 'vuepress/client'
|
||||||
import { normalizeLink, normalizePrefix, resolveNavLink } from '../utils/index.js'
|
import { normalizeLink, normalizePrefix, resolveNavLink } from '../utils/index.js'
|
||||||
import { useData } from './data.js'
|
import { useData } from './data.js'
|
||||||
@ -41,9 +36,7 @@ if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sidebarSymbol: InjectionKey<Ref<ResolvedSidebarItem[]>> = Symbol(
|
const sidebar: Ref<ResolvedSidebarItem[]> = ref([])
|
||||||
__VUEPRESS_DEV__ ? 'sidebar' : '',
|
|
||||||
)
|
|
||||||
|
|
||||||
export function setupSidebar(): void {
|
export function setupSidebar(): void {
|
||||||
const { page, frontmatter } = useData()
|
const { page, frontmatter } = useData()
|
||||||
@ -59,23 +52,22 @@ export function setupSidebar(): void {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const sidebarData = computed(() => {
|
watch([
|
||||||
return hasSidebar.value
|
hasSidebar,
|
||||||
|
routeLocale,
|
||||||
|
() => frontmatter.value.sidebar,
|
||||||
|
() => page.value.path,
|
||||||
|
], () => {
|
||||||
|
sidebar.value = hasSidebar.value
|
||||||
? getSidebar(typeof frontmatter.value.sidebar === 'string'
|
? getSidebar(typeof frontmatter.value.sidebar === 'string'
|
||||||
? frontmatter.value.sidebar
|
? frontmatter.value.sidebar
|
||||||
: page.value.path, routeLocale.value)
|
: page.value.path, routeLocale.value)
|
||||||
: []
|
: []
|
||||||
})
|
}, { immediate: true })
|
||||||
|
|
||||||
provide(sidebarSymbol, sidebarData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSidebarData(): Ref<ResolvedSidebarItem[]> {
|
export function useSidebarData(): Ref<ResolvedSidebarItem[]> {
|
||||||
const sidebarData = inject(sidebarSymbol)
|
return sidebar
|
||||||
if (!sidebarData) {
|
|
||||||
throw new Error('useSidebarData() is called without provider.')
|
|
||||||
}
|
|
||||||
return sidebarData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import type { ComputedRef, Ref } from 'vue'
|
import type { ComputedRef, Ref } from 'vue'
|
||||||
import type { ResolvedSidebarItem } from '../../shared/index.js'
|
import type { ResolvedSidebarItem } from '../../shared/index.js'
|
||||||
import { ensureLeadingSlash, isArray } from '@vuepress/helper/client'
|
import { ensureLeadingSlash, isArray } from '@vuepress/helper/client'
|
||||||
import { useMediaQuery } from '@vueuse/core'
|
|
||||||
import { computed, onMounted, onUnmounted, ref, watch, watchEffect } from 'vue'
|
import { computed, onMounted, onUnmounted, ref, watch, watchEffect } from 'vue'
|
||||||
import { resolveRouteFullPath, useRoute, useRouteLocale } from 'vuepress/client'
|
import { resolveRouteFullPath, useRoute, useRouteLocale } from 'vuepress/client'
|
||||||
import { isActive } from '../utils/index.js'
|
import { isActive } from '../utils/index.js'
|
||||||
import { useData } from './data.js'
|
import { useData } from './data.js'
|
||||||
import { useEncrypt } from './encrypt.js'
|
import { useLayout } from './layout.js'
|
||||||
import { getSidebarGroups, sidebarData, useSidebarData } from './sidebar-data.js'
|
import { getSidebarGroups, sidebarData, useSidebarData } from './sidebar-data.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,39 +26,56 @@ export function hasActiveLink(path: string, items: ResolvedSidebarItem | Resolve
|
|||||||
: false
|
: false
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SidebarControl {
|
|
||||||
collapsed: Ref<boolean>
|
|
||||||
collapsible: ComputedRef<boolean>
|
|
||||||
isLink: ComputedRef<boolean>
|
|
||||||
isActiveLink: Ref<boolean>
|
|
||||||
hasActiveLink: ComputedRef<boolean>
|
|
||||||
hasChildren: ComputedRef<boolean>
|
|
||||||
toggle: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UseSidebarReturn {
|
|
||||||
isOpen: Ref<boolean>
|
|
||||||
sidebar: Ref<ResolvedSidebarItem[]>
|
|
||||||
sidebarKey: Ref<string>
|
|
||||||
sidebarGroups: Ref<ResolvedSidebarItem[]>
|
|
||||||
hasSidebar: ComputedRef<boolean>
|
|
||||||
hasAside: ComputedRef<boolean>
|
|
||||||
leftAside: ComputedRef<boolean>
|
|
||||||
isSidebarEnabled: ComputedRef<boolean>
|
|
||||||
open: () => void
|
|
||||||
close: () => void
|
|
||||||
toggle: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const containsActiveLink = hasActiveLink
|
const containsActiveLink = hasActiveLink
|
||||||
|
|
||||||
export function useSidebar(): UseSidebarReturn {
|
const isSidebarEnabled = ref(false)
|
||||||
const { theme, frontmatter, page } = useData()
|
const isSidebarCollapsed = ref(false)
|
||||||
const routeLocal = useRouteLocale()
|
|
||||||
const is960 = useMediaQuery('(min-width: 960px)')
|
|
||||||
const { isPageDecrypted } = useEncrypt()
|
|
||||||
|
|
||||||
const isOpen = ref(false)
|
export function useSidebarControl() {
|
||||||
|
const enableSidebar = (): void => {
|
||||||
|
isSidebarEnabled.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const disableSidebar = (): void => {
|
||||||
|
isSidebarEnabled.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleSidebarEnabled = (): void => {
|
||||||
|
if (isSidebarEnabled.value) {
|
||||||
|
disableSidebar()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
enableSidebar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSidebarCollapse(collapse?: boolean) {
|
||||||
|
isSidebarCollapsed.value = collapse ?? !isSidebarCollapsed.value
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isSidebarEnabled,
|
||||||
|
enableSidebar,
|
||||||
|
disableSidebar,
|
||||||
|
toggleSidebarEnabled,
|
||||||
|
isSidebarCollapsed,
|
||||||
|
toggleSidebarCollapse,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSidebar(): {
|
||||||
|
sidebar: Ref<ResolvedSidebarItem[]>
|
||||||
|
sidebarKey: ComputedRef<string>
|
||||||
|
sidebarGroups: ComputedRef<ResolvedSidebarItem[]>
|
||||||
|
} {
|
||||||
|
const { page } = useData()
|
||||||
|
const routeLocal = useRouteLocale()
|
||||||
|
const { hasSidebar } = useLayout()
|
||||||
|
const sidebar = useSidebarData()
|
||||||
|
|
||||||
|
const sidebarGroups = computed(() => {
|
||||||
|
return hasSidebar.value ? getSidebarGroups(sidebar.value) : []
|
||||||
|
})
|
||||||
|
|
||||||
const sidebarKey = computed(() => {
|
const sidebarKey = computed(() => {
|
||||||
const _sidebar = sidebarData.value[routeLocal.value]
|
const _sidebar = sidebarData.value[routeLocal.value]
|
||||||
@ -73,87 +89,19 @@ export function useSidebar(): UseSidebarReturn {
|
|||||||
}) || ''
|
}) || ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const sidebar = useSidebarData()
|
return { sidebar, sidebarKey, sidebarGroups }
|
||||||
|
|
||||||
const hasSidebar = computed(() => {
|
|
||||||
return (
|
|
||||||
frontmatter.value.sidebar !== false
|
|
||||||
&& sidebar.value.length > 0
|
|
||||||
&& frontmatter.value.pageLayout !== 'home'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const hasAside = computed(() => {
|
|
||||||
if (frontmatter.value.pageLayout === 'home' || frontmatter.value.home)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (frontmatter.value.pageLayout === 'friends' || frontmatter.value.friends)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (!isPageDecrypted.value)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (frontmatter.value.aside != null)
|
|
||||||
return !!frontmatter.value.aside
|
|
||||||
return theme.value.aside !== false
|
|
||||||
})
|
|
||||||
|
|
||||||
const leftAside = computed(() => {
|
|
||||||
if (hasAside.value) {
|
|
||||||
return frontmatter.value.aside == null
|
|
||||||
? theme.value.aside === 'left'
|
|
||||||
: frontmatter.value.aside === 'left'
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
const isSidebarEnabled = computed(() => hasSidebar.value && is960.value)
|
|
||||||
|
|
||||||
const sidebarGroups = computed(() => {
|
|
||||||
return hasSidebar.value ? getSidebarGroups(sidebar.value) : []
|
|
||||||
})
|
|
||||||
|
|
||||||
const open = (): void => {
|
|
||||||
isOpen.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const close = (): void => {
|
|
||||||
isOpen.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggle = (): void => {
|
|
||||||
if (isOpen.value) {
|
|
||||||
close()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
isOpen,
|
|
||||||
sidebar,
|
|
||||||
sidebarKey,
|
|
||||||
sidebarGroups,
|
|
||||||
hasSidebar,
|
|
||||||
hasAside,
|
|
||||||
leftAside,
|
|
||||||
isSidebarEnabled,
|
|
||||||
open,
|
|
||||||
close,
|
|
||||||
toggle,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a11y: cache the element that opened the Sidebar (the menu button) then
|
* a11y: cache the element that opened the Sidebar (the menu button) then
|
||||||
* focus that button again when Menu is closed with Escape key.
|
* focus that button again when Menu is closed with Escape key.
|
||||||
*/
|
*/
|
||||||
export function useCloseSidebarOnEscape(isOpen: Ref<boolean>, close: () => void): void {
|
export function useCloseSidebarOnEscape(): void {
|
||||||
|
const { disableSidebar } = useSidebarControl()
|
||||||
let triggerElement: HTMLButtonElement | undefined
|
let triggerElement: HTMLButtonElement | undefined
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
triggerElement = isOpen.value
|
triggerElement = isSidebarEnabled.value
|
||||||
? (document.activeElement as HTMLButtonElement)
|
? (document.activeElement as HTMLButtonElement)
|
||||||
: undefined
|
: undefined
|
||||||
})
|
})
|
||||||
@ -167,14 +115,14 @@ export function useCloseSidebarOnEscape(isOpen: Ref<boolean>, close: () => void)
|
|||||||
})
|
})
|
||||||
|
|
||||||
function onEscape(e: KeyboardEvent): void {
|
function onEscape(e: KeyboardEvent): void {
|
||||||
if (e.key === 'Escape' && isOpen.value) {
|
if (e.key === 'Escape' && isSidebarEnabled.value) {
|
||||||
close()
|
disableSidebar()
|
||||||
triggerElement?.focus()
|
triggerElement?.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSidebarControl(item: ComputedRef<ResolvedSidebarItem>): SidebarControl {
|
export function useSidebarItemControl(item: ComputedRef<ResolvedSidebarItem>): SidebarItemControl {
|
||||||
const { page } = useData()
|
const { page } = useData()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
@ -240,3 +188,13 @@ export function useSidebarControl(item: ComputedRef<ResolvedSidebarItem>): Sideb
|
|||||||
toggle,
|
toggle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SidebarItemControl {
|
||||||
|
collapsed: Ref<boolean>
|
||||||
|
collapsible: ComputedRef<boolean>
|
||||||
|
isLink: ComputedRef<boolean>
|
||||||
|
isActiveLink: Ref<boolean>
|
||||||
|
hasActiveLink: ComputedRef<boolean>
|
||||||
|
hasChildren: ComputedRef<boolean>
|
||||||
|
toggle: () => void
|
||||||
|
}
|
||||||
|
|||||||
@ -10,23 +10,18 @@ import VPLocalNav from '@theme/VPLocalNav.vue'
|
|||||||
import VPSidebar from '@theme/VPSidebar.vue'
|
import VPSidebar from '@theme/VPSidebar.vue'
|
||||||
import VPSignDown from '@theme/VPSignDown.vue'
|
import VPSignDown from '@theme/VPSignDown.vue'
|
||||||
import VPSkipLink from '@theme/VPSkipLink.vue'
|
import VPSkipLink from '@theme/VPSkipLink.vue'
|
||||||
import { watch } from 'vue'
|
import { registerWatchers, useData, useEncrypt, useSidebarControl } from '../composables/index.js'
|
||||||
import { useRoute } from 'vuepress/client'
|
|
||||||
import { useCloseSidebarOnEscape, useData, useEncrypt, useSidebar } from '../composables/index.js'
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOpen: isSidebarOpen,
|
isSidebarEnabled,
|
||||||
open: openSidebar,
|
enableSidebar,
|
||||||
close: closeSidebar,
|
disableSidebar,
|
||||||
} = useSidebar()
|
} = useSidebarControl()
|
||||||
|
|
||||||
const { frontmatter } = useData()
|
const { frontmatter } = useData()
|
||||||
const { isGlobalDecrypted, isPageDecrypted } = useEncrypt()
|
const { isGlobalDecrypted, isPageDecrypted } = useEncrypt()
|
||||||
|
|
||||||
const route = useRoute()
|
registerWatchers()
|
||||||
watch(() => route.path, closeSidebar)
|
|
||||||
|
|
||||||
useCloseSidebarOnEscape(isSidebarOpen, closeSidebar)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -42,7 +37,7 @@ useCloseSidebarOnEscape(isSidebarOpen, closeSidebar)
|
|||||||
|
|
||||||
<VPSkipLink />
|
<VPSkipLink />
|
||||||
|
|
||||||
<VPBackdrop :show="isSidebarOpen" @click="closeSidebar" />
|
<VPBackdrop :show="isSidebarEnabled" @click="disableSidebar" />
|
||||||
|
|
||||||
<VPNav>
|
<VPNav>
|
||||||
<template #nav-bar-title-before>
|
<template #nav-bar-title-before>
|
||||||
@ -78,12 +73,12 @@ useCloseSidebarOnEscape(isSidebarOpen, closeSidebar)
|
|||||||
</VPNav>
|
</VPNav>
|
||||||
|
|
||||||
<VPLocalNav
|
<VPLocalNav
|
||||||
:open="isSidebarOpen"
|
:open="isSidebarEnabled"
|
||||||
:show-outline="isPageDecrypted"
|
:show-outline="isPageDecrypted"
|
||||||
@open-menu="openSidebar"
|
@open-menu="enableSidebar"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<VPSidebar :open="isSidebarOpen">
|
<VPSidebar :open="isSidebarEnabled">
|
||||||
<template #sidebar-nav-before>
|
<template #sidebar-nav-before>
|
||||||
<slot name="sidebar-nav-before" />
|
<slot name="sidebar-nav-before" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -167,3 +167,11 @@
|
|||||||
.vpi-close {
|
.vpi-close {
|
||||||
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath d='m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z'/%3E%3Cpath fill='%23000' d='m12 14.122l5.303 5.303a1.5 1.5 0 0 0 2.122-2.122L14.12 12l5.304-5.303a1.5 1.5 0 1 0-2.122-2.121L12 9.879L6.697 4.576a1.5 1.5 0 1 0-2.122 2.12L9.88 12l-5.304 5.304a1.5 1.5 0 1 0 2.122 2.12z'/%3E%3C/g%3E%3C/svg%3E");
|
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath d='m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z'/%3E%3Cpath fill='%23000' d='m12 14.122l5.303 5.303a1.5 1.5 0 0 0 2.122-2.122L14.12 12l5.304-5.303a1.5 1.5 0 1 0-2.122-2.121L12 9.879L6.697 4.576a1.5 1.5 0 1 0-2.122 2.12L9.88 12l-5.304 5.304a1.5 1.5 0 1 0 2.122 2.12z'/%3E%3C/g%3E%3C/svg%3E");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vpi-sidebar-open {
|
||||||
|
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Crect width='18' height='18' x='3' y='3' rx='2'/%3E%3Cpath d='M9 3v18m5-12l3 3l-3 3'/%3E%3C/g%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpi-sidebar-close {
|
||||||
|
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Crect width='18' height='18' x='3' y='3' rx='2'/%3E%3Cpath d='M9 3v18m7-6l-3-3l3-3'/%3E%3C/g%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user