210 lines
4.7 KiB
TypeScript
210 lines
4.7 KiB
TypeScript
import type { PageData } from 'vuepress/client'
|
|
import { usePageData, usePageFrontmatter, withBase } from 'vuepress/client'
|
|
import type {
|
|
NotesData,
|
|
NotesSidebarItem,
|
|
} from '@vuepress-plume/plugin-notes-data'
|
|
import { useNotesData } from '@vuepress-plume/plugin-notes-data/client'
|
|
import { useMediaQuery } from '@vueuse/core'
|
|
import type { ComputedRef, Ref } from 'vue'
|
|
import { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import type { PlumeThemePageData } from '../../shared/index.js'
|
|
import { isActive } from '../utils/index.js'
|
|
import { useThemeLocaleData } from './themeData.js'
|
|
|
|
export function normalizePath(path: string) {
|
|
return path.replace(/\/\\+/g, '/').replace(/\\+/g, '/')
|
|
}
|
|
|
|
export function getSidebarList(path: string, notesData: NotesData) {
|
|
const link = Object.keys(notesData).find(link =>
|
|
path.startsWith(normalizePath(withBase(link))),
|
|
)
|
|
return link ? notesData[link] : []
|
|
}
|
|
|
|
export function useSidebar() {
|
|
const route = useRoute()
|
|
const notesData = useNotesData()
|
|
const theme = useThemeLocaleData()
|
|
const frontmatter = usePageFrontmatter()
|
|
const page = usePageData<PlumeThemePageData>()
|
|
|
|
const is960 = useMediaQuery('(min-width: 960px)')
|
|
|
|
const isOpen = ref(false)
|
|
|
|
const sidebar = computed(() => {
|
|
return theme.value.notes ? getSidebarList(route.path, notesData.value) : []
|
|
})
|
|
const hasSidebar = computed(() => {
|
|
return (
|
|
!frontmatter.value.home
|
|
&& !page.value.isBlogPost
|
|
&& sidebar.value.length > 0
|
|
&& frontmatter.value.sidebar !== false
|
|
)
|
|
})
|
|
|
|
const hasAside = computed(() => {
|
|
return !frontmatter.value.home && frontmatter.value.aside !== false
|
|
})
|
|
|
|
const isSidebarEnabled = computed(() => hasSidebar.value && is960.value)
|
|
|
|
const sidebarGroups = computed(() => {
|
|
return hasSidebar.value ? getSidebarGroups(sidebar.value) : []
|
|
})
|
|
|
|
function open() {
|
|
isOpen.value = true
|
|
}
|
|
|
|
function close() {
|
|
isOpen.value = false
|
|
}
|
|
|
|
function toggle() {
|
|
isOpen.value ? close() : open()
|
|
}
|
|
|
|
return {
|
|
isOpen,
|
|
sidebar,
|
|
hasSidebar,
|
|
hasAside,
|
|
isSidebarEnabled,
|
|
sidebarGroups,
|
|
open,
|
|
close,
|
|
toggle,
|
|
}
|
|
}
|
|
|
|
export function useCloseSidebarOnEscape(
|
|
isOpen: Ref<boolean>,
|
|
close: () => void,
|
|
) {
|
|
let triggerElement: HTMLButtonElement | undefined
|
|
|
|
watchEffect(() => {
|
|
triggerElement = isOpen.value
|
|
? (document.activeElement as HTMLButtonElement)
|
|
: undefined
|
|
})
|
|
|
|
onMounted(() => {
|
|
window.addEventListener('keyup', onEscape)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener('keyup', onEscape)
|
|
})
|
|
|
|
function onEscape(e: KeyboardEvent) {
|
|
if (e.key === 'Escape' && isOpen.value) {
|
|
close()
|
|
triggerElement?.focus()
|
|
}
|
|
}
|
|
}
|
|
|
|
export function useSidebarControl(item: ComputedRef<NotesSidebarItem>) {
|
|
const page = usePageData<PageData>()
|
|
|
|
const collapsed = ref(false)
|
|
|
|
const collapsible = computed(() => {
|
|
return item.value.collapsed != null
|
|
})
|
|
|
|
const isLink = computed(() => {
|
|
return !!item.value.link
|
|
})
|
|
|
|
const isActiveLink = computed(() => {
|
|
return isActive(page.value.path, item.value.link)
|
|
})
|
|
|
|
const hasActiveLink = computed(() => {
|
|
if (isActiveLink.value)
|
|
return true
|
|
|
|
return item.value.items
|
|
? containsActiveLink(
|
|
page.value.path,
|
|
item.value.items as NotesSidebarItem[],
|
|
)
|
|
: false
|
|
})
|
|
|
|
const hasChildren = computed(() => {
|
|
return !!(item.value.items && item.value.items.length)
|
|
})
|
|
|
|
watchEffect(() => {
|
|
collapsed.value = !!(collapsible.value && item.value.collapsed)
|
|
})
|
|
|
|
watchEffect(() => {
|
|
;(isActiveLink.value || hasActiveLink.value) && (collapsed.value = false)
|
|
})
|
|
|
|
function toggle() {
|
|
if (collapsible.value)
|
|
collapsed.value = !collapsed.value
|
|
}
|
|
|
|
return {
|
|
collapsed,
|
|
collapsible,
|
|
isLink,
|
|
isActiveLink,
|
|
hasActiveLink,
|
|
hasChildren,
|
|
toggle,
|
|
}
|
|
}
|
|
|
|
export function containsActiveLink(
|
|
path: string,
|
|
items: NotesSidebarItem | NotesSidebarItem[],
|
|
): boolean {
|
|
if (Array.isArray(items))
|
|
return items.some(item => containsActiveLink(path, item))
|
|
|
|
return isActive(path, items.link)
|
|
? true
|
|
: items.items
|
|
? containsActiveLink(path, items.items as NotesSidebarItem[])
|
|
: false
|
|
}
|
|
|
|
/**
|
|
* Get or generate sidebar group from the given sidebar items.
|
|
*/
|
|
export function getSidebarGroups(
|
|
sidebar: NotesSidebarItem[],
|
|
): NotesSidebarItem[] {
|
|
const groups: NotesSidebarItem[] = []
|
|
|
|
let lastGroupIndex = 0
|
|
|
|
for (const index in sidebar) {
|
|
const item = sidebar[index]
|
|
|
|
if (item.items) {
|
|
lastGroupIndex = groups.push(item)
|
|
continue
|
|
}
|
|
|
|
if (!groups[lastGroupIndex])
|
|
groups.push({ items: [] })
|
|
|
|
groups[lastGroupIndex]!.items!.push(item)
|
|
}
|
|
|
|
return groups
|
|
}
|