fix(vitepress): downgrade vitepress theme to 1.0.0-alpha8 to fix sidebar

This commit is contained in:
Sagi 2022-10-05 23:42:07 +08:00
parent 7d4544f7b5
commit e3bb4609c3
26 changed files with 258 additions and 230 deletions

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { provide, watch } from 'vue'
import { useData, useRoute } from 'vitepress'
import { useRoute } from 'vitepress'
import { useSidebar, useCloseSidebarOnEscape } from './composables/sidebar.js'
import VPSkipLink from './components/VPSkipLink.vue'
import VPBackdrop from './components/VPBackdrop.vue'
@ -22,12 +22,10 @@ watch(() => route.path, closeSidebar)
useCloseSidebarOnEscape(isSidebarOpen, closeSidebar)
provide('close-sidebar', closeSidebar)
const { frontmatter } = useData()
</script>
<template>
<div v-if="frontmatter.layout !== false" class="Layout">
<div class="Layout">
<slot name="layout-top" />
<VPSkipLink />
<VPBackdrop class="backdrop" :show="isSidebarOpen" @click="closeSidebar" />
@ -63,7 +61,6 @@ const { frontmatter } = useData()
<VPFooter />
<slot name="layout-bottom" />
</div>
<Content v-else />
</template>
<style scoped>

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { DefaultTheme } from 'vitepress/theme'
import docsearch from '@docsearch/js'
import { default as docsearch } from '@docsearch/js'
import { onMounted } from 'vue'
import { useRouter, useRoute, useData } from 'vitepress'
@ -29,8 +29,7 @@ function poll() {
}, 16)
}
const docsearch$ = docsearch.default ?? docsearch
type DocSearchProps = Parameters<typeof docsearch$>[0]
type DocSearchProps = Parameters<typeof docsearch>[0]
function initialize(userOptions: DefaultTheme.AlgoliaSearchOptions) {
// note: multi-lang search support is removed since the theme
@ -75,7 +74,7 @@ function initialize(userOptions: DefaultTheme.AlgoliaSearchOptions) {
}
})
docsearch$(options)
docsearch(options)
}
function getRelativePath(absoluteUrl: string) {

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, watch, onMounted } from 'vue'
import { useData } from 'vitepress'
import { useAside } from '../composables/aside.ts'
import { useAside } from '../composables/aside.js'
const { theme } = useData()
const carbonOptions = theme.value.carbonAds

View File

@ -1,16 +1,19 @@
<script setup lang="ts">
import { useRoute, useData } from 'vitepress'
import { useSidebar } from '../composables/sidebar.ts'
import { useCopyCode } from '../composables/copy-code.js'
import { useSidebar } from '../composables/sidebar.js'
import Theme from '@theme/index'
import VPPage from './VPPage.vue'
import VPHome from './VPHome.vue'
import VPDoc from './VPDoc.vue'
import { inject } from 'vue'
const route = useRoute()
const { frontmatter } = useData()
const { hasSidebar } = useSidebar()
const NotFound = inject('NotFound')
const NotFound = Theme.NotFound || (() => '404 Not Found')
useCopyCode()
</script>
<template>

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vitepress'
import { computed, provide, ref } from 'vue'
import { useSidebar } from '../composables/sidebar.ts'
import { useSidebar } from '../composables/sidebar.js'
import VPDocAside from './VPDocAside.vue'
import VPDocFooter from './VPDocFooter.vue'
@ -11,9 +11,6 @@ const { hasSidebar, hasAside } = useSidebar()
const pageName = computed(() =>
route.path.replace(/[./]+/g, '_').replace(/_html$/, '')
)
const onContentUpdated = ref()
provide('onContentUpdated', onContentUpdated)
</script>
<template>
@ -42,7 +39,7 @@ provide('onContentUpdated', onContentUpdated)
<div class="content-container">
<slot name="doc-before" />
<main class="main">
<Content class="vp-doc" :class="pageName" :onContentUpdated="onContentUpdated" />
<Content class="vp-doc" :class="pageName" />
</main>
<slot name="doc-footer-before" />
<VPDocFooter />
@ -76,6 +73,10 @@ provide('onContentUpdated', onContentUpdated)
max-width: 992px;
}
.VPDoc:not(.has-sidebar) .aside {
display: block;
}
.VPDoc:not(.has-sidebar) .content {
max-width: 752px;
}

