Skip to content

Commit

Permalink
fix: fix "next and prev link" not working when link has extention
Browse files Browse the repository at this point in the history
When there's extention in the sidebar item links, such as `.md` or
`.html`, the "next and prev link" is failing. This commit fixes
that issue.
  • Loading branch information
kiaking committed Nov 20, 2020
1 parent af8a162 commit 6dcf6b3
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 67 deletions.
123 changes: 123 additions & 0 deletions __tests__/client/theme-default/support/sideBar.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import {
getSideBarConfig,
getFlatSideBarLinks
} from 'client/theme-default/support/sideBar'

describe('client/theme-default/support/sideBar', () => {
it('gets the correct sidebar items', () => {
expect(getSideBarConfig(false, '')).toEqual(false)
expect(getSideBarConfig('auto', '')).toEqual('auto')

const sidebar = [{ text: 'Title 01', link: 'title-01' }]
const expected = [{ text: 'Title 01', link: 'title-01' }]

expect(getSideBarConfig(sidebar, '')).toEqual(expected)
})

it('gets the correct sidebar items from the given path', () => {
const sidebar = {
'/': [{ text: 'R', link: 'r' }],
'/guide/': [{ text: 'G', link: 'g' }]
}

expect(getSideBarConfig(sidebar, '/')).toEqual(sidebar['/'])
expect(getSideBarConfig(sidebar, '/guide/')).toEqual(sidebar['/guide/'])
})

it('gets the correct sidebar items with various combination', () => {
const s = {
'/guide/': [{ text: 'G', link: 'g' }],
api: [{ text: 'A', link: 'a' }]
}

expect(getSideBarConfig(s, '/guide/')).toEqual(s['/guide/'])
expect(getSideBarConfig(s, '/guide')).toEqual(s['/guide/'])
expect(getSideBarConfig(s, 'guide/')).toEqual(s['/guide/'])
expect(getSideBarConfig(s, 'guide/nested')).toEqual(s['/guide/'])
expect(getSideBarConfig(s, '/guide/nested')).toEqual(s['/guide/'])
expect(getSideBarConfig(s, 'guide/nested/')).toEqual(s['/guide/'])
expect(getSideBarConfig(s, '/api/')).toEqual(s['api'])
expect(getSideBarConfig(s, '/api')).toEqual(s['api'])
expect(getSideBarConfig(s, 'api/')).toEqual(s['api'])
expect(getSideBarConfig(s, 'api/nested')).toEqual(s['api'])
expect(getSideBarConfig(s, '/api/nested')).toEqual(s['api'])
expect(getSideBarConfig(s, 'api/nested/')).toEqual(s['api'])
expect(getSideBarConfig(s, '/')).toEqual('auto')
})

it('creates flat sidebar links', () => {
const sidebar = [
{ text: 'Title 01', link: '/title-01' },
{ text: 'Title 02', link: '/title-02' },
{ text: 'Title 03', link: '/title-03' }
]

const expected = [
{ text: 'Title 01', link: '/title-01' },
{ text: 'Title 02', link: '/title-02' },
{ text: 'Title 03', link: '/title-03' }
]

expect(getFlatSideBarLinks(sidebar)).toEqual(expected)
})

it('creates flat sidebar links with mixed sidebar group', () => {
const sidebar = [
{
text: 'Title 01',
link: '/title-01',
children: [
{ text: 'Children 01', link: '/children-01' },
{ text: 'Children 02', link: '/children-02' }
]
},
{ text: 'Title 02', link: '/title-02' },
{ text: 'Title 03', link: '/title-03' }
]

const expected = [
{ text: 'Title 01', link: '/title-01' },
{ text: 'Children 01', link: '/children-01' },
{ text: 'Children 02', link: '/children-02' },
{ text: 'Title 02', link: '/title-02' },
{ text: 'Title 03', link: '/title-03' }
]

expect(getFlatSideBarLinks(sidebar)).toEqual(expected)
})

it('ignores any items with no `link` property', () => {
const sidebar = [
{
text: 'Title 01',
children: [
{ text: 'Children 01', link: '/children-01' },
{ text: 'Children 02', link: '/children-02' }
]
},
{ text: 'Title 02', link: '/title-02' }
]

const expected = [
{ text: 'Children 01', link: '/children-01' },
{ text: 'Children 02', link: '/children-02' },
{ text: 'Title 02', link: '/title-02' }
]

expect(getFlatSideBarLinks(sidebar)).toEqual(expected)
})

it('removes `.md` or `.html` extention', () => {
const sidebar = [
{ text: 'Title 01', link: '/title-01.md' },
{ text: 'Title 02', link: '/title-02.html' }
]

const expected = [
{ text: 'Title 01', link: '/title-01' },
{ text: 'Title 02', link: '/title-02' }
]

expect(getFlatSideBarLinks(sidebar)).toEqual(expected)
})
})
19 changes: 19 additions & 0 deletions __tests__/client/theme-default/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,23 @@ describe('client/theme-default/utils', () => {
expect(Utils.ensureEndingSlash('path/page.html')).toBe('path/page.html')
})
})

