vitepress 1.0.0-beta.1 → 1.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/client/app/composables/copyCode.js +2 -3
  2. package/dist/client/index.d.ts +3 -2
  3. package/dist/client/index.js +1 -1
  4. package/dist/client/theme-default/components/VPFooter.vue +0 -3
  5. package/dist/client/theme-default/components/VPLocalNav.vue +38 -4
  6. package/dist/client/theme-default/components/VPLocalNavOutlineDropdown.vue +8 -12
  7. package/dist/client/theme-default/components/VPLocalSearchBox.vue +121 -90
  8. package/dist/client/theme-default/components/VPNavBar.vue +0 -6
  9. package/dist/client/theme-default/components/VPNavBarSearch.vue +3 -1
  10. package/dist/client/theme-default/components/VPSkipLink.vue +0 -4
  11. package/dist/client/theme-default/components/VPSocialLink.vue +2 -1
  12. package/dist/client/theme-default/components/VPSocialLinks.vue +2 -1
  13. package/dist/client/theme-default/components/VPSwitchAppearance.vue +2 -2
  14. package/dist/client/theme-default/composables/outline.js +1 -1
  15. package/dist/client/theme-default/composables/prev-next.js +6 -2
  16. package/dist/client/theme-default/styles/components/vp-code-group.css +1 -1
  17. package/dist/node/cli.js +28 -12
  18. package/dist/node/index.d.ts +118 -114
  19. package/dist/node/index.js +13 -11
  20. package/dist/node/{serve-d5608de4.js → serve-ec0537e0.js} +7603 -796
  21. package/dist/node-cjs/cli.cjs +28 -12
  22. package/dist/node-cjs/index.cjs +12 -10
  23. package/dist/node-cjs/{serve-cdd2176c.cjs → serve-9a16cbd6.cjs} +7528 -727
  24. package/package.json +21 -21
  25. package/template/.vitepress/theme/index.js +1 -1
  26. package/theme-without-fonts.d.ts +1 -0
  27. package/types/default-theme.d.ts +5 -4
@@ -1,13 +1,12 @@
1
1
  import { inBrowser } from 'vitepress';