View File

@ -3,7 +3,7 @@ import { useData } from 'vitepress'
import VPDocAsideOutline from './VPDocAsideOutline.vue'
import VPDocAsideCarbonAds from './VPDocAsideCarbonAds.vue'
const { theme } = useData()
const { page, theme } = useData()
</script>
<template>
@ -11,7 +11,7 @@ const { theme } = useData()
<slot name="aside-top" />
<slot name="aside-outline-before" />
<VPDocAsideOutline />
<VPDocAsideOutline v-if="page.headers.length" />
<slot name="aside-outline-after" />
<div class="spacer" />

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
const VPCarbonAds = __CARBON__
const VPCarbonAds = __ALGOLIA__
? defineAsyncComponent(() => import('./VPCarbonAds.vue'))
: () => null
</script>

View File

@ -1,33 +1,25 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useData } from 'vitepress'
import type { DefaultTheme } from 'vitepress/theme'
import { computed, inject, ref, type Ref } from 'vue'
import {
getHeaders,
useActiveAnchor,
type MenuItem
} from '../composables/outline.ts'
import VPDocAsideOutlineItem from './VPDocAsideOutlineItem.vue'
resolveHeaders,
useOutline,
useActiveAnchor
} from '../composables/outline.js'
const { frontmatter, theme } = useData()
const { page, frontmatter, theme } = useData()
const pageOutline = computed<DefaultTheme.Config['outline']>(
() => frontmatter.value.outline ?? theme.value.outline
)
const onContentUpdated = inject('onContentUpdated') as Ref<() => void>
onContentUpdated.value = () => {
headers.value = getHeaders(pageOutline.value)
}
const headers = ref<MenuItem[]>([])
const hasOutline = computed(() => headers.value.length > 0)
const { hasOutline } = useOutline()
const container = ref()
const marker = ref()
useActiveAnchor(container, marker)
const resolvedHeaders = computed(() => {
return resolveHeaders(page.value.headers)
})
function handleClick({ target: el }: Event) {
const id = '#' + (el as HTMLAnchorElement).href!.split('#')[1]
const heading = document.querySelector<HTMLAnchorElement>(
@ -50,7 +42,24 @@ function handleClick({ target: el }: Event) {
<span class="visually-hidden" id="doc-outline-aria-label">
Table of Contents for current page
</span>
<VPDocAsideOutlineItem :headers="headers" :root="true" :onClick="handleClick" />
<ul class="root">
<li
v-for="{ text, link, children, hidden } in resolvedHeaders"
v-show="!hidden"
>
<a class="outline-link" :href="link" @click="handleClick">
{{ text }}
</a>
<ul v-if="children && frontmatter.outline === 'deep'">
<li v-for="{ text, link, hidden } in children" v-show="!hidden">
<a class="outline-link nested" :href="link" @click="handleClick">
{{ text }}
</a>
</li>
</ul>
</li>
</ul>
</nav>
</div>
</div>
@ -91,4 +100,29 @@ function handleClick({ target: el }: Event) {
font-size: 13px;
font-weight: 600;
}
.outline-link {
display: block;
line-height: 28px;
color: var(--vp-c-text-2);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: color 0.5s;
}
.outline-link:hover,
.outline-link.active {
color: var(--vp-c-text-1);
transition: color 0.25s;
}
.outline-link.nested {
padding-left: 13px;
}
.root {
position: relative;
z-index: 1;
}
</style>

View File

@ -1,51 +0,0 @@
<script setup lang="ts">
import type { MenuItem } from '../composables/outline.ts'
defineProps<{
headers: MenuItem[]
onClick: (e: MouseEvent) => void
root?: boolean
}>()
</script>
<template>
<ul :class="root ? 'root' : 'nested'">
<li v-for="{ children, link, title } in headers">
<a class="outline-link" :href="link" @click="onClick">{{ title }}</a>
<template v-if="children?.length">
<VPDocAsideOutlineItem :headers="children" :onClick="onClick" />
</template>
</li>
</ul>
</template>
<style scoped>
.root {
position: relative;
z-index: 1;
}
.nested {
padding-left: 13px;
}
.outline-link {
display: block;
line-height: 28px;
color: var(--vp-c-text-2);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: color 0.5s;
}
.outline-link:hover,
.outline-link.active {
color: var(--vp-c-text-1);
transition: color 0.25s;
}
.outline-link.nested {
padding-left: 13px;
}
</style>

View File

@ -9,8 +9,8 @@ const { hasSidebar } = useSidebar()
<template>
<footer v-if="theme.footer" class="VPFooter" :class="{ 'has-sidebar': hasSidebar }">
<div class="container">
<p v-if="theme.footer.message" class="message" v-html="theme.footer.message"></p>
<p v-if="theme.footer.copyright" class="copyright" v-html="theme.footer.copyright"></p>
<p class="message" v-html="theme.footer.message"></p>
<p class="copyright" v-html="theme.footer.copyright"></p>
</div>
</footer>
</template>

View File

@ -20,7 +20,6 @@ export default {
class="VPImage"
v-bind="typeof image === 'string' ? $attrs : { ...image, ...$attrs }"
:src="withBase(typeof image === 'string' ? image : image.src)"
:alt="typeof image === 'string' ? '' : (image.alt || '')"
/>
<template v-else>
<VPImage class="dark" :image="image.dark" v-bind="$attrs" />

View File

@ -102,7 +102,7 @@ const { hasSidebar } = useSidebar()
}
.container :deep(*) {
pointer-events: auto;
pointer-events: all;
}
.content {

View File

@ -4,15 +4,12 @@ import VPFlyout from './VPFlyout.vue'
import VPMenuLink from './VPMenuLink.vue'
import VPSwitchAppearance from './VPSwitchAppearance.vue'
import VPSocialLinks from './VPSocialLinks.vue'
import { computed } from 'vue'
const { site, theme } = useData()
const hasExtraContent = computed(() => theme.value.localeLinks || site.value.appearance || theme.value.socialLinks)
</script>
<template>
<VPFlyout v-if="hasExtraContent" class="VPNavBarExtra" label="extra navigation">
<VPFlyout class="VPNavBarExtra" label="extra navigation">
<div v-if="theme.localeLinks" class="group">
<p class="trans-title">{{ theme.localeLinks.text }}</p>

View File

@ -257,7 +257,7 @@ function load() {
.DocSearch-Button .DocSearch-Button-Key:first-child {
font-size: 1px;
letter-spacing: -12px;
letter-spacing: -1px;
color: transparent;
}

View File

@ -52,7 +52,7 @@ function unlockBodyScroll() {
background-color: var(--vp-c-bg);
overflow-y: auto;
transition: background-color 0.5s;
pointer-events: auto;
pointer-events: all;
}
.VPNavScreen.fade-enter-active,

View File

@ -88,10 +88,6 @@ function toggle() {
display: block;
}
.VPSidebarGroup.collapsible .title {
cursor: pointer;
}
.title:hover .action {
color: var(--vp-c-text-2);
}

View File

@ -5,35 +5,29 @@ import { useData } from 'vitepress'
import { isActive } from '../support/utils.js'
import VPLink from './VPLink.vue'
withDefaults(
defineProps<{ item: DefaultTheme.SidebarItem; depth?: number }>(),
{ depth: 1 }
)
withDefaults(defineProps<{ item: DefaultTheme.SidebarItem; depth?: number }>(), { depth: 1 })
const { page, frontmatter } = useData()
const maxDepth = computed<number>(
() => frontmatter.value.sidebarDepth || Infinity
)
const maxDepth = computed<number>(() => frontmatter.value.sidebarDepth || Infinity)
const closeSideBar = inject('close-sidebar') as () => void
</script>
<template>
<VPLink
class="link"
:class="{ active: isActive(page.relativePath, item.link) }"
:style="{ paddingLeft: 16 * (depth - 1) + 'px' }"
:class="{ active: isActive(page.relativePath, item.link), offset: depth > 1 }"
:href="item.link"
@click="closeSideBar"
>
<span class="link-text" :class="{ light: depth > 1 }">{{ item.text }}</span>
<template
v-if="'items' in item && depth < maxDepth"
v-for="child in item.items"
:key="child.link"
>
<VPSidebarLink :item="child" :depth="depth + 1" />
</template>
</VPLink>
<template
v-if="'items' in item && depth < maxDepth"
v-for="child in item.items"
:key="child.link"
>
<VPSidebarLink :item="child" :depth="depth + 1" />
</template>
</template>
<style scoped>
@ -44,6 +38,10 @@ const closeSideBar = inject('close-sidebar') as () => void
transition: color 0.5s;
}
.link.offset {
padding-left: 16px;
}
.link:hover {
color: var(--vp-c-text-1);
}

View File

@ -1,17 +1,11 @@
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { APPEARANCE_KEY } from '../shared/shared.ts'
import VPSwitch from './VPSwitch.vue'
import VPIconSun from './icons/VPIconSun.vue'
import VPIconMoon from './icons/VPIconMoon.vue'
const checked = ref(false)
const toggle = typeof localStorage !== 'undefined' ? useAppearance() : () => {}
onMounted(() => {
checked.value = document.documentElement.classList.contains('dark')
})
function useAppearance() {
const query = window.matchMedia('(prefers-color-scheme: dark)')
const classList = document.documentElement.classList
@ -39,7 +33,6 @@ function useAppearance() {
}
function setClass(dark: boolean): void {
checked.value = dark
classList[dark ? 'add' : 'remove']('dark')
}
@ -51,7 +44,6 @@ function useAppearance() {
<VPSwitch
class="VPSwitchAppearance"
aria-label="toggle dark mode"
:aria-checked="checked"
@click="toggle"
>
<VPIconSun class="sun" />

View File

@ -0,0 +1,94 @@
import { nextTick, watch } from 'vue'
import { inBrowser, useData } from 'vitepress'
export function useCopyCode() {
const { page } = useData()
if (inBrowser)
watch(
() => page.value.relativePath,
() => {
nextTick(() => {
document
.querySelectorAll<HTMLSpanElement>(
'.vp-doc div[class*="language-"] > button.copy'
)
.forEach(handleElement)
})
},
{ immediate: true, flush: 'post' }
)
}
async function copyToClipboard(text: string) {
try {
return navigator.clipboard.writeText(text)
} catch {
const element = document.createElement('textarea')
const previouslyFocusedElement = document.activeElement
element.value = text
// Prevent keyboard from showing on mobile
element.setAttribute('readonly', '')
element.style.contain = 'strict'
element.style.position = 'absolute'
element.style.left = '-9999px'
element.style.fontSize = '12pt' // Prevent zooming on iOS
const selection = document.getSelection()
const originalRange = selection
? selection.rangeCount > 0 && selection.getRangeAt(0)
: null
document.body.appendChild(element)
element.select()
// Explicit selection workaround for iOS
element.selectionStart = 0
element.selectionEnd = text.length
document.execCommand('copy')
document.body.removeChild(element)
if (originalRange) {
selection!.removeAllRanges() // originalRange can't be truthy when selection is falsy
selection!.addRange(originalRange)
}
// Get the focus back on the previously focused element, if any
if (previouslyFocusedElement) {
;(previouslyFocusedElement as HTMLElement).focus()
}
}
}
function handleElement(el: HTMLElement) {
el.onclick = () => {
const parent = el.parentElement
const sibling = el.nextElementSibling
?.nextElementSibling as HTMLPreElement | null
if (!parent || !sibling) {
return
}
const isShell = /language-(shellscript|shell|bash|sh|zsh)/.test(
parent.classList.toString()
)
let { innerText: text = '' } = sibling
if (isShell) {
text = text.replace(/^ *(\$|>) /gm, '')
}
copyToClipboard(text).then(() => {
el.classList.add('copied')
setTimeout(() => {
el.classList.remove('copied')
el.blur()
}, 2000)
})
}
}

View File

@ -1,87 +1,64 @@
import type { DefaultTheme } from 'vitepress/theme'
import { onMounted, onUnmounted, onUpdated, type Ref } from 'vue'
import type { Header } from '../../shared.js'
import { Ref, computed, onMounted, onUpdated, onUnmounted } from 'vue'
import { Header, useData } from 'vitepress'
import { useAside } from '../composables/aside.js'
import { throttleAndDebounce } from '../support/utils.js'
interface HeaderWithChildren extends Header {
children?: Header[]
hidden?: boolean
}
interface MenuItemWithLinkAndChildren {
text: string
link: string
children?: MenuItemWithLinkAndChildren[]
hidden?: boolean
}
// magic number to avoid repeated retrieval
const PAGE_OFFSET = 71
const PAGE_OFFSET = 56
export type MenuItem = Omit<Header, 'slug' | 'children'> & {
children?: MenuItem[]
}
export function useOutline() {
const { page } = useData()
export function getHeaders(pageOutline: DefaultTheme.Config['outline']) {
if (pageOutline === false) return []
let updatedHeaders: MenuItem[] = []
document
.querySelectorAll<HTMLHeadingElement>('h2, h3, h4, h5, h6')
.forEach((el) => {
if (el.textContent && el.id) {
updatedHeaders.push({
level: Number(el.tagName[1]),
title: el.innerText.replace(/\s+#\s*$/, ''),
link: `#${el.id}`
})
}
})
return resolveHeaders(updatedHeaders, pageOutline)
}
export function resolveHeaders(
headers: MenuItem[],
levelsRange: Exclude<DefaultTheme.Config['outline'], false> = 2
) {
const levels: [number, number] =
typeof levelsRange === 'number'
? [levelsRange, levelsRange]
: levelsRange === 'deep'
? [2, 6]
: levelsRange
return groupHeaders(headers, levels)
}
function groupHeaders(headers: MenuItem[], levelsRange: [number, number]) {
const result: MenuItem[] = []
headers = headers.map((h) => ({ ...h }))
headers.forEach((h, index) => {
if (h.level >= levelsRange[0] && h.level <= levelsRange[1]) {
if (addToParent(index, headers, levelsRange)) {
result.push(h)
}
}
const hasOutline = computed(() => {
return page.value.headers.length > 0
})
return result
return {
hasOutline
}
}
function addToParent(
currIndex: number,
headers: MenuItem[],
levelsRange: [number, number]
) {
if (currIndex === 0) {
return true
}
export function resolveHeaders(headers: Header[]) {
return mapHeaders(groupHeaders(headers))
}
const currentHeader = headers[currIndex]
for (let index = currIndex - 1; index >= 0; index--) {
const header = headers[index]
function groupHeaders(headers: Header[]): HeaderWithChildren[] {
headers = headers.map((h) => Object.assign({}, h))
if (
header.level < currentHeader.level &&
header.level >= levelsRange[0] &&
header.level <= levelsRange[1]
) {
if (header.children == null) header.children = []
header.children.push(currentHeader)
return false
let lastH2: HeaderWithChildren | undefined
for (const h of headers) {
if (h.level === 2) {
lastH2 = h
} else if (lastH2 && h.level <= 3) {
;(lastH2.children || (lastH2.children = [])).push(h)
}
}
return true
return headers.filter((h) => h.level === 2)
}
function mapHeaders(
headers: HeaderWithChildren[]
): MenuItemWithLinkAndChildren[] {
return headers.map((header) => ({
text: header.title,
link: `#${header.slug}`,
children: header.children ? mapHeaders(header.children) : undefined,
hidden: header.hidden
}))
}
export function useActiveAnchor(
@ -174,7 +151,7 @@ export function useActiveAnchor(
}
function getAnchorTop(anchor: HTMLAnchorElement): number {
return anchor.parentElement!.offsetTop - PAGE_OFFSET
return anchor.parentElement!.offsetTop - PAGE_OFFSET - 15
}
function isAnchorActive(

View File

@ -23,9 +23,13 @@ export function useSidebar() {
})
const hasAside = computed(() => {
return (
frontmatter.value.layout !== 'home' && frontmatter.value.aside !== false
if (
frontmatter.value.layout !== 'home' &&
frontmatter.value.aside === false
)
return false
return hasSidebar.value
})
function open() {

View File

@ -7,15 +7,15 @@ export interface GridSetting {
export type GridSize = 'xmini' | 'mini' | 'small' | 'medium' | 'big'
export interface UseSponsorsGridOptions {
export interface UseSponsorsGridOprions {
el: Ref<HTMLElement | null>
size?: GridSize
}
/**
* Defines grid configuration for each sponsor size in tuple.
* Defines grid configuration for each sponsor size in touple.
*
* [Screen width, Column size]
* [Screen widh, Column size]
*
* It sets grid size on matching screen size. For example, `[768, 5]` will
* set 5 columns when screen size is bigger or equal to 768px.
@ -49,7 +49,7 @@ const GridSettings: GridSetting = {
export function useSponsorsGrid({
el,
size = 'medium'
}: UseSponsorsGridOptions) {
}: UseSponsorsGridOprions) {
const onResize = throttleAndDebounce(manage, 100)
onMounted(() => {

View File

@ -147,8 +147,8 @@ button {
background-image: none;
}
button:enabled,
[role='button']:enabled {
button,
[role='button'] {
cursor: pointer;
}
@ -213,13 +213,3 @@ fieldset {
margin: 0;
padding: 0;
}
h1,
h2,
h3,
h4,
h5,
h6,
p {
overflow-wrap: break-word;
}

View File

@ -145,12 +145,10 @@
* -------------------------------------------------------------------------- */
:root {
--vp-font-family-base: 'Inter var experimental', 'Inter var', ui-sans-serif,
system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Helvetica, Arial, 'Noto Sans', sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--vp-font-family-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco,
Consolas, 'Liberation Mono', 'Courier New', monospace;
--vp-font-family-base: 'Inter var experimental', 'Inter var', -apple-system,
BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
--vp-font-family-mono: Menlo, Monaco, Consolas, 'Courier New', monospace;
}
/**

View File

@ -1,5 +1,5 @@
import type { DefaultTheme } from 'vitepress/theme'
import { ensureStartingSlash } from './utils'
import { ensureStartingSlash } from './utils.js'
/**
* Get the `Sidebar` from sidebar option. This method will ensure to get correct

View File

@ -13,12 +13,12 @@ export function isExternal(path: string): boolean {
}
export function throttleAndDebounce(fn: () => void, delay: number): () => void {
let timeoutId: NodeJS.Timeout
let timeout: any
let called = false
return () => {
if (timeoutId) {
clearTimeout(timeoutId)
if (timeout) {
clearTimeout(timeout)
}
if (!called) {
@ -28,7 +28,7 @@ export function throttleAndDebounce(fn: () => void, delay: number): () => void {
called = false
}, delay)
} else {
timeoutId = setTimeout(fn, delay)
timeout = setTimeout(fn, delay)
}
}
}