Merge branch 'main' into feat/multithread-render

pull/3386/head
Yuxuan Zhang 2 years ago committed by GitHub
commit ba0c3f5969
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -625,11 +625,11 @@ const line4 = 'This is line 4'
```md ```md
<<< @/snippets/snippet.cs{c#} <<< @/snippets/snippet.cs{c#}
<!-- with line highlighting: --> <!-- 带行高亮: -->
<<< @/snippets/snippet.cs{1,2,4-6 c#} <<< @/snippets/snippet.cs{1,2,4-6 c#}
<!-- with line numbers: --> <!-- 带行号: -->
<<< @/snippets/snippet.cs{1,2,4-6 c#:line-numbers} <<< @/snippets/snippet.cs{1,2,4-6 c#:line-numbers}
``` ```

@ -44,9 +44,9 @@ export interface VitePressData<T = any> {
title: Ref<string> title: Ref<string>
description: Ref<string> description: Ref<string>
lang: Ref<string> lang: Ref<string>
isDark: Ref<boolean>
dir: Ref<string> dir: Ref<string>
localeIndex: Ref<string> localeIndex: Ref<string>
isDark: Ref<boolean>
} }
// site data is a singleton // site data is a singleton
@ -89,14 +89,12 @@ export function initData(route: Route): VitePressData {
frontmatter: computed(() => route.data.frontmatter), frontmatter: computed(() => route.data.frontmatter),
params: computed(() => route.data.params), params: computed(() => route.data.params),
lang: computed(() => site.value.lang), lang: computed(() => site.value.lang),
dir: computed(() => route.data.frontmatter.dir || site.value.dir || 'ltr'), dir: computed(() => route.data.frontmatter.dir || site.value.dir),
localeIndex: computed(() => site.value.localeIndex || 'root'), localeIndex: computed(() => site.value.localeIndex || 'root'),
title: computed(() => { title: computed(() => createTitle(site.value, route.data)),
return createTitle(site.value, route.data) description: computed(
}), () => route.data.description || site.value.description
description: computed(() => { ),
return route.data.description || site.value.description
}),
isDark isDark
} }
} }

