vitepress 2.0.0-alpha.12 → 2.0.0-alpha.13

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 (25) hide show
  1. package/dist/client/app/composables/copyCode.js +5 -3
  2. package/dist/client/app/router.js +39 -17
  3. package/dist/client/shared.js +4 -0
  4. package/dist/client/theme-default/components/VPBadge.vue +1 -0
  5. package/dist/client/theme-default/components/VPDocFooterLastUpdated.vue +22 -10
  6. package/dist/client/theme-default/components/VPDocOutlineItem.vue +1 -7
  7. package/dist/client/theme-default/components/VPMenuLink.vue +14 -3
  8. package/dist/client/theme-default/components/VPNavBarExtra.vue +1 -1
  9. package/dist/client/theme-default/components/VPNavBarMenuGroup.vue +1 -1
  10. package/dist/client/theme-default/components/VPNavBarMenuLink.vue +10 -3
  11. package/dist/client/theme-default/components/VPNavBarTranslations.vue +1 -1
  12. package/dist/client/theme-default/components/VPNavScreenMenuGroupLink.vue +12 -3
  13. package/dist/client/theme-default/components/VPNavScreenMenuLink.vue +12 -3
  14. package/dist/client/theme-default/components/VPNavScreenTranslations.vue +8 -1
  15. package/dist/client/theme-default/components/VPSidebar.vue +1 -1
  16. package/dist/client/theme-default/components/VPSkipLink.vue +2 -23
  17. package/dist/client/theme-default/composables/langs.js +3 -1
  18. package/dist/client/theme-default/composables/outline.js +1 -1
  19. package/dist/client/theme-default/styles/components/vp-doc.css +1 -1
  20. package/dist/node/{chunk-3d7P_gGE.js → chunk-ChGxanGi.js} +2854 -3628
  21. package/dist/node/cli.js +10 -8
  22. package/dist/node/index.d.ts +48 -10
  23. package/dist/node/index.js +11 -9
  24. package/package.json +44 -44
  25. package/types/default-theme.d.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  import { inBrowser } from 'vitepress';
2
- const shellRE = /language-(shellscript|shell|bash|sh|zsh)/;
2
+ import { isShell } from '../../shared';
3
3
  const ignoredNodes = ['.vp-copy-ignore', '.diff.remove'].join(', ');
