diff --git a/docs/reference/runtime-api.md b/docs/reference/runtime-api.md index 03bb6160..b65c2661 100644 --- a/docs/reference/runtime-api.md +++ b/docs/reference/runtime-api.md @@ -38,6 +38,10 @@ interface VitePressData { isDark: Ref dir: Ref localeIndex: Ref + /** + * Current location hash + */ + hash: Ref } interface PageData { diff --git a/src/client/app/data.ts b/src/client/app/data.ts index c75f79e7..20fb1281 100644 --- a/src/client/app/data.ts +++ b/src/client/app/data.ts @@ -6,12 +6,14 @@ import { readonly, ref, shallowRef, + watch, type InjectionKey, type Ref } from 'vue' import { APPEARANCE_KEY, createTitle, + inBrowser, resolveSiteDataByRoute, type PageData, type SiteData @@ -47,6 +49,10 @@ export interface VitePressData { dir: Ref localeIndex: Ref isDark: Ref + /** + * Current location hash + */ + hash: Ref } // site data is a singleton @@ -82,6 +88,21 @@ export function initData(route: Route): VitePressData { }) : ref(false) + const hashRef = ref(inBrowser ? location.hash : '') + + if (inBrowser) { + window.addEventListener('hashchange', () => { + hashRef.value = location.hash + }) + } + + watch( + () => route.data, + () => { + hashRef.value = inBrowser ? location.hash : '' + } + ) + return { site, theme: computed(() => site.value.themeConfig), @@ -95,7 +116,8 @@ export function initData(route: Route): VitePressData { description: computed( () => route.data.description || site.value.description ), - isDark + isDark, + hash: computed(() => hashRef.value) } } diff --git a/src/client/app/router.ts b/src/client/app/router.ts index cb4058f6..9fe2314e 100644 --- a/src/client/app/router.ts +++ b/src/client/app/router.ts @@ -66,16 +66,10 @@ export function createRouter( async function go(href: string = inBrowser ? location.href : '/') { href = normalizeHref(href) if ((await router.onBeforeRouteChange?.(href)) === false) return - if (inBrowser) { - const currentUrl = new URL(location.href) - if (href !== normalizeHref(currentUrl.href)) { - // save scroll position before changing url - history.replaceState({ scrollPosition: window.scrollY }, document.title) - history.pushState(null, '', href) - if (new URL(href, fakeHost).hash !== currentUrl.hash) { - window.dispatchEvent(new Event('hashchange')) - } - } + if (inBrowser && href !== normalizeHref(location.href)) { + // save scroll position before changing url + history.replaceState({ scrollPosition: window.scrollY }, document.title) + history.pushState(null, '', href) } await loadPage(href) await router.onAfterRouteChanged?.(href) @@ -211,7 +205,12 @@ export function createRouter( if (hash !== currentUrl.hash) { history.pushState(null, '', href) // still emit the event so we can listen to it in themes - window.dispatchEvent(new Event('hashchange')) + window.dispatchEvent( + new HashChangeEvent('hashchange', { + oldURL: currentUrl.href, + newURL: href + }) + ) } if (hash) { // use smooth scroll when clicking on header anchor links diff --git a/src/client/theme-default/composables/hash.ts b/src/client/theme-default/composables/hash.ts deleted file mode 100644 index 3dedc01b..00000000 --- a/src/client/theme-default/composables/hash.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { inBrowser } from '../../shared' -import { ref } from 'vue' - -const hashRef = ref(inBrowser ? location.hash : '') - -if (inBrowser) { - window.addEventListener('hashchange', () => { - hashRef.value = location.hash - }) -} - -export { hashRef } diff --git a/src/client/theme-default/composables/langs.ts b/src/client/theme-default/composables/langs.ts index 4d105cd7..e29afe94 100644 --- a/src/client/theme-default/composables/langs.ts +++ b/src/client/theme-default/composables/langs.ts @@ -1,13 +1,12 @@ import { computed } from 'vue' import { ensureStartingSlash } from '../support/utils' import { useData } from './data' -import { hashRef } from './hash' export function useLangs({ removeCurrent = true, correspondingLink = false } = {}) { - const { site, localeIndex, page, theme } = useData() + const { site, localeIndex, page, theme, hash } = useData() const currentLang = computed(() => ({ label: site.value.locales[localeIndex.value]?.label, link: @@ -29,7 +28,7 @@ export function useLangs({ currentLang.value.link.length - 1 ), !site.value.cleanUrls - ) + hashRef.value + ) + hash.value } ) ) diff --git a/src/client/theme-default/composables/sidebar.ts b/src/client/theme-default/composables/sidebar.ts index a5a07a16..ed060e4d 100644 --- a/src/client/theme-default/composables/sidebar.ts +++ b/src/client/theme-default/composables/sidebar.ts @@ -18,7 +18,6 @@ import { getSidebarGroups } from '../support/sidebar' import { useData } from './data' -import { hashRef } from './hash' export interface SidebarControl { collapsed: Ref @@ -138,7 +137,7 @@ export function useCloseSidebarOnEscape( export function useSidebarControl( item: ComputedRef ): SidebarControl { - const { page } = useData() + const { page, hash } = useData() const collapsed = ref(false) @@ -155,7 +154,7 @@ export function useSidebarControl( isActiveLink.value = isActive(page.value.relativePath, item.value.link) } - watch([page, item, hashRef], updateIsActiveLink) + watch([page, item, hash], updateIsActiveLink) onMounted(updateIsActiveLink) const hasActiveLink = computed(() => {