Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix next and prev links not working #130

Merged
merged 4 commits into from
Nov 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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