4
4
  export function useCopyCode() {
5
5
  if (inBrowser) {
@@ -12,7 +12,6 @@ export function useCopyCode() {
12
12
  if (!parent || !sibling) {
13
13
  return;
14
14
  }
15
- const isShell = shellRE.test(parent.className);
16
15
  // Clone the node and remove the ignored nodes
17
16
  const clone = sibling.cloneNode(true);
18
17
  clone.querySelectorAll(ignoredNodes).forEach((node) => node.remove());
@@ -20,7 +19,10 @@ export function useCopyCode() {
20
19
  // doesn't affect the newlines already in the code because they are rendered as `\n<span class="line"></span>`
21
20
  clone.innerHTML = clone.innerHTML.replace(/\n+/g, '\n');
22
21
  let text = clone.textContent || '';
23
- if (isShell) {
22
+ // NOTE: Any changes to this the code here may also need to update
23
+ // `transformerDisableShellSymbolSelect` in `src/node/markdown/plugins/highlight.ts`
24
+ const lang = /language-(\w+)/.exec(parent.className)?.[1] || '';
25
+ if (isShell(lang)) {
24
26
  text = text.replace(/^ *(\$|>) /gm, '').trim();
25
27
  }
26
28
  copyToClipboard(text).then(() => {
@@ -4,7 +4,7 @@ import { siteDataRef } from './data';
4
4
  import { getScrollOffset, inBrowser, withBase } from './utils';
5
5
  export const RouterSymbol = Symbol();
6
6
  // we are just using URL to parse the pathname and hash - the base doesn't
7
- // matter and is only passed to support same-host hrefs.
7
+ // matter and is only passed to support same-host hrefs
8
8
  const fakeHost = 'http://a.com';
9
9
  const getDefaultRoute = () => ({
10
10
  path: '/',
@@ -177,21 +177,43 @@ export function scrollTo(hash, smooth = false, scrollPosition = 0) {
177
177
  catch (e) {
178
178
  console.warn(e);
179
179
  }
180
- if (target) {
181
- const targetPadding = parseInt(window.getComputedStyle(target).paddingTop, 10);
182
- const targetTop = window.scrollY +
183
- target.getBoundingClientRect().top -
184
- getScrollOffset() +
185
- targetPadding;
186
- function scrollToTarget() {
187
- // only smooth scroll if distance is smaller than screen height.
188
- if (!smooth || Math.abs(targetTop - window.scrollY) > window.innerHeight)
189
- window.scrollTo(0, targetTop);
190
- else
191
- window.scrollTo({ left: 0, top: targetTop, behavior: 'smooth' });
192
- }
193
- requestAnimationFrame(scrollToTarget);
194
- }
180
+ if (!target)
181
+ return;
182
+ const targetTop = window.scrollY +
183
+ target.getBoundingClientRect().top -
184
+ getScrollOffset() +
185
+ Number.parseInt(window.getComputedStyle(target).paddingTop, 10) || 0;
186
+ const behavior = window.matchMedia('(prefers-reduced-motion)').matches
187
+ ? 'instant'
188
+ : // only smooth scroll if distance is smaller than screen height
189
+ smooth && Math.abs(targetTop - window.scrollY) <= window.innerHeight
190
+ ? 'smooth'
191
+ : 'auto';
192
+ const scrollToTarget = () => {
193
+ window.scrollTo({ left: 0, top: targetTop, behavior });
194
+ // focus the target element for better accessibility
195
+ target.focus({ preventScroll: true });
196
+ // return if focus worked
197
+ if (document.activeElement === target)
198
+ return;
199
+ // element has tabindex already, likely not focusable
200
+ // because of some other reason, bail out
201
+ if (target.hasAttribute('tabindex'))
202
+ return;
203
+ const restoreTabindex = () => {
204
+ target.removeAttribute('tabindex');
205
+ target.removeEventListener('blur', restoreTabindex);
206
+ };
207
+ // temporarily make the target element focusable
208
+ target.setAttribute('tabindex', '-1');
209
+ target.addEventListener('blur', restoreTabindex);
210
+ // try to focus again
211
+ target.focus({ preventScroll: true });
212
+ // remove tabindex and event listener if focus still not worked
213
+ if (document.activeElement !== target)
214
+ restoreTabindex();
215
+ };
216
+ requestAnimationFrame(scrollToTarget);
195
217
  }
196
218
  function handleHMR(route) {
197
219
  // update route.data on HMR updates of active page
@@ -213,7 +235,7 @@ function shouldHotReload(payload) {
213
235
  function normalizeHref(href) {
214
236
  const url = new URL(href, fakeHost);
215
237
  url.pathname = url.pathname.replace(/(^|\/)index(\.html)?$/, '$1');
216
- // ensure correct deep link so page refresh lands on correct files.
238
+ // ensure correct deep link so page refresh lands on correct files
217
239
  if (siteDataRef.value.cleanUrls) {
218
240
  url.pathname = url.pathname.replace(/\.html$/, '');
219
241
  }
@@ -237,3 +237,7 @@ stackView.unpack = function (obj) {
237
237
  export function isObject(value) {
238
238
  return Object.prototype.toString.call(value) === '[object Object]';
239
239
  }
240
+ const shellLangs = ['shellscript', 'shell', 'bash', 'sh', 'zsh'];
241
+ export function isShell(lang) {
242
+ return shellLangs.includes(lang);
243
+ }
@@ -24,6 +24,7 @@ withDefaults(defineProps<Props>(), {
24
24
  line-height: 22px;
25
25
  font-size: 12px;
26
26
  font-weight: 500;
27
+ white-space: nowrap;
27
28
  transform: translateY(-2px);
28
29
  }
29
30
 
@@ -1,26 +1,38 @@
1
1
  <script setup lang="ts">
2
- import { ref, computed, watchEffect, onMounted } from 'vue'
2
+ import { useNavigatorLanguage } from '@vueuse/core'
3
+ import { computed, onMounted, shallowRef, useTemplateRef, watchEffect } from 'vue'
3
4
  import { useData } from '../composables/data'
4
5
 
5
- const { theme, page, lang } = useData()
6
+ const { theme, page, lang: pageLang } = useData()
7
+ const { language: browserLang } = useNavigatorLanguage()
6
8
 
7
- const date = computed(
8
- () => new Date(page.value.lastUpdated!)
9
- )
9
+ const timeRef = useTemplateRef('timeRef')
10
+
11
+ const date = computed(() => new Date(page.value.lastUpdated!))
10
12
  const isoDatetime = computed(() => date.value.toISOString())
11
- const datetime = ref('')
13
+ const datetime = shallowRef('')
12
14
 
13
15
  // set time on mounted hook to avoid hydration mismatch due to
14
16
  // potential differences in timezones of the server and clients
15
17
  onMounted(() => {
16
18
  watchEffect(() => {
19
+ const lang = theme.value.lastUpdated?.formatOptions?.forceLocale
20
+ ? pageLang.value
21
+ : browserLang.value
22
+
17
23
  datetime.value = new Intl.DateTimeFormat(
18
- theme.value.lastUpdated?.formatOptions?.forceLocale ? lang.value : undefined,
24
+ lang,
19
25
  theme.value.lastUpdated?.formatOptions ?? {
20
- dateStyle: 'short',
21
- timeStyle: 'short'
26
+ dateStyle: 'medium',
27
+ timeStyle: 'medium'
22
28
  }
23
29
  ).format(date.value)
30
+
31
+ if (lang && pageLang.value !== lang) {
32
+ timeRef.value?.setAttribute('lang', lang)
33
+ } else {
34
+ timeRef.value?.removeAttribute('lang')
35
+ }
24
36
  })
25
37
  })
26
38
  </script>
@@ -28,7 +40,7 @@ onMounted(() => {
28
40
  <template>
29
41
  <p class="VPLastUpdated">
30
42
  {{ theme.lastUpdated?.text || theme.lastUpdatedText || 'Last updated' }}:
31
- <time :datetime="isoDatetime">{{ datetime }}</time>
43
+ <time ref="timeRef" :datetime="isoDatetime">{{ datetime }}</time>
32
44
  </p>
33
45
  </template>
34
46
 
@@ -5,18 +5,12 @@ defineProps<{
5
5
  headers: DefaultTheme.OutlineItem[]
6
6
  root?: boolean
7
7
  }>()
8
-
9
- function onClick({ target: el }: Event) {
10
- const id = (el as HTMLAnchorElement).href!.split('#')[1]
11
- const heading = document.getElementById(decodeURIComponent(id))
12
- heading?.focus({ preventScroll: true })
13
- }
14
8
  </script>
15
9
 
16
10
  <template>
17
11
  <ul class="VPDocOutlineItem" :class="root ? 'root' : 'nested'">
18
12
  <li v-for="{ children, link, title } in headers">
19
- <a class="outline-link" :href="link" @click="onClick" :title>
13
+ <a class="outline-link" :href="link" :title>
20
14
  {{ title }}
21
15
  </a>
22
16
  <template v-if="children?.length">
@@ -1,27 +1,37 @@
1
1
  <script lang="ts" setup generic="T extends DefaultTheme.NavItemWithLink">
2
2
  import type { DefaultTheme } from 'vitepress/theme'
3
+ import { computed } from 'vue'
3
4
  import { useData } from '../composables/data'
4
5
  import { isActive } from '../../shared'
5
6
  import VPLink from './VPLink.vue'
6
7
 
7
- defineProps<{
8
+ const props = defineProps<{
8
9
  item: T
9
10
  }>()
10
11
 
11
12
  const { page } = useData()
13
+
14
+ const href = computed(() =>
15
+ typeof props.item.link === 'function'
16
+ ? props.item.link(page.value)
17
+ : props.item.link
18
+ )
19
+
20
+ defineOptions({ inheritAttrs: false })
12
21
  </script>
13
22
 
14
23
  <template>
15
24
  <div class="VPMenuLink">
16
25
  <VPLink
26
+ v-bind="$attrs"
17
27
  :class="{
18
28
  active: isActive(
19
29
  page.relativePath,
20
- item.activeMatch || item.link,
30
+ item.activeMatch || href,
21
31
  !!item.activeMatch
22
32
  )
23
33
  }"
24
- :href="item.link"
34
+ :href
25
35
  :target="item.target"
26
36
  :rel="item.rel"
27
37
  :no-icon="item.noIcon"
@@ -46,6 +56,7 @@ const { page } = useData()
46
56
  font-size: 14px;
47
57
  font-weight: 500;
48
58
  color: var(--vp-c-text-1);
59
+ text-align: left;
49
60
  white-space: nowrap;
50
61
  transition:
51
62
  background-color 0.25s,
@@ -31,7 +31,7 @@ const hasExtraContent = computed(
31
31
  <p class="trans-title">{{ currentLang.label }}</p>
32
32
 
33
33
  <template v-for="locale in localeLinks" :key="locale.link">
34
- <VPMenuLink :item="locale" />
34
+ <VPMenuLink :item="locale" :lang="locale.lang" :dir="locale.dir" />
35
35
  </template>
36
36
  </div>
37
37
 
@@ -17,7 +17,7 @@ const isChildActive = (navItem: DefaultTheme.NavItem) => {
17
17
  if ('link' in navItem) {
18
18
  return isActive(
19
19
  page.value.relativePath,
20
- navItem.link,
20
+ typeof navItem.link === "function" ? navItem.link(page.value) : navItem.link,
21
21
  !!props.item.activeMatch
22
22
  )
23
23
  }
@@ -1,14 +1,21 @@
1
1
  <script lang="ts" setup>
2
2
  import type { DefaultTheme } from 'vitepress/theme'
3
+ import { computed } from 'vue'
3
4
  import { useData } from '../composables/data'
4
5
  import { isActive } from '../../shared'
5
6
  import VPLink from './VPLink.vue'
6
7
 
7
- defineProps<{
8
+ const props = defineProps<{
8
9
  item: DefaultTheme.NavItemWithLink
9
10
  }>()
10
11
 
11
12
  const { page } = useData()
13
+
14
+ const href = computed(() =>
15
+ typeof props.item.link === 'function'
16
+ ? props.item.link(page.value)
17
+ : props.item.link
18
+ )
12
19
  </script>
13
20
 
14
21
  <template>
@@ -17,11 +24,11 @@ const { page } = useData()
17
24
  VPNavBarMenuLink: true,
18
25
  active: isActive(
19
26
  page.relativePath,
20
- item.activeMatch || item.link,
27
+ item.activeMatch || href,
21
28
  !!item.activeMatch
22
29
  )
23
30
  }"
24
- :href="item.link"
31
+ :href
25
32
  :target="item.target"
26
33
  :rel="item.rel"
27
34
  :no-icon="item.noIcon"
@@ -19,7 +19,7 @@ const { localeLinks, currentLang } = useLangs({ correspondingLink: true })
19
19
  <p class="title">{{ currentLang.label }}</p>
20
20
 
21
21
  <template v-for="locale in localeLinks" :key="locale.link">
22
- <VPMenuLink :item="locale" />
22
+ <VPMenuLink :item="locale" :lang="locale.lang" :dir="locale.dir" />
23
23
  </template>
24
24
  </div>
25
25
  </VPFlyout>
@@ -1,20 +1,29 @@
1
1
  <script lang="ts" setup>
2
2
  import type { DefaultTheme } from 'vitepress/theme'
3
- import { inject } from 'vue'
3
+ import { computed, inject } from 'vue'
4
+ import { useData } from '../composables/data'
4
5
  import { navInjectionKey } from '../composables/nav'
5
6
  import VPLink from './VPLink.vue'
6
7
 
7
- defineProps<{
8
+ const props = defineProps<{
8
9
  item: DefaultTheme.NavItemWithLink
9
10
  }>()
10
11
 
12
+ const { page } = useData()
13
+
14
+ const href = computed(() =>
15
+ typeof props.item.link === 'function'
16
+ ? props.item.link(page.value)
17
+ : props.item.link
18
+ )
19
+
11
20
  const { closeScreen } = inject(navInjectionKey)!
12
21
  </script>
13
22
 
14
23
  <template>
15
24
  <VPLink
16
25
  class="VPNavScreenMenuGroupLink"
17
- :href="item.link"
26
+ :href
18
27
  :target="item.target"
19
28
  :rel="item.rel"
20
29
  :no-icon="item.noIcon"
@@ -1,20 +1,29 @@
1
1
  <script lang="ts" setup>
2
2
  import type { DefaultTheme } from 'vitepress/theme'
3
- import { inject } from 'vue'
3
+ import { computed, inject } from 'vue'
4
+ import { useData } from '../composables/data'
4
5
  import { navInjectionKey } from '../composables/nav'
5
6
  import VPLink from './VPLink.vue'
6
7
 
7
- defineProps<{
8
+ const props = defineProps<{
8
9
  item: DefaultTheme.NavItemWithLink
9
10
  }>()
10
11
 
12
+ const { page } = useData()
13
+
14
+ const href = computed(() =>
15
+ typeof props.item.link === 'function'
16
+ ? props.item.link(page.value)
17
+ : props.item.link
18
+ )
19
+
11
20
  const { closeScreen } = inject(navInjectionKey)!
12
21
  </script>
13
22
 
14
23
  <template>
15
24
  <VPLink
16
25
  class="VPNavScreenMenuLink"
17
- :href="item.link"
26
+ :href
18
27
  :target="item.target"
19
28
  :rel="item.rel"
20
29
  :no-icon="item.noIcon"
@@ -25,7 +25,14 @@ function toggle() {
25
25
 
26
26
  <ul class="list">
27
27
  <li v-for="locale in localeLinks" :key="locale.link" class="item">
28
- <VPLink class="link" :href="locale.link">{{ locale.text }}</VPLink>
28
+ <VPLink
29
+ class="link"
30
+ :href="locale.link"
31
+ :lang="locale.lang"
32
+ :dir="locale.dir"
33
+ >
34
+ {{ locale.text }}
35
+ </VPLink>
29
36
  </li>
30
37
  </ul>
31
38
  </div>
@@ -119,7 +119,7 @@ watch(
119
119
  @media (min-width: 960px) {
120
120
  .curtain {
121
121
  position: sticky;
122
- top: -64px;
122
+ top: calc(var(--vp-nav-height) * -1);
123
123
  left: 0;
124
124
  z-index: 1;
125
125
  margin-top: calc(var(--vp-nav-height) * -1);
@@ -8,39 +8,18 @@ const route = useRoute()
8
8
  const backToTop = ref()
9
9
 
10
10
  watch(() => route.path, () => backToTop.value.focus())
11
-
12
- function focusOnTargetAnchor({ target }: Event) {
13
- const el = document.getElementById(
14
- decodeURIComponent((target as HTMLAnchorElement).hash).slice(1)
15
- )
16
-
17
- if (el) {
18
- const removeTabIndex = () => {
19
- el.removeAttribute('tabindex')
20
- el.removeEventListener('blur', removeTabIndex)
21
- }
22
-
23
- el.setAttribute('tabindex', '-1')
24
- el.addEventListener('blur', removeTabIndex)
25
- el.focus()
26
- window.scrollTo(0, 0)
27
- }
28
- }
29
11
  </script>
30
12
 
31
13
  <template>
32
14
  <span ref="backToTop" tabindex="-1" />
33
- <a
34
- href="#VPContent"
35
- class="VPSkipLink visually-hidden"
36
- @click="focusOnTargetAnchor"
37
- >
15
+ <a href="#VPContent" class="VPSkipLink visually-hidden">
38
16
  {{ theme.skipToContentLabel || 'Skip to content' }}
39
17
  </a>
40
18
  </template>
41
19
 
42
20
  <style scoped>
43
21
  .VPSkipLink {
22
+ position: fixed;
44
23
  top: 8px;
45
24
  left: 8px;
46
25
  padding: 8px 16px;
@@ -12,7 +12,9 @@ export function useLangs({ correspondingLink = false } = {}) {
12
12
  ? []
13
13
  : {
14
14
  text: value.label,
15
- link: normalizeLink(value.link || (key === 'root' ? '/' : `/${key}/`), theme.value.i18nRouting !== false && correspondingLink, page.value.relativePath.slice(currentLang.value.link.length - 1), !site.value.cleanUrls) + hash.value
15
+ link: normalizeLink(value.link || (key === 'root' ? '/' : `/${key}/`), theme.value.i18nRouting !== false && correspondingLink, page.value.relativePath.slice(currentLang.value.link.length - 1), !site.value.cleanUrls) + hash.value,
16
+ lang: value.lang,
17
+ dir: value.dir
16
18
  }));
17
19
  return { localeLinks, currentLang };
18
20
  }
@@ -14,7 +14,7 @@ export function resolveTitle(theme) {
14
14
  }
15
15
  export function getHeaders(range) {
16
16
  const headers = [
17
- ...document.querySelectorAll('.VPDoc :where(h1,h2,h3,h4,h5,h6)')
17
+ ...document.querySelectorAll('.VPDoc h1, .VPDoc h2, .VPDoc h3, .VPDoc h4, .VPDoc h5, .VPDoc h6')
18
18
  ]
19
19
  .filter((el) => el.id && el.hasChildNodes())
20
20
  .map((el) => {
@@ -226,7 +226,7 @@
226
226
  }
227
227
 
228
228
  .vp-doc .custom-block div[class*='language-'] {
229
- margin: 8px 0;
229
+ margin: 8px 0 !important;
230
230
  border-radius: 8px;
231
231
  }
232
232