@ -38,13 +38,13 @@ const Theme = resolveThemeExtends(RawTheme)
const VitePressApp = defineComponent({ const VitePressApp = defineComponent({
name: 'VitePressApp', name: 'VitePressApp',
setup() { setup() {
const { site } = useData() const { site, lang, dir } = useData()
// change the language on the HTML element based on the current lang // change the language on the HTML element based on the current lang
onMounted(() => { onMounted(() => {
watchEffect(() => { watchEffect(() => {
document.documentElement.lang = site.value.lang document.documentElement.lang = lang.value
document.documentElement.dir = site.value.dir document.documentElement.dir = dir.value
}) })
}) })

@ -4,10 +4,11 @@ import type { Header } from '../../shared'
import { useAside } from './aside' import { useAside } from './aside'
import { throttleAndDebounce } from '../support/utils' import { throttleAndDebounce } from '../support/utils'
// magic number to avoid repeated retrieval // cached list of anchor elements from resolveHeaders
const PAGE_OFFSET = 71 const resolvedHeaders: { element: HTMLHeadElement; link: string }[] = []
export type MenuItem = Omit<Header, 'slug' | 'children'> & { export type MenuItem = Omit<Header, 'slug' | 'children'> & {
element: HTMLHeadElement
children?: MenuItem[] children?: MenuItem[]
} }
@ -29,6 +30,7 @@ export function getHeaders(range: DefaultTheme.Config['outline']) {
.map((el) => { .map((el) => {
const level = Number(el.tagName[1]) const level = Number(el.tagName[1])
return { return {
element: el as HTMLHeadElement,
title: serializeHeader(el), title: serializeHeader(el),
link: '#' + el.id, link: '#' + el.id,
level level
@ -78,6 +80,12 @@ export function resolveHeaders(
: levelsRange : levelsRange
headers = headers.filter((h) => h.level >= high && h.level <= low) headers = headers.filter((h) => h.level >= high && h.level <= low)
// clear previous caches
resolvedHeaders.length = 0
// update global header list for active link rendering
for (const { element, link } of headers) {
resolvedHeaders.push({ element, link })
}
const ret: MenuItem[] = [] const ret: MenuItem[] = []
outer: for (let i = 0; i < headers.length; i++) { outer: for (let i = 0; i < headers.length; i++) {
@ -128,40 +136,55 @@ export function useActiveAnchor(
return return
} }
const links = [].slice.call( // pixel offset, start of main content
container.value.querySelectorAll('.outline-link') const offsetDocTop = (() => {
) as HTMLAnchorElement[] const container =
document.querySelector('#VPContent .VPDoc')?.firstElementChild
const anchors = [].slice if (container) return getAbsoluteTop(container as HTMLElement)
.call(document.querySelectorAll('.content .header-anchor')) else return 78
.filter((anchor: HTMLAnchorElement) => { })()
return links.some((link) => {
return link.hash === anchor.hash && anchor.offsetParent !== null
})
}) as HTMLAnchorElement[]
const scrollY = window.scrollY const scrollY = window.scrollY
const innerHeight = window.innerHeight const innerHeight = window.innerHeight
const offsetHeight = document.body.offsetHeight const offsetHeight = document.body.offsetHeight
const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1 const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1
// page bottom - highlight last one // resolvedHeaders may be repositioned, hidden or fix positioned
if (anchors.length && isBottom) { const headers = resolvedHeaders
activateLink(anchors[anchors.length - 1].hash) .map(({ element, link }) => ({
link,
top: getAbsoluteTop(element)
}))
.filter(({ top }) => !Number.isNaN(top))
.sort((a, b) => a.top - b.top)
// no headers available for active link
if (!headers.length) {
activateLink(null)
return return
} }
for (let i = 0; i < anchors.length; i++) { // page top
const anchor = anchors[i] if (scrollY < 1) {
const nextAnchor = anchors[i + 1] activateLink(null)
return
}
const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor) // page bottom - highlight last link
if (isBottom) {
activateLink(headers[headers.length - 1].link)
return
}
if (isActive) { // find the last header above the top of viewport
activateLink(hash) let activeLink: string | null = null
return for (const { link, top } of headers) {
if (top > scrollY + offsetDocTop) {
break
} }
activeLink = link
} }
activateLink(activeLink)
} }
function activateLink(hash: string | null) { function activateLink(hash: string | null) {
@ -190,28 +213,18 @@ export function useActiveAnchor(
} }
} }
function getAnchorTop(anchor: HTMLAnchorElement): number { function getAbsoluteTop(element: HTMLElement): number {
return anchor.parentElement!.offsetTop - PAGE_OFFSET let offsetTop = 0
} while (element !== document.body) {
if (element === null) {
function isAnchorActive( // child element is:
index: number, // - not attached to the DOM (display: none)
anchor: HTMLAnchorElement, // - set to fixed position (not scrollable)
nextAnchor: HTMLAnchorElement | undefined // - body or html element (null offsetParent)
): [boolean, string | null] { return NaN
const scrollTop = window.scrollY }
offsetTop += element.offsetTop
if (index === 0 && scrollTop === 0) { element = element.offsetParent as HTMLElement
return [true, null]
}
if (scrollTop < getAnchorTop(anchor)) {
return [false, null]
}
if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) {
return [true, anchor.hash]
} }
return offsetTop
return [false, null]
} }

@ -168,9 +168,11 @@ export async function renderPage(
} }
} }
const dir = pageData.frontmatter.dir || siteData.dir || 'ltr'
const html = [ const html = [
`<!DOCTYPE html>`, `<!DOCTYPE html>`,
`<html lang="${siteData.lang}" dir="${siteData.dir}">`, `<html lang="${siteData.lang}" dir="${dir}">`,
`<head>`, `<head>`,
`<meta charset="utf-8">`, `<meta charset="utf-8">`,
isMetaViewportOverridden(head) isMetaViewportOverridden(head)

Loading…
Cancel
Save