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

@ -1,55 +1,48 @@
import { computed } from 'vue' import { computed } from 'vue'
import { useRoute, useSiteData } from 'vitepress' import { useSiteDataByRoute, usePageData } from 'vitepress'
import { isArray, getPathDirName, ensureStartingSlash } from '../utils'
import { DefaultTheme } from '../config' import { DefaultTheme } from '../config'
export function useNextAndPrevLinks() { export function useNextAndPrevLinks() {
const route = useRoute() const site = useSiteDataByRoute()
// TODO: could this be useSiteData<DefaultTheme.Config> or is the siteData const page = usePageData()
// resolved and has a different structure?
const siteData = useSiteData() const candidates = computed(() => {
const path = ensureStartingSlash(page.value.relativePath)
const resolveLink = (targetLink: string) => { const sidebar = site.value.themeConfig.sidebar
let target: DefaultTheme.SideBarLink | undefined
Object.keys(siteData.value.themeConfig.sidebar).some((k) => { return getFlatSidebarLinks(path, sidebar)
return siteData.value.themeConfig.sidebar[k].some( })
(v: { children: any }) => {
if (Array.isArray(v.children)) { const currentPath = computed(() => {
target = v.children.find((value: any) => { const path = ensureStartingSlash(page.value.relativePath)
return value.link === targetLink
}) return path.replace(/(index)?\.(md|html)$/, '')
} })
return !!target
} const currentIndex = computed(() => {
) return candidates.value.findIndex((item) => {
return item.link === currentPath.value
}) })
return target })
}
const next = computed(() => { const next = computed(() => {
const pageData = route.data if (
if (pageData.frontmatter.next === false) { site.value.themeConfig.nextLinks !== false &&
return undefined currentIndex.value > -1 &&
} currentIndex.value < candidates.value.length - 1
if (typeof pageData.frontmatter.next === 'string') { ) {
return resolveLink(pageData.frontmatter.next) return candidates.value[currentIndex.value + 1]
} }
return pageData.next
}) })
const prev = computed(() => { const prev = computed(() => {
const pageData = route.data if (site.value.themeConfig.prevLinks !== false && currentIndex.value > 0) {
if (pageData.frontmatter.prev === false) { return candidates.value[currentIndex.value - 1]
return undefined
} }
if (typeof pageData.frontmatter.prev === 'string') {
return resolveLink(pageData.frontmatter.prev)
}
return pageData.prev
}) })
const hasLinks = computed(() => { const hasLinks = computed(() => !!next.value || !!prev.value)
return !!next.value || !!prev.value
})
return { return {
next, next,
@ -57,3 +50,53 @@ export function useNextAndPrevLinks() {
hasLinks 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 return value === null || value === undefined
} }
export function isArray(value: any): value is any[] {
return Array.isArray(value)
}
export function withBase(path: string) { export function withBase(path: string) {
return (useSiteData().value.base + path).replace(/\/+/g, '/') return (useSiteData().value.base + path).replace(/\/+/g, '/')
} }
@ -62,6 +66,10 @@ export function getPathDirName(path: string): string {
return ensureEndingSlash(segments.join('/')) return ensureEndingSlash(segments.join('/'))
} }
export function ensureStartingSlash(path: string): string {
return /^\//.test(path) ? path : `/${path}`
}
export function ensureEndingSlash(path: string): string { export function ensureEndingSlash(path: string): string {
return /(\.html|\/)$/.test(path) ? path : `${path}/` return /(\.html|\/)$/.test(path) ? path : `${path}/`
} }

@ -105,18 +105,13 @@ function createVitePressPlugin({
ctx.body = vueSrc ctx.body = vueSrc
debug(ctx.url, ctx.status) debug(ctx.url, ctx.status)
const pageDataWithLinks = {
...pageData,
// TODO: this doesn't work with locales
...getNextAndPrev(siteData.themeConfig, ctx.path)
}
await next() await next()
// make sure this is the main <script> block // make sure this is the main <script> block
if (!ctx.query.type) { if (!ctx.query.type) {
// inject pageData to generated script // inject pageData to generated script
ctx.body += `\nexport const __pageData = ${JSON.stringify( ctx.body += `\nexport const __pageData = ${JSON.stringify(
JSON.stringify(pageDataWithLinks) JSON.stringify(pageData)
)}` )}`
} }
return 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 = {}) { export async function createServer(options: ServerConfig = {}) {
const config = await resolveConfig(options.root) const config = await resolveConfig(options.root)

2
types/shared.d.ts vendored

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

Loading…
Cancel
Save