2
2
  export function useCopyCode() {
3
3
  if (inBrowser) {
4
- const timeoutIdMap = new Map();
4
+ const timeoutIdMap = new WeakMap();
5
5
  window.addEventListener('click', (e) => {
6
6
  const el = e.target;
7
7
  if (el.matches('div[class*="language-"] > button.copy')) {
8
8
  const parent = el.parentElement;
9
- const sibling = el.nextElementSibling
10
- ?.nextElementSibling;
9
+ const sibling = el.nextElementSibling?.nextElementSibling;
11
10
  if (!parent || !sibling) {
12
11
  return;
13
12
  }
@@ -1,5 +1,5 @@
1
1
  import * as vue from 'vue';
2
- import { Component, Ref, App, AsyncComponentLoader } from 'vue';
2
+ import { Component, InjectionKey, Ref, App, AsyncComponentLoader } from 'vue';
3
3
  import { PageData, Awaitable, SiteData } from '../../types/shared.js';
4
4
  export { HeadConfig, Header, PageData, SiteData } from '../../types/shared.js';
5
5
 
@@ -19,6 +19,7 @@ interface Router {
19
19
  declare function useRouter(): Router;
20
20
  declare function useRoute(): Route;
21
21
 
22
+ declare const dataSymbol: InjectionKey<VitePressData>;
22
23
  interface VitePressData<T = any> {
23
24
  /**
24
25
  * Site-level metadata
@@ -96,4 +97,4 @@ declare const Content: vue.DefineComponent<{
96
97
  as: string | Record<string, any>;
97
98
  }, {}>;
98
99
 
99
- export { Content, EnhanceAppContext, Route, Router, Theme, VitePressData, defineClientComponent, inBrowser, onContentUpdated, useData, useRoute, useRouter, withBase };
100
+ export { Content, EnhanceAppContext, Route, Router, Theme, VitePressData, dataSymbol, defineClientComponent, inBrowser, onContentUpdated, useData, useRoute, useRouter, withBase };
@@ -1,7 +1,7 @@
1
1
  // exports in this file are exposed to themes and md files via 'vitepress'
2
2
  // so the user can do `import { useRoute, useSiteData } from 'vitepress'`
3
3
  // composables
4
- export { useData } from './app/data';
4
+ export { useData, dataSymbol } from './app/data';
5
5
  export { useRoute, useRouter } from './app/router';
6
6
  // utilities
7
7
  export { inBrowser, onContentUpdated, defineClientComponent, withBase } from './app/utils';
@@ -47,7 +47,4 @@ const { hasSidebar } = useSidebar()
47
47
  font-weight: 500;
48
48
  color: var(--vp-c-text-2);
49
49
  }
50
-
51
- .message { order: 2; }
52
- .copyright { order: 1; }
53
50
  </style>
@@ -1,8 +1,12 @@
1
1
  <script lang="ts" setup>
2
+ import { useWindowScroll } from '@vueuse/core'
3
+ import { computed, shallowRef } from 'vue'
4
+ import { onContentUpdated } from 'vitepress'
2
5
  import { useData } from '../composables/data'
6
+ import { getHeaders, type MenuItem } from '../composables/outline'
3
7
  import { useSidebar } from '../composables/sidebar'
4
- import VPIconAlignLeft from './icons/VPIconAlignLeft.vue'
5
8
  import VPLocalNavOutlineDropdown from './VPLocalNavOutlineDropdown.vue'
9
+ import VPIconAlignLeft from './icons/VPIconAlignLeft.vue'
6
10
 
7
11
  defineProps<{
8
12
  open: boolean
@@ -14,10 +18,32 @@ defineEmits<{
14
18
 
15
19
  const { theme, frontmatter } = useData()
16
20
  const { hasSidebar } = useSidebar()
21
+ const { y } = useWindowScroll()
22
+
23
+ const headers = shallowRef<MenuItem[]>([])
24
+
25
+ onContentUpdated(() => {
26
+ headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline)
27
+ })
28
+
29
+ const empty = computed(() => {
30
+ return headers.value.length === 0 && !hasSidebar.value
31
+ })
32
+
33
+ const classes = computed(() => {
34
+ return {
35
+ VPLocalNav: true,
36
+ fixed: empty.value,
37
+ 'reached-top': y.value >= 64
38
+ }
39
+ })
17
40
  </script>
18
41
 
19
42
  <template>
20
- <div class="VPLocalNav" v-if="frontmatter.layout !== 'home'">
43
+ <div
44
+ v-if="frontmatter.layout !== 'home' && (!empty || y >= 64)"
45
+ :class="classes"
46
+ >
21
47
  <button
22
48
  v-if="hasSidebar"
23
49
  class="menu"
@@ -31,7 +57,7 @@ const { hasSidebar } = useSidebar()
31
57
  </span>
32
58
  </button>
33
59
 
34
- <VPLocalNavOutlineDropdown />
60
+ <VPLocalNavOutlineDropdown :headers="headers" />
35
61
  </div>
36
62
  </template>
37
63
 
@@ -45,11 +71,19 @@ const { hasSidebar } = useSidebar()
45
71
  display: flex;
46
72
  justify-content: space-between;
47
73
  align-items: center;
74
+ border-top: 1px solid var(--vp-c-gutter);
48
75
  border-bottom: 1px solid var(--vp-c-gutter);
49
76
  padding-top: var(--vp-layout-top-height, 0px);
50
77
  width: 100%;
51
78
  background-color: var(--vp-local-nav-bg-color);
52
- transition: border-color 0.5s, background-color 0.5s;
79
+ }
80
+
81
+ .VPLocalNav.fixed {
82
+ position: fixed;
83
+ }
84
+
85
+ .VPLocalNav.reached-top {
86
+ border-top-color: transparent;
53
87
  }
54
88
 
55
89
  @media (min-width: 960px) {
@@ -1,12 +1,16 @@
1
1
  <script setup lang="ts">
2
- import { ref, nextTick, shallowRef } from 'vue'
2
+ import { onContentUpdated } from 'vitepress'
3
+ import { nextTick, ref } from 'vue'
3
4
  import { useData } from '../composables/data'
4
- import { getHeaders, resolveTitle, type MenuItem } from '../composables/outline'
5
+ import { resolveTitle, type MenuItem } from '../composables/outline'
5
6
  import VPDocOutlineItem from './VPDocOutlineItem.vue'
6
- import { onContentUpdated } from 'vitepress'
7
7
  import VPIconChevronRight from './icons/VPIconChevronRight.vue'
8
8
 
9
- const { frontmatter, theme } = useData()
9
+ defineProps<{
10
+ headers: MenuItem[]
11
+ }>()
12
+
13
+ const { theme } = useData()
10
14
  const open = ref(false)
11
15
  const vh = ref(0)
12
16
  const items = ref<HTMLDivElement>()
@@ -36,14 +40,6 @@ function scrollToTop() {
36
40
  open.value = false
37
41
  window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
38
42
  }
39
-
40
- const headers = shallowRef<MenuItem[]>([])
41
-
42
- onContentUpdated(() => {
43
- headers.value = getHeaders(
44
- frontmatter.value.outline ?? theme.value.outline
45
- )
46
- })
47
43
  </script>
48
44
 
49
45
  <template>
@@ -12,7 +12,7 @@ import {
12
12
  import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
13
13
  import Mark from 'mark.js/src/vanilla.js'
14
14
  import MiniSearch, { type SearchResult } from 'minisearch'
15
- import { useRouter } from 'vitepress'
15
+ import { useRouter, dataSymbol } from 'vitepress'
16
16
  import {
17
17
  computed,
18
18
  createApp,
@@ -27,7 +27,6 @@ import {
27
27
  type Ref
28
28
  } from 'vue'
29
29
  import type { ModalTranslations } from '../../../../types/local-search'
30
- import { dataSymbol } from '../../app/data'
31
30
  import { pathToFile } from '../../app/utils'
32
31
  import { useData } from '../composables/data'
33
32
  import { createTranslate } from '../support/translation'
@@ -119,8 +118,6 @@ watchEffect(() => {
119
118
 
120
119
  const results: Ref<(SearchResult & Result)[]> = shallowRef([])
121
120
 
122
- const headingRegex = /<h(\d*).*?>.*?<a.*? href="#(.*?)".*?>.*?<\/a><\/h\1>/gi
123
-
124
121
  const enableNoResults = ref(false)
125
122
 
126
123
  watch(filterText, () => {
@@ -155,8 +152,13 @@ debouncedWatch(
155
152
  if (canceled) return
156
153
  const c = new Map<string, Map<string, string>>()
157
154
  for (const { id, mod } of mods) {
155
+ const mapId = id.slice(0, id.indexOf('#'))
156
+ let map = c.get(mapId)
157
+ if (map) continue
158
+ map = new Map()
159
+ c.set(mapId, map)
158
160
  const comp = mod.default ?? mod
159
- if (comp?.render) {
161
+ if (comp?.render || comp?.setup) {
160
162
  const app = createApp(comp)
161
163
  // Silence warnings about missing components
162
164
  app.config.warnHandler = () => {}
@@ -175,20 +177,17 @@ debouncedWatch(
175
177
  })
176
178
  const div = document.createElement('div')
177
179
  app.mount(div)
178
- const sections = div.innerHTML.split(headingRegex)
180
+ const headings = div.querySelectorAll('h1, h2, h3, h4, h5, h6')
181
+ headings.forEach((el) => {
182
+ const href = el.querySelector('a')?.getAttribute('href')
183
+ const anchor = href?.startsWith('#') && href.slice(1)
184
+ if (!anchor) return
185
+ let html = ''
186
+ while ((el = el.nextElementSibling!) && !/^h[1-6]$/i.test(el.tagName))
187
+ html += el.outerHTML
188
+ map!.set(anchor, html)
189
+ })
179
190
  app.unmount()
180
- sections.shift()
181
- const mapId = id.slice(0, id.indexOf('#'))
182
- let map = c.get(mapId)
183
- if (!map) {
184
- map = new Map()
185
- c.set(mapId, map)
186
- }
187
- for (let i = 0; i < sections.length; i += 3) {
188
- const anchor = sections[i + 1]
189
- const html = sections[i + 2]
190
- map.set(anchor, html)
191
- }
192
191
  }
193
192
  if (canceled) return
194
193
  }
@@ -241,10 +240,12 @@ async function fetchExcerpt(id: string) {
241
240
  /* Search input focus */
242
241
 
243
242
  const searchInput = ref<HTMLInputElement>()
244
-
245
- function focusSearchInput() {
243
+ const disableReset = computed(() => {
244
+ return filterText.value?.length <= 0
245
+ })
246
+ function focusSearchInput(select = true) {
246
247
  searchInput.value?.focus()
247
- searchInput.value?.select()
248
+ select && searchInput.value?.select()
248
249
  }
249
250
 
250
251
  onMounted(() => {
@@ -259,11 +260,11 @@ function onSearchBarClick(event: PointerEvent) {
259
260
 
260
261
  /* Search keyboard selection */
261
262
 
262
- const selectedIndex = ref(0)
263
+ const selectedIndex = ref(-1)
263
264
  const disableMouseOver = ref(false)
264
265
 
265
- watch(results, () => {
266
- selectedIndex.value = 0
266
+ watch(results, (r) => {
267
+ selectedIndex.value = r.length ? 0 : -1
267
268
  scrollToSelectedResult()
268
269
  })
269
270
 
@@ -360,6 +361,11 @@ onBeforeUnmount(() => {
360
361
  isLocked.value = false
361
362
  })
362
363
 
364
+ function resetSearch() {
365
+ filterText.value = ''
366
+ nextTick().then(() => focusSearchInput(false))
367
+ }
368
+
363
369
  function formMarkRegex(terms: Set<string>) {
364
370
  return new RegExp(
365
371
  [...terms]
@@ -377,34 +383,44 @@ function formMarkRegex(terms: Set<string>) {
377
383
 
378
384
  <template>
379
385
  <Teleport to="body">
380
- <div ref="el" class="VPLocalSearchBox" aria-modal="true">
386
+ <div
387
+ ref="el"
388
+ role="button"
389
+ :aria-owns="results?.length ? 'localsearch-list' : undefined"
390
+ aria-expanded="true"
391
+ aria-haspopup="listbox"
392
+ aria-labelledby="localsearch-label"
393
+ class="VPLocalSearchBox"
394
+ >
381
395
  <div class="backdrop" @click="$emit('close')" />
382
396
 
383
397
  <div class="shell">
384
- <div class="search-bar" @pointerup="onSearchBarClick($event)">
385
- <svg
386
- class="search-icon"
387
- width="18"
388
- height="18"
389
- viewBox="0 0 24 24"
390
- aria-hidden="true"
391
- >
392
- <g
393
- fill="none"
394
- stroke="currentColor"
395
- stroke-linecap="round"
396
- stroke-linejoin="round"
397
- stroke-width="2"
398
+ <form class="search-bar" @pointerup="onSearchBarClick($event)" @submit.prevent="">
399
+ <label :title="placeholder" id="localsearch-label" for="localsearch-input">
400
+ <svg
401
+ class="search-icon"
402
+ width="18"
403
+ height="18"
404
+ viewBox="0 0 24 24"
405
+ aria-hidden="true"
398
406
  >
399
- <circle cx="11" cy="11" r="8" />
400
- <path d="m21 21l-4.35-4.35" />
401
- </g>
402
- </svg>
407
+ <g
408
+ fill="none"
409
+ stroke="currentColor"
410
+ stroke-linecap="round"
411
+ stroke-linejoin="round"
412
+ stroke-width="2"
413
+ >
414
+ <circle cx="11" cy="11" r="8" />
415
+ <path d="m21 21l-4.35-4.35" />
416
+ </g>
417
+ </svg>
418
+ </label>
403
419
  <div class="search-actions before">
404
420
  <button
405
421
  class="back-button"
406
422
  :title="$t('modal.backButtonTitle')"
407
- @click="$emit('close')"
423
+ @click="selectedIndex > -1 && $emit('close')"
408
424
  >
409
425
  <svg
410
426
  width="18"
@@ -427,6 +443,8 @@ function formMarkRegex(terms: Set<string>) {
427
443
  ref="searchInput"
428
444
  v-model="filterText"
429
445
  :placeholder="placeholder"
446
+ id="localsearch-input"
447
+ aria-labelledby="localsearch-label"
430
448
  class="search-input"
431
449
  />
432
450
  <div class="search-actions">
@@ -435,7 +453,7 @@ function formMarkRegex(terms: Set<string>) {
435
453
  class="toggle-layout-button"
436
454
  :class="{ 'detailed-list': showDetailedList }"
437
455
  :title="$t('modal.displayDetails')"
438
- @click="showDetailedList = !showDetailedList"
456
+ @click="selectedIndex > -1 && (showDetailedList = !showDetailedList)"
439
457
  >
440
458
  <svg
441
459
  width="18"
@@ -456,8 +474,10 @@ function formMarkRegex(terms: Set<string>) {
456
474
 
457
475
  <button
458
476
  class="clear-button"
477
+ type="reset"
478
+ :disabled="disableReset"
459
479
  :title="$t('modal.resetButtonTitle')"
460
- @click="filterText = ''"
480
+ @click="resetSearch"
461
481
  >
462
482
  <svg
463
483
  width="18"
@@ -476,65 +496,72 @@ function formMarkRegex(terms: Set<string>) {
476
496
  </svg>
477
497
  </button>
478
498
  </div>
479
- </div>
499
+ </form>
480
500
 
481
- <div
501
+ <ul
482
502
  ref="resultsEl"
503
+ :id="results?.length ? 'localsearch-list' : undefined"
504
+ :role="results?.length ? 'listbox' : undefined"
505
+ :aria-labelledby="results?.length ? 'localsearch-label' : undefined"
483
506
  class="results"
484
507
  @mousemove="disableMouseOver = false"
485
508
  >
486
- <a
509
+ <li
487
510
  v-for="(p, index) in results"
488
511
  :key="p.id"
489
- :href="p.id"
490
- class="result"
491
- :class="{
492
- selected: selectedIndex === index
493
- }"
494
- :aria-label="[...p.titles, p.title].join(' > ')"
495
- @mouseenter="!disableMouseOver && (selectedIndex = index)"
496
- @focusin="selectedIndex = index"
497
- @click="$emit('close')"
512
+ role="option"
513
+ :aria-selected="selectedIndex === index ? 'true' : 'false'"
498
514
  >
499
- <div>
500
- <div class="titles">
501
- <span class="title-icon">#</span>
502
- <span v-for="(t, index) in p.titles" :key="index" class="title">
503
- <span class="text" v-html="t" />
504
- <svg width="18" height="18" viewBox="0 0 24 24">
505
- <path
506
- fill="none"
507
- stroke="currentColor"
508
- stroke-linecap="round"
509
- stroke-linejoin="round"
510
- stroke-width="2"
511
- d="m9 18l6-6l-6-6"
512
- />
513
- </svg>
514
- </span>
515
- <span class="title main">
516
- <span class="text" v-html="p.title" />
517
- </span>
518
- </div>
515
+ <a
516
+ :href="p.id"
517
+ class="result"
518
+ :class="{
519
+ selected: selectedIndex === index
520
+ }"
521
+ :aria-label="[...p.titles, p.title].join(' > ')"
522
+ @mouseenter="!disableMouseOver && (selectedIndex = index)"
523
+ @focusin="selectedIndex = index"
524
+ @click="$emit('close')"
525
+ >
526
+ <div>
527
+ <div class="titles">
528
+ <span class="title-icon">#</span>
529
+ <span v-for="(t, index) in p.titles" :key="index" class="title">
530
+ <span class="text" v-html="t" />
531
+ <svg width="18" height="18" viewBox="0 0 24 24">
532
+ <path
533
+ fill="none"
534
+ stroke="currentColor"
535
+ stroke-linecap="round"
536
+ stroke-linejoin="round"
537
+ stroke-width="2"
538
+ d="m9 18l6-6l-6-6"
539
+ />
540
+ </svg>
541
+ </span>
542
+ <span class="title main">
543
+ <span class="text" v-html="p.title" />
544
+ </span>
545
+ </div>
519
546
 
520
- <div v-if="showDetailedList" class="excerpt-wrapper">
521
- <div v-if="p.text" class="excerpt" inert>
522
- <div class="vp-doc" v-html="p.text" />
547
+ <div v-if="showDetailedList" class="excerpt-wrapper">
548
+ <div v-if="p.text" class="excerpt" inert>
549
+ <div class="vp-doc" v-html="p.text" />
550
+ </div>
551
+ <div class="excerpt-gradient-bottom" />
552
+ <div class="excerpt-gradient-top" />
523
553
  </div>
524
- <div class="excerpt-gradient-bottom" />
525
- <div class="excerpt-gradient-top" />
526
554
  </div>
527
- </div>
528
- </a>
529
-
530
- <div
555
+ </a>
556
+ </li>
557
+ <li
531
558
  v-if="filterText && !results.length && enableNoResults"
532
559
  class="no-results"
533
560
  >
534
561
  {{ $t('modal.noResultsText') }} "<strong>{{ filterText }}</strong
535
562
  >"
536
- </div>
537
- </div>
563
+ </li>
564
+ </ul>
538
565
 
539
566
  <div class="search-keyboard-shortcuts">
540
567
  <span>
@@ -692,11 +719,15 @@ function formMarkRegex(terms: Set<string>) {
692
719
  padding: 8px;
693
720
  }
694
721
 
695
- .search-actions button:hover,
722
+ .search-actions button:not([disabled]):hover,
696
723
  .toggle-layout-button.detailed-list {
697
724
  color: var(--vp-c-brand);
698
725
  }
699
726
 
727
+ .search-actions button.clear-button:disabled {
728
+ opacity: 0.37;
729
+ }
730
+
700
731
  .search-keyboard-shortcuts {
701
732
  font-size: 0.8rem;
702
733
  opacity: 75%;
@@ -62,15 +62,10 @@ const classes = computed(() => ({
62
62
  border-bottom: 1px solid transparent;
63
63
  padding: 0 8px 0 24px;
64
64
  height: var(--vp-nav-height);
65
- transition: border-color 0.5s, background-color 0.5s;
66
65
  pointer-events: none;
67
66
  white-space: nowrap;
68
67
  }
69
68
 
70
- .VPNavBar.has-sidebar {
71
- border-bottom-color: var(--vp-c-gutter);
72
- }
73
-
74
69
  @media (min-width: 768px) {
75
70
  .VPNavBar {
76
71
  padding: 0 32px;
@@ -79,7 +74,6 @@ const classes = computed(() => ({
79
74
 
80
75
  @media (min-width: 960px) {
81
76
  .VPNavBar.has-sidebar {
82
- border-bottom-color: transparent;
83
77
  padding: 0;
84
78
  }
85
79
 
@@ -26,6 +26,7 @@ const { theme, localeIndex } = useData()
26
26
  // payload), we delay initializing it until the user has actually clicked or
27
27
  // hit the hotkey to invoke it.
28
28
  const loaded = ref(false)
29
+ const actuallyLoaded = ref(false)
29
30
 
30
31
  const buttonText = computed(() => {
31
32
  const options = theme.value.search?.options ?? theme.value.algolia
@@ -169,9 +170,10 @@ const provider = __ALGOLIA__ ? 'algolia' : __VP_LOCAL_SEARCH__ ? 'local' : ''
169
170
  <VPAlgoliaSearchBox
170
171
  v-if="loaded"
171
172
  :algolia="theme.search?.options ?? theme.algolia"
173
+ @vue:beforeMount="actuallyLoaded = true"
172
174
  />
173
175
 
174
- <div v-else id="docsearch">
176
+ <div v-if="!actuallyLoaded" id="docsearch">
175
177
  <VPNavBarSearchButton :placeholder="buttonText" @click="load" />
176
178
  </div>
177
179
  </template>
@@ -59,10 +59,6 @@ function focusOnTargetAnchor({ target }: Event) {
59
59
  clip-path: none;
60
60
  }
61
61
 
62
- .dark .VPSkipLink {
63
- color: var(--vp-c-green);
64
- }
65
-
66
62
  @media (min-width: 1280px) {
67
63
  .VPSkipLink {
68
64
  top: 14px;
@@ -6,6 +6,7 @@ import { icons } from '../support/socialIcons'
6
6
  const props = defineProps<{
7
7
  icon: DefaultTheme.SocialLinkIcon
8
8
  link: string
9
+ ariaLabel?: string
9
10
  }>()
10
11
 
11
12
  const svg = computed(() => {
@@ -18,7 +19,7 @@ const svg = computed(() => {
18
19
  <a
19
20
  class="VPSocialLink"
20
21
  :href="link"
21
- :aria-label="typeof icon === 'string' ? icon : ''"
22
+ :aria-label="ariaLabel ?? (typeof icon === 'string' ? icon : '')"
22
23
  target="_blank"
23
24
  rel="noopener"
24
25
  v-html="svg"
@@ -10,10 +10,11 @@ defineProps<{
10
10
  <template>
11
11
  <div class="VPSocialLinks">
12
12
  <VPSocialLink
13
- v-for="{ link, icon } in links"
13
+ v-for="{ link, icon, ariaLabel } in links"
14
14
  :key="link"
15
15
  :icon="icon"
16
16
  :link="link"
17
+ :ariaLabel="ariaLabel"
17
18
  />
18
19
  </div>
19
20
  </template>
@@ -1,14 +1,14 @@
1
1
  <script lang="ts" setup>
2
2
  import { ref, onMounted, watch } from 'vue'
3
3
  import { useData } from '../composables/data'
4
- import { APPEARANCE_KEY } from '../../shared'
4
+ import { inBrowser, APPEARANCE_KEY } from '../../shared'
5
5
  import VPSwitch from './VPSwitch.vue'
6
6
  import VPIconSun from './icons/VPIconSun.vue'
7
7
  import VPIconMoon from './icons/VPIconMoon.vue'
8
8
 
9
9
  const { site, isDark } = useData()
10
10
  const checked = ref(false)
11
- const toggle = typeof localStorage !== 'undefined' ? useAppearance() : () => {}
11
+ const toggle = inBrowser ? useAppearance() : () => {}
12
12
 
13
13
  onMounted(() => {
14
14
  checked.value = document.documentElement.classList.contains('dark')
@@ -1,5 +1,5 @@
1
1
  import { onMounted, onUnmounted, onUpdated } from 'vue';
2
- import { useAside } from '../composables/aside';
2
+ import { useAside } from './aside';
3
3
  import { throttleAndDebounce } from '../support/utils';
4
4
  // magic number to avoid repeated retrieval
5
5
  const PAGE_OFFSET = 71;
@@ -10,8 +10,12 @@ export function usePrevNext() {
10
10
  const index = candidates.findIndex((link) => {
11
11
  return isActive(page.value.relativePath, link.link);
12
12
  });
13
+ const hidePrev = (theme.value.docFooter?.prev === false && !frontmatter.value.prev) ||
14
+ frontmatter.value.prev === false;
15
+ const hideNext = (theme.value.docFooter?.next === false && !frontmatter.value.next) ||
16
+ frontmatter.value.next === false;
13
17
  return {
14
- prev: frontmatter.value.prev === false
18
+ prev: hidePrev
15
19
  ? undefined
16
20
  : {
17
21
  text: (typeof frontmatter.value.prev === 'string'
@@ -23,7 +27,7 @@ export function usePrevNext() {
23
27
  ? frontmatter.value.prev.link
24
28
  : undefined) ?? candidates[index - 1]?.link
25
29
  },
26
- next: frontmatter.value.next === false
30
+ next: hideNext
27
31
  ? undefined
28
32
  : {
29
33
  text: (typeof frontmatter.value.next === 'string'
@@ -56,7 +56,7 @@
56
56
  right: 8px;
57
57
  bottom: -1px;
58
58
  left: 8px;
59
- z-index: 10;
59
+ z-index: 1;
60
60
  height: 1px;
61
61
  content: '';
62
62
  background-color: transparent;