describe('removeExtention', () => {
it('removes `.md` or `.html` extention from the path', () => {
expect(Utils.removeExtention('/')).toBe('/')
expect(Utils.removeExtention('index')).toBe('/')
expect(Utils.removeExtention('index.md')).toBe('/')
expect(Utils.removeExtention('index.html')).toBe('/')
expect(Utils.removeExtention('/index')).toBe('/')
expect(Utils.removeExtention('/index.md')).toBe('/')
expect(Utils.removeExtention('/index.html')).toBe('/')
expect(Utils.removeExtention('path')).toBe('path')
expect(Utils.removeExtention('path.md')).toBe('path')
expect(Utils.removeExtention('path.html')).toBe('path')
expect(Utils.removeExtention('path/')).toBe('path/')
expect(Utils.removeExtention('path/nested.md')).toBe('path/nested')
expect(Utils.removeExtention('path/nested.html')).toBe('path/nested')
expect(Utils.removeExtention('path/nested/index')).toBe('path/nested/')
})
})
})
81 changes: 14 additions & 67 deletions src/client/theme-default/composables/nextAndPrevLinks.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,41 @@
import { computed } from 'vue'
import { useSiteDataByRoute, usePageData } from 'vitepress'
import { isArray, getPathDirName, ensureStartingSlash } from '../utils'
import { DefaultTheme } from '../config'
import { isArray, ensureStartingSlash, removeExtention } from '../utils'
import { getSideBarConfig, getFlatSideBarLinks } from '../support/sideBar'

export function useNextAndPrevLinks() {
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 path = computed(() => {
return removeExtention(ensureStartingSlash(page.value.relativePath))
})

const currentPath = computed(() => {
const path = ensureStartingSlash(page.value.relativePath)
const candidates = computed(() => {
const config = getSideBarConfig(site.value.themeConfig.sidebar, path.value)

return path.replace(/(index)?\.(md|html)$/, '')
return isArray(config) ? getFlatSideBarLinks(config) : []
})

const currentIndex = computed(() => {
const index = computed(() => {
return candidates.value.findIndex((item) => {
return item.link === currentPath.value
return item.link === path.value
})
})

const next = computed(() => {
if (
site.value.themeConfig.nextLinks !== false &&
currentIndex.value > -1 &&
currentIndex.value < candidates.value.length - 1
index.value > -1 &&
index.value < candidates.value.length - 1
) {
return candidates.value[currentIndex.value + 1]
return candidates.value[index.value + 1]
}
})

const prev = computed(() => {
if (site.value.themeConfig.prevLinks !== false && currentIndex.value > 0) {
return candidates.value[currentIndex.value - 1]
if (site.value.themeConfig.prevLinks !== false && index.value > 0) {
return candidates.value[index.value - 1]
}
})

Expand All @@ -50,53 +47,3 @@ 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
}
70 changes: 70 additions & 0 deletions src/client/theme-default/support/sideBar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { DefaultTheme } from '../config'
import {
isArray,
ensureSlash,
ensureStartingSlash,
removeExtention
} from '../utils'

export function isSideBarConfig(
sidebar: DefaultTheme.SideBarConfig | DefaultTheme.MultiSideBarConfig
): sidebar is DefaultTheme.SideBarConfig {
return sidebar === false || sidebar === 'auto' || isArray(sidebar)
}

export function isSideBarGroup(
item: DefaultTheme.SideBarItem
): item is DefaultTheme.SideBarGroup {
return (item as DefaultTheme.SideBarGroup).children !== undefined
}

/**
* Get the `SideBarConfig` from sidebar option. This method will ensure to get
* correct sidebar config from `MultiSideBarConfig` with various path
* combinations such as matching `guide/` and `/guide/`. If no matching config
* was found, it will return `auto` as a fallback.
*/
export function getSideBarConfig(
sidebar: DefaultTheme.SideBarConfig | DefaultTheme.MultiSideBarConfig,
path: string
): DefaultTheme.SideBarConfig {
if (isSideBarConfig(sidebar)) {
return sidebar
}

// get the very first segment of the path to compare with nulti sidebar keys
// and make sure it's surrounded by slash
path = ensureStartingSlash(path).split('/')[1] || '/'
path = ensureSlash(path)

for (const dir in sidebar) {
// make sure the multi sidebar key is surrounded by slash too
if (path === ensureSlash(dir)) {
return sidebar[dir]
}
}

return 'auto'
}

/**
* Get flat sidebar links from the sidebar items. This method is useful for
* creating the "next and prev link" feature. It will ignore any items that
* don't have `link` property and removes `.md` or `.html` extension if a
* link contains it.
*/
export function getFlatSideBarLinks(
sidebar: DefaultTheme.SideBarItem[]
): DefaultTheme.SideBarLink[] {
return sidebar.reduce<DefaultTheme.SideBarLink[]>((links, item) => {
if (item.link) {
links.push({ text: item.text, link: removeExtention(item.link) })
}

if (isSideBarGroup(item)) {
links = [...links, ...getFlatSideBarLinks(item.children)]
}

return links
}, [])
}
12 changes: 12 additions & 0 deletions src/client/theme-default/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,22 @@ export function getPathDirName(path: string): string {
return ensureEndingSlash(segments.join('/'))
}

export function ensureSlash(path: string): string {
return ensureEndingSlash(ensureStartingSlash(path))
}

export function ensureStartingSlash(path: string): string {
return /^\//.test(path) ? path : `/${path}`
}

export function ensureEndingSlash(path: string): string {
return /(\.html|\/)$/.test(path) ? path : `${path}/`
}

/**
* Remove `.md` or `.html` extention from the given path. It also converts
* `index` to slush.
*/
export function removeExtention(path: string): string {
return path.replace(/(index)?(\.(md|html))?$/, '') || '/'
}

0 comments on commit 6dcf6b3

Please sign in to comment.