Skip to content

Commit 0ce5ece

Browse files
authored
fix(theme): misaligned outline indicator (#3458)
1 parent 2b13bc9 commit 0ce5ece

File tree

6 files changed

+105
-92
lines changed

6 files changed

+105
-92
lines changed

Diff for: docs/.vitepress/config/index.ts

+3-57
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,10 @@
11
import { defineConfig } from 'vitepress'
2+
import { shared } from './shared'
23
import { en } from './en'
3-
import { zh, search as zhSearch } from './zh'
4+
import { zh } from './zh'
45

56
export default defineConfig({
6-
title: 'VitePress',
7-
8-
lastUpdated: true,
9-
cleanUrls: true,
10-
11-
markdown: {
12-
math: true,
13-
codeTransformers: [
14-
// We use `[!!code` in demo to prevent transformation, here we revert it back.
15-
{
16-
postprocess(code) {
17-
return code.replace(/\[\!\!code/g, '[!code')
18-
}
19-
}
20-
]
21-
},
22-
23-
sitemap: {
24-
hostname: 'https://vitepress.dev',
25-
transformItems(items) {
26-
return items.filter((item) => !item.url.includes('migration'))
27-
}
28-
},
29-
30-
/* prettier-ignore */
31-
head: [
32-
['link', { rel: 'icon', type: 'image/svg+xml', href: '/vitepress-logo-mini.svg' }],
33-
['link', { rel: 'icon', type: 'image/png', href: '/vitepress-logo-mini.png' }],
34-
['meta', { name: 'theme-color', content: '#5f67ee' }],
35-
['meta', { name: 'og:type', content: 'website' }],
36-
['meta', { name: 'og:locale', content: 'en' }],
37-
['meta', { name: 'og:site_name', content: 'VitePress' }],
38-
['meta', { name: 'og:image', content: 'https://vitepress.dev/vitepress-og.jpg' }],
39-
['script', { src: 'https://cdn.usefathom.com/script.js', 'data-site': 'AZBRSFGG', 'data-spa': 'auto', defer: '' }]
40-
],
41-
42-
themeConfig: {
43-
logo: { src: '/vitepress-logo-mini.svg', width: 24, height: 24 },
44-
45-
socialLinks: [
46-
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
47-
],
48-
49-
search: {
50-
provider: 'algolia',
51-
options: {
52-
appId: '8J64VVRP8K',
53-
apiKey: 'a18e2f4cc5665f6602c5631fd868adfd',
54-
indexName: 'vitepress',
55-
locales: { ...zhSearch }
56-
}
57-
},
58-
59-
carbonAds: { code: 'CEBDT27Y', placement: 'vuejsorg' }
60-
},
61-
7+
...shared,
628
locales: {
639
root: { label: 'English', ...en },
6410
zh: { label: '简体中文', ...zh }

Diff for: docs/.vitepress/config/shared.ts

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { defineConfig } from 'vitepress'
2+
import { search as zhSearch } from './zh'
3+
4+
export const shared = defineConfig({
5+
title: 'VitePress',
6+
7+
lastUpdated: true,
8+
cleanUrls: true,
9+
10+
markdown: {
11+
math: true,
12+
codeTransformers: [
13+
// We use `[!!code` in demo to prevent transformation, here we revert it back.
14+
{
15+
postprocess(code) {
16+
return code.replace(/\[\!\!code/g, '[!code')
17+
}
18+
}
19+
]
20+
},
21+
22+
sitemap: {
23+
hostname: 'https://vitepress.dev',
24+
transformItems(items) {
25+
return items.filter((item) => !item.url.includes('migration'))
26+
}
27+
},
28+
29+
/* prettier-ignore */
30+
head: [
31+
['link', { rel: 'icon', type: 'image/svg+xml', href: '/vitepress-logo-mini.svg' }],
32+
['link', { rel: 'icon', type: 'image/png', href: '/vitepress-logo-mini.png' }],
33+
['meta', { name: 'theme-color', content: '#5f67ee' }],
34+
['meta', { name: 'og:type', content: 'website' }],
35+
['meta', { name: 'og:locale', content: 'en' }],
36+
['meta', { name: 'og:site_name', content: 'VitePress' }],
37+
['meta', { name: 'og:image', content: 'https://vitepress.dev/vitepress-og.jpg' }],
38+
['script', { src: 'https://cdn.usefathom.com/script.js', 'data-site': 'AZBRSFGG', 'data-spa': 'auto', defer: '' }]
39+
],
40+
41+
themeConfig: {
42+
logo: { src: '/vitepress-logo-mini.svg', width: 24, height: 24 },
43+
44+
socialLinks: [
45+
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
46+
],
47+
48+
search: {
49+
provider: 'algolia',
50+
options: {
51+
appId: '8J64VVRP8K',
52+
apiKey: 'a18e2f4cc5665f6602c5631fd868adfd',
53+
indexName: 'vitepress',
54+
locales: { ...zhSearch }
55+
}
56+
},
57+
58+
carbonAds: { code: 'CEBDT27Y', placement: 'vuejsorg' }
59+
}
60+
})

Diff for: src/client/app/router.ts

+4-32
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { reactive, inject, markRaw, nextTick, readonly } from 'vue'
21
import type { Component, InjectionKey } from 'vue'
2+
import { inject, markRaw, nextTick, reactive, readonly } from 'vue'
3+
import type { Awaitable, PageData, PageDataPayload } from '../shared'
34
import { notFoundPageData, treatAsHtml } from '../shared'
4-
import type { PageData, PageDataPayload, Awaitable } from '../shared'
5-
import { inBrowser, withBase } from './utils'
65
import { siteDataRef } from './data'
6+
import { getScrollOffset, inBrowser, withBase } from './utils'
77

88
export interface Route {
99
path: string
@@ -261,34 +261,14 @@ export function scrollTo(el: Element, hash: string, smooth = false) {
261261
}
262262

263263
if (target) {
264-
let scrollOffset = siteDataRef.value.scrollOffset
265-
let offset = 0
266-
let padding = 24
267-
if (typeof scrollOffset === 'object' && 'padding' in scrollOffset) {
268-
padding = scrollOffset.padding
269-
scrollOffset = scrollOffset.selector
270-
}
271-
if (typeof scrollOffset === 'number') {
272-
offset = scrollOffset
273-
} else if (typeof scrollOffset === 'string') {
274-
offset = tryOffsetSelector(scrollOffset, padding)
275-
} else if (Array.isArray(scrollOffset)) {
276-
for (const selector of scrollOffset) {
277-
const res = tryOffsetSelector(selector, padding)
278-
if (res) {
279-
offset = res
280-
break
281-
}
282-
}
283-
}
284264
const targetPadding = parseInt(
285265
window.getComputedStyle(target).paddingTop,
286266
10
287267
)
288268
const targetTop =
289269
window.scrollY +
290270
target.getBoundingClientRect().top -
291-
offset +
271+
getScrollOffset() +
292272
targetPadding
293273
function scrollToTarget() {
294274
// only smooth scroll if distance is smaller than screen height.
@@ -300,14 +280,6 @@ export function scrollTo(el: Element, hash: string, smooth = false) {
300280
}
301281
}
302282

303-
function tryOffsetSelector(selector: string, padding: number): number {
304-
const el = document.querySelector(selector)
305-
if (!el) return 0
306-
const bot = el.getBoundingClientRect().bottom
307-
if (bot < 0) return 0
308-
return bot + padding
309-
}
310-
311283
function handleHMR(route: Route): void {
312284
// update route.data on HMR updates of active page
313285
if (import.meta.hot) {

Diff for: src/client/app/utils.ts

+33
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,36 @@ export function defineClientComponent(
107107
}
108108
}
109109
}
110+
111+
export function getScrollOffset() {
112+
let scrollOffset = siteDataRef.value.scrollOffset
113+
let offset = 0
114+
let padding = 24
115+
if (typeof scrollOffset === 'object' && 'padding' in scrollOffset) {
116+
padding = scrollOffset.padding
117+
scrollOffset = scrollOffset.selector
118+
}
119+
if (typeof scrollOffset === 'number') {
120+
offset = scrollOffset
121+
} else if (typeof scrollOffset === 'string') {
122+
offset = tryOffsetSelector(scrollOffset, padding)
123+
} else if (Array.isArray(scrollOffset)) {
124+
for (const selector of scrollOffset) {
125+
const res = tryOffsetSelector(selector, padding)
126+
if (res) {
127+
offset = res
128+
break
129+
}
130+
}
131+
}
132+
133+
return offset
134+
}
135+
136+
function tryOffsetSelector(selector: string, padding: number): number {
137+
const el = document.querySelector(selector)
138+
if (!el) return 0
139+
const bot = el.getBoundingClientRect().bottom
140+
if (bot < 0) return 0
141+
return bot + padding
142+
}

Diff for: src/client/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ export {
2020
inBrowser,
2121
onContentUpdated,
2222
defineClientComponent,
23-
withBase
23+
withBase,
24+
getScrollOffset
2425
} from './app/utils'
2526

2627
// components

Diff for: src/client/theme-default/composables/outline.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import { getScrollOffset } from 'vitepress'
12
import type { DefaultTheme } from 'vitepress/theme'
23
import { onMounted, onUnmounted, onUpdated, type Ref } from 'vue'
34
import type { Header } from '../../shared'
4-
import { useAside } from './aside'
55
import { throttleAndDebounce } from '../support/utils'
6+
import { useAside } from './aside'
67

78
// cached list of anchor elements from resolveHeaders
89
const resolvedHeaders: { element: HTMLHeadElement; link: string }[] = []
@@ -179,7 +180,7 @@ export function useActiveAnchor(
179180
// find the last header above the top of viewport
180181
let activeLink: string | null = null
181182
for (const { link, top } of headers) {
182-
if (top > scrollY + offsetDocTop) {
183+
if (top > scrollY + offsetDocTop + getScrollOffset()) {
183184
break
184185
}
185186
activeLink = link

0 commit comments

Comments
 (0)