fix: fix next and prev links not working (#130)

pull/138/head
Kia King Ishii 4 years ago committed by GitHub
parent f56e745731
commit fdd498be70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -49,5 +49,6 @@ export default defineComponent({
margin-left: 4px;
width: 1rem;
height: 1rem;
transform: translateY(-1px);
}
</style>

@ -1,61 +0,0 @@
import { computed } from 'vue'
import { useRoute, useSiteData } from 'vitepress'
import { DefaultTheme } from '../config'
export default {
setup() {
const route = useRoute()
// TODO: could this be useSiteData<DefaultTheme.Config> or is the siteData
// resolved and has a different structure?
const siteData = useSiteData()
const resolveLink = (targetLink: string) => {
let target: DefaultTheme.SideBarLink | undefined
Object.keys(siteData.value.themeConfig.sidebar).some((k) => {
return siteData.value.themeConfig.sidebar[k].some(
(v: { children: any }) => {
if (Array.isArray(v.children)) {
target = v.children.find((value: any) => {
return value.link === targetLink
})
}
return !!target
}
)
})
return target
}
const next = computed(() => {
const pageData = route.data
if (pageData.frontmatter.next === false) {
return undefined
}
if (typeof pageData.frontmatter.next === 'string') {
return resolveLink(pageData.frontmatter.next)
}
return pageData.next
})
const prev = computed(() => {
const pageData = route.data
if (pageData.frontmatter.prev === false) {
return undefined
}
if (typeof pageData.frontmatter.prev === 'string') {
return resolveLink(pageData.frontmatter.prev)
}
return pageData.prev
})
const hasLinks = computed(() => {
return !!next || !!prev
})
return {
next,
prev,
hasLinks
}
}
}

@ -1,16 +1,18 @@
<template>
<div v-if="hasLinks" class="next-and-prev-link">
<div class="prev">
<a v-if="prev" class="link" :href="prev.link">
<ArrowLeft class="icon icon-prev" />
<span class="text">{{ prev.text }}</span>
</a>
</div>
<div class="next">
<a v-if="next" class="link" :href="next.link">
<span class="text">{{ next.text }}</span>
<ArrowRight class="icon icon-next" />
</a>
<div class="container">
<div class="prev">
<a v-if="prev" class="link" :href="prev.link">
<ArrowLeft class="icon icon-prev" />
<span class="text">{{ prev.text }}</span>
</a>
</div>
<div class="next">
<a v-if="next" class="link" :href="next.link">
<span class="text">{{ next.text }}</span>
<ArrowRight class="icon icon-next" />
</a>
</div>
</div>
</div>
</template>
@ -41,6 +43,10 @@ export default defineComponent({
<style scoped>
.next-and-prev-link {
padding-top: 1rem;
}
.container {
display: flex;
justify-content: space-between;
border-top: 1px solid var(--border-color);
@ -82,8 +88,8 @@ export default defineComponent({
.icon {
display: block;
flex-shrink: 0;
width: 1rem;
height: 1rem;
width: 16px;
height: 16px;
fill: var(--text-color);
}

@ -1,55 +1,48 @@
import { computed } from 'vue'
import { useRoute, useSiteData } from 'vitepress'
import { useSiteDataByRoute, usePageData } from 'vitepress'
import { isArray, getPathDirName, ensureStartingSlash } from '../utils'
import { DefaultTheme } from '../config'
export function useNextAndPrevLinks() {
const route = useRoute()
// TODO: could this be useSiteData<DefaultTheme.Config> or is the siteData
// resolved and has a different structure?
const siteData = useSiteData()
const resolveLink = (targetLink: string) => {
let target: DefaultTheme.SideBarLink | undefined
Object.keys(siteData.value.themeConfig.sidebar).some((k) => {
return siteData.value.themeConfig.sidebar[k].some(
(v: { children: any }) => {
if (Array.isArray(v.children)) {
target = v.children.find((value: any) => {
return value.link === targetLink
})
}
return !!target
}
)
const site = useSiteDataByRoute()
const page = usePageData()
const candidates = computed(() => {
const path = ensureStartingSlash(page.value.relativePath)
const sidebar = site.value.themeConfig.sidebar
return getFlatSidebarLinks(path, sidebar)
})
const currentPath = computed(() => {
const path = ensureStartingSlash(page.value.relativePath)
return path.replace(/(index)?\.(md|html)$/, '')
})
const currentIndex = computed(() => {
return candidates.value.findIndex((item) => {
return item.link === currentPath.value
})
return target
}
})
const next = computed(() => {
const pageData = route.data
if (pageData.frontmatter.next === false) {
return undefined
}
if (typeof pageData.frontmatter.next === 'string') {
return resolveLink(pageData.frontmatter.next)
if (
site.value.themeConfig.nextLinks !== false &&
currentIndex.value > -1 &&
currentIndex.value < candidates.value.length - 1
) {
return candidates.value[currentIndex.value + 1]
}
return pageData.next
})
const prev = computed(() => {
const pageData = route.data
if (pageData.frontmatter.prev === false) {
return undefined
if (site.value.themeConfig.prevLinks !== false && currentIndex.value > 0) {
return candidates.value[currentIndex.value - 1]
}
if (typeof pageData.frontmatter.prev === 'string') {
return resolveLink(pageData.frontmatter.prev)
}
return pageData.prev
})
const hasLinks = computed(() => {
return !!next.value || !!prev.value
})
const hasLinks = computed(() => !!next.value || !!prev.value)
return {
next,
@ -57,3 +50,53 @@ export function useNextAndPrevLinks() {
hasLinks
}
}
function getFlatSidebarLinks(
path: string,
sidebar?: DefaultTheme.SideBarConfig
): DefaultTheme.SideBarLink[] {
if (!sidebar || sidebar === 'auto') {
return []
}
return isArray(sidebar)
? getFlatSidebarLinksFromArray(path, sidebar)
: getFlatSidebarLinksFromObject(path, sidebar)
}
function getFlatSidebarLinksFromArray(
path: string,
sidebar: DefaultTheme.SideBarItem[]
): DefaultTheme.SideBarLink[] {
return sidebar.reduce<DefaultTheme.SideBarLink[]>((links, item) => {
if (item.link) {
links.push({ text: item.text, link: item.link })
}
if (isSideBarGroup(item)) {
links = [...links, ...getFlatSidebarLinks(path, item.children)]
}
return links
}, [])
}
function getFlatSidebarLinksFromObject(
path: string,
sidebar: DefaultTheme.MultiSideBarConfig
): DefaultTheme.SideBarLink[] {
const paths = [path, Object.keys(sidebar)[0]]
const item = paths.map((p) => sidebar[getPathDirName(p)]).find(Boolean)
if (isArray(item)) {
return getFlatSidebarLinksFromArray(path, item)
}
return []
}
function isSideBarGroup(
item: DefaultTheme.SideBarItem
): item is DefaultTheme.SideBarGroup {
return (item as DefaultTheme.SideBarGroup).children !== undefined
}

@ -9,6 +9,10 @@ export function isNullish(value: any): value is null | undefined {
return value === null || value === undefined
}
export function isArray(value: any): value is any[] {
return Array.isArray(value)
}
export function withBase(path: string) {
return (useSiteData().value.base + path).replace(/\/+/g, '/')
}
@ -62,6 +66,10 @@ export function getPathDirName(path: string): string {
return ensureEndingSlash(segments.join('/'))
}
export function ensureStartingSlash(path: string): string {
return /^\//.test(path) ? path : `/${path}`
}
export function ensureEndingSlash(path: string): string {
return /(\.html|\/)$/.test(path) ? path : `${path}/`
}

@ -105,18 +105,13 @@ function createVitePressPlugin({
ctx.body = vueSrc
debug(ctx.url, ctx.status)
const pageDataWithLinks = {
...pageData,
// TODO: this doesn't work with locales
...getNextAndPrev(siteData.themeConfig, ctx.path)
}
await next()
// make sure this is the main <script> block
if (!ctx.query.type) {
// inject pageData to generated script
ctx.body += `\nexport const __pageData = ${JSON.stringify(
JSON.stringify(pageDataWithLinks)
JSON.stringify(pageData)
)}`
}
return
@ -133,56 +128,6 @@ function createVitePressPlugin({
}
}
// TODO: share types from SideBarLink, SideBarGroup, etc. We are also assuming
// all themes follow this structure, in which case, we should expose the type
// instead of having any for themeConfig or not nest `sidebar` inside
// `themeConfig`, specially given it must be specified inside `locales` if there
// are any
interface SideBarLink {
text: string
link: string
}
function getNextAndPrev(themeConfig: any, pagePath: string) {
if (!themeConfig.sidebar) {
return
}
const sidebar = themeConfig.sidebar
let candidates: SideBarLink[] = []
Object.keys(sidebar).forEach((k) => {
if (!pagePath.startsWith(k)) {
return
}
sidebar[k].forEach((sidebarItem: { children?: SideBarLink[] }) => {
if (!sidebarItem.children) {
return
}
sidebarItem.children.forEach((candidate) => {
candidates.push(candidate)
})
})
})
const path = pagePath.replace(/\.(md|html)$/, '')
const currentLinkIndex = candidates.findIndex((v) => v.link === path)
const nextAndPrev: { prev?: SideBarLink; next?: SideBarLink } = {}
if (
themeConfig.nextLinks !== false &&
currentLinkIndex > -1 &&
currentLinkIndex < candidates.length - 1
) {
nextAndPrev.next = candidates[currentLinkIndex + 1]
}
if (themeConfig.prevLinks !== false && currentLinkIndex > 0) {
nextAndPrev.next = candidates[currentLinkIndex - 1]
}
return nextAndPrev
}
export async function createServer(options: ServerConfig = {}) {
const config = await resolveConfig(options.root)

2
types/shared.d.ts vendored

@ -29,8 +29,6 @@ export interface PageData {
headers: Header[]
relativePath: string
lastUpdated: number
next?: { text: string; link: string }
prev?: { text: string; link: string }
}
export interface Header {

Loading…
Cancel
Save