Skip to content

Commit

Permalink
fix: fix next and prev links not working (#130)
Browse files Browse the repository at this point in the history
  • Loading branch information
kiaking authored Nov 20, 2020
1 parent f56e745 commit fdd498b
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 170 deletions.
1 change: 1 addition & 0 deletions src/client/theme-default/components/EditLink.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@ export default defineComponent({
margin-left: 4px;
width: 1rem;
height: 1rem;
transform: translateY(-1px);
}
</style>
61 changes: 0 additions & 61 deletions src/client/theme-default/components/NextAndPrevLinks.ts

This file was deleted.

32 changes: 19 additions & 13 deletions src/client/theme-default/components/NextAndPrevLinks.vue
Original file line number Diff line number Diff line change
@@ -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>
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down
119 changes: 81 additions & 38 deletions src/client/theme-default/composables/nextAndPrevLinks.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,102 @@
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,
prev,
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
}
8 changes: 8 additions & 0 deletions src/client/theme-default/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, '/')
}
Expand Down Expand Up @@ -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}/`
}
57 changes: 1 addition & 56 deletions src/node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand Down
2 changes: 0 additions & 2 deletions types/shared.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit fdd498b

Please sign in to comment.