Skip to content

Commit

Permalink
feat(theme-default): support alt + ←→ to navigate and rebuild page nav
Browse files Browse the repository at this point in the history
  • Loading branch information
Mister-Hope committed Mar 25, 2024
1 parent 4cfaace commit 424f888
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 69 deletions.
18 changes: 11 additions & 7 deletions themes/theme-default/src/client/components/AutoLink.vue
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,11 @@ const isActive = computed(() => {
:aria-label="linkAriaLabel"
v-bind="$attrs"
>
<slot name="before" />
{{ item.text }}
<slot name="after" />
<slot>
<slot name="before" />
{{ item.text }}
<slot name="after" />
</slot>
</RouteLink>
<a
v-else
Expand All @@ -103,9 +105,11 @@ const isActive = computed(() => {
:aria-label="linkAriaLabel"
v-bind="$attrs"
>
<slot name="before" />
{{ item.text }}
<AutoLinkExternalIcon v-if="isBlankTarget" />
<slot name="after" />
<slot>
<slot name="before" />
{{ item.text }}
<AutoLinkExternalIcon v-if="isBlankTarget" />
<slot name="after" />
</slot>
</a>
</template>
116 changes: 105 additions & 11 deletions themes/theme-default/src/client/components/PageNav.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import AutoLink from '@theme/AutoLink.vue'
import { useEventListener } from '@vueuse/core'
import { computed } from 'vue'
import type { Router } from 'vuepress/client'
import { usePageFrontmatter, useRoute, useRouter } from 'vuepress/client'
Expand All @@ -9,7 +10,11 @@ import type {
NavLink,
ResolvedSidebarItem,
} from '../../shared/index.js'
import { useSidebarItems, useThemeLocaleData } from '../composables/index.js'
import {
useNavigate,
useSidebarItems,
useThemeLocaleData,
} from '../composables/index.js'
import { getNavLink } from '../utils/index.js'
/**
Expand Down Expand Up @@ -69,8 +74,10 @@ const resolveFromSidebarItems = (
const frontmatter = usePageFrontmatter<DefaultThemeNormalPageFrontmatter>()
const sidebarItems = useSidebarItems()
const themeLocale = useThemeLocaleData()
const route = useRoute()
const router = useRouter()
const navigate = useNavigate()
const prevNavLink = computed(() => {
const prevConfig = resolveFromFrontmatterConfig(
Expand Down Expand Up @@ -100,22 +107,109 @@ const navbarLabel = computed(() => {
const themeLocale = useThemeLocaleData()
return themeLocale.value.pageNavbarLabel ?? 'page navigation'
})
useEventListener('keydown', (event): void => {
if (event.altKey)
if (event.key === 'ArrowRight') {
if (nextNavLink.value) {
navigate(nextNavLink.value.link)
event.preventDefault()
}
} else if (event.key === 'ArrowLeft') {
if (prevNavLink.value) {
navigate(prevNavLink.value.link)
event.preventDefault()
}
}
})
</script>

<template>
<nav
v-if="prevNavLink || nextNavLink"
class="page-nav"
class="vp-page-nav"
:aria-label="navbarLabel"
>
<p class="inner">
<span v-if="prevNavLink" class="prev">
<AutoLink :item="prevNavLink" />
</span>

<span v-if="nextNavLink" class="next">
<AutoLink :item="nextNavLink" />
</span>
</p>
<AutoLink v-if="prevNavLink" class="prev" :item="prevNavLink">
<div class="hint">
<span class="arrow left" />
{{ themeLocale.prev ?? 'Prev' }}
</div>
<div class="link">
<span>{{ prevNavLink.text }}</span>
</div>
</AutoLink>

<AutoLink v-if="nextNavLink" class="next" :item="nextNavLink">
<div class="hint">
{{ themeLocale.next ?? 'Next' }}
<span class="arrow right" />
</div>
<div class="link">
<span>{{ nextNavLink.text }}</span>
</div>
</AutoLink>
</nav>
</template>

<style lang="scss">
@import '../styles/_variables';

.vp-page-nav {
display: flex;
flex-wrap: wrap;

max-width: var(--content-width, 740px);
min-height: 2rem;
margin-inline: auto;
margin-top: 0;
padding-block: 0.5rem;
padding-inline: 2rem;
border-top: 1px solid var(--c-border);

transition: border-top var(--t-color);

@media (max-width: $MQNarrow) {
padding-inline: 1rem;
}

@media print {
display: none;
}

padding-top: 1rem;
padding-bottom: 0;

@media print {
display: none;
}

.route-link {
display: inline-block;
flex-grow: 1;

margin: 0.25rem;
padding: 0.25rem 0.5rem;
border: 1px solid var(--c-border);
border-radius: 0.25rem;

&:hover {
background: var(--c-bg-light);
}

.hint {
color: var(--c-text-quote);
font-size: 0.875rem;
line-height: 2;
}
}

.prev {
text-align: start;
}

.next {
text-align: end;
}
}
</style>
1 change: 1 addition & 0 deletions themes/theme-default/src/client/composables/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './useDarkMode.js'
export * from './useNavigate.js'
export * from './useScrollPromise.js'
export * from './useSidebarItems.js'
export * from './useThemeData.js'
Expand Down
25 changes: 25 additions & 0 deletions themes/theme-default/src/client/composables/useNavigate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { isLinkAbsolute, isLinkWithProtocol } from '@vuepress/helper/client'
import { useRoute, useRouter } from 'vuepress/client'

const FAKE_HOST = 'http://.'

export const useNavigate = (): ((url: string) => void) => {
const router = useRouter()
const route = useRoute()

return (url) => {
if (url)
if (isLinkAbsolute(url)) {
// Inner absolute path
if (route.path !== url) router.push(url)
} else if (isLinkWithProtocol(url)) {
// Outer url
if (window) window.open(url)
} else {
// Inner relative path
const loc = route.path.slice(0, route.path.lastIndexOf('/'))

router.push(new URL(`${loc}/${encodeURI(url)}`, FAKE_HOST).pathname)
}
}
}
42 changes: 18 additions & 24 deletions themes/theme-default/src/client/styles/arrow.scss
Original file line number Diff line number Diff line change
@@ -1,37 +1,31 @@
.arrow {
display: inline-block;
width: 0;
height: 0;

&.up {
border: {
left: 4px solid transparent;
right: 4px solid transparent;
bottom: 6px solid var(--c-bg-arrow);
}
vertical-align: middle;

width: 1em;
height: 1em;

background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");
background-position: center;
background-repeat: no-repeat;

line-height: normal;

transition: all 0.3s;

html.dark & {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");
}

&.down {
border: {
left: 4px solid transparent;
right: 4px solid transparent;
top: 6px solid var(--c-bg-arrow);
}
transform: rotate(180deg);
}

&.right {
border: {
top: 4px solid transparent;
bottom: 4px solid transparent;
left: 6px solid var(--c-bg-arrow);
}
transform: rotate(90deg);
}

&.left {
border: {
top: 4px solid transparent;
bottom: 4px solid transparent;
right: 6px solid var(--c-bg-arrow);
}
transform: rotate(-90deg);
}
}
27 changes: 0 additions & 27 deletions themes/theme-default/src/client/styles/page.scss
Original file line number Diff line number Diff line change
Expand Up @@ -58,30 +58,3 @@
}
}
}

.page-nav {
@include content_wrapper;
padding-top: 1rem;
padding-bottom: 0;

.inner {
min-height: 2rem;
margin-top: 0;
border-top: 1px solid var(--c-border);
transition: border-color var(--t-color);
padding-top: 1rem;
overflow: auto;
}

.prev {
a:before {
content: '';
}
}
.next {
float: right;
a:after {
content: '';
}
}
}
10 changes: 10 additions & 0 deletions themes/theme-default/src/shared/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,4 +324,14 @@ export interface DefaultThemeLocaleData extends LocaleData {
* A11y text for sidebar toggle button
*/
toggleSidebar?: string

/**
* text for prev link
*/
prev?: string

/**
* text for next link
*/
next?: string
}

0 comments on commit 424f888

Please sign in to comment.