mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
260 lines
7.5 KiB
TypeScript
260 lines
7.5 KiB
TypeScript
import type { App, Page } from 'vuepress'
|
|
import type {
|
|
ResolvedSidebarItem,
|
|
ThemeDocCollection,
|
|
ThemeIcon,
|
|
ThemePageData,
|
|
ThemeSidebar,
|
|
ThemeSidebarItem,
|
|
} from '../../shared/index.js'
|
|
import { deleteKey } from '@pengzhanbo/utils'
|
|
import {
|
|
ensureLeadingSlash,
|
|
entries,
|
|
isArray,
|
|
isPlainObject,
|
|
removeLeadingSlash,
|
|
} from '@vuepress/helper'
|
|
import { findCollection } from '../collections/index.js'
|
|
import { getThemeConfig } from '../loadConfig/index.js'
|
|
import { normalizeLink, perf, resolveContent, writeTemp } from '../utils/index.js'
|
|
|
|
/**
|
|
* Prepare sidebar data
|
|
*
|
|
* 准备侧边栏数据,处理所有语言环境的侧边栏配置并生成临时文件
|
|
*/
|
|
export async function prepareSidebar(app: App): Promise<void> {
|
|
perf.mark('prepare:sidebar')
|
|
const sidebar = getAllSidebar()
|
|
|
|
const { resolved, autoHome } = getSidebarData(app, sidebar)
|
|
sidebar.__auto__ = resolved
|
|
sidebar.__home__ = autoHome as any
|
|
await writeTemp(app, 'internal/sidebar.js', resolveContent(app, { name: 'sidebar', content: sidebar }))
|
|
|
|
perf.log('prepare:sidebar')
|
|
}
|
|
|
|
function getSidebarData(
|
|
app: App,
|
|
locales: Record<string, ThemeSidebar>,
|
|
): { resolved: ThemeSidebar, autoHome: Record<string, string> } {
|
|
const autoDirList: string[] = []
|
|
const resolved: ThemeSidebar = {}
|
|
|
|
entries(locales).forEach(([localePath, sidebar]) => {
|
|
if (!sidebar)
|
|
return
|
|
|
|
if (isArray(sidebar)) {
|
|
autoDirList.push(...findAutoDirList(sidebar))
|
|
}
|
|
else if (isPlainObject(sidebar)) {
|
|
entries(sidebar).forEach(([dirname, config]) => {
|
|
const prefix = normalizeLink(localePath, removeLeadingSlash(dirname))
|
|
if (config === 'auto') {
|
|
autoDirList.push(prefix)
|
|
}
|
|
else if (isArray(config)) {
|
|
autoDirList.push(...findAutoDirList(config, prefix))
|
|
}
|
|
else if (config.items === 'auto') {
|
|
autoDirList.push(normalizeLink(prefix, config.prefix))
|
|
}
|
|
else {
|
|
autoDirList.push(
|
|
...findAutoDirList(
|
|
config.items || [],
|
|
normalizeLink(prefix, config.prefix),
|
|
),
|
|
)
|
|
}
|
|
})
|
|
}
|
|
else if (sidebar === 'auto') {
|
|
autoDirList.push(localePath)
|
|
}
|
|
})
|
|
|
|
const autoHome: Record<string, string> = {}
|
|
autoDirList.forEach((localePath) => {
|
|
const { link, sidebar } = getAutoDirSidebar(app, localePath)
|
|
resolved[localePath] = sidebar
|
|
if (link) {
|
|
autoHome[localePath] = link
|
|
}
|
|
})
|
|
|
|
return { resolved, autoHome }
|
|
}
|
|
|
|
const MD_RE = /\.md$/
|
|
const NUMBER_RE = /^\d+\./
|
|
function resolveTitle(dirname: string) {
|
|
return dirname
|
|
.replace(MD_RE, '')
|
|
.replace(NUMBER_RE, '')
|
|
}
|
|
|
|
const RE_FILE_SORTING = /(?:(\d+)\.)?(?=[^/]+$)/
|
|
function fileSorting(filepath?: string): number | false {
|
|
if (!filepath)
|
|
return false
|
|
const matched = filepath.match(RE_FILE_SORTING)
|
|
const sorted = matched ? Number(matched[1]) : 0
|
|
if (Number.isNaN(sorted))
|
|
return Number.MAX_SAFE_INTEGER
|
|
return sorted
|
|
}
|
|
|
|
function getAutoDirSidebar(
|
|
app: App,
|
|
prefix: string,
|
|
): { link: string, sidebar: ThemeSidebarItem[] } {
|
|
const rootPath = removeLeadingSlash(prefix)
|
|
let pages = (app.pages as Page<ThemePageData>[])
|
|
.filter(page => page.data.filePathRelative?.startsWith(rootPath))
|
|
.map((page) => {
|
|
return { ...page, splitPath: page.data.filePathRelative?.split('/') || [] }
|
|
})
|
|
const maxIndex = Math.max(...pages.map(page => page.splitPath.length))
|
|
let nowIndex = maxIndex - 1
|
|
while (nowIndex >= 0) {
|
|
pages = pages.sort((prev, next) => {
|
|
const pi = fileSorting(prev.splitPath?.[nowIndex])
|
|
const ni = fileSorting(next.splitPath?.[nowIndex])
|
|
if (pi === false || ni === false)
|
|
return 0
|
|
if (pi === ni)
|
|
return 0
|
|
return pi < ni ? -1 : 1
|
|
})
|
|
|
|
nowIndex--
|
|
}
|
|
|
|
const RE_INDEX = ['index.md', 'README.md', 'readme.md']
|
|
|
|
const sidebar: ResolvedSidebarItem[] = []
|
|
let rootLink = ''
|
|
for (const page of pages) {
|
|
const { data, title, path, frontmatter } = page
|
|
const paths = (data.filePathRelative || '')
|
|
.slice(rootPath.replace(/^\/|\/$/g, '').length + 1)
|
|
.split('/')
|
|
const collection = findCollection(page) as ThemeDocCollection | undefined
|
|
let index = 0
|
|
let dir: string
|
|
let items = sidebar
|
|
let parent: ResolvedSidebarItem | undefined
|
|
// eslint-disable-next-line no-cond-assign
|
|
while ((dir = paths[index])) {
|
|
const text = resolveTitle(dir)
|
|
const isHome = RE_INDEX.includes(dir)
|
|
let current = items.find(item => item.text === text)
|
|
if (!current) {
|
|
current = { text, link: undefined, items: [], collapsed: collection?.sidebarCollapsed } as ResolvedSidebarItem
|
|
if (!isHome) {
|
|
items.push(current)
|
|
}
|
|
}
|
|
if (dir.endsWith('.md')) {
|
|
if (isHome) {
|
|
if (parent) {
|
|
parent.link = path
|
|
}
|
|
else {
|
|
rootLink = path
|
|
}
|
|
}
|
|
else {
|
|
current.link = path
|
|
current.text = title
|
|
}
|
|
}
|
|
if (frontmatter.icon && dir.endsWith('.md')) {
|
|
current.icon = frontmatter.icon as ThemeIcon
|
|
}
|
|
if (parent?.items?.length) {
|
|
parent.collapsed ??= false
|
|
}
|
|
parent = current
|
|
items = current.items as ResolvedSidebarItem[]
|
|
index++
|
|
}
|
|
}
|
|
return { link: rootLink, sidebar: cleanSidebar(sidebar) }
|
|
}
|
|
|
|
function cleanSidebar(sidebar: (ThemeSidebarItem)[]) {
|
|
for (const item of sidebar) {
|
|
if (isPlainObject(item)) {
|
|
if (isArray(item.items)) {
|
|
if (item.items.length === 0) {
|
|
deleteKey(item, ['items', 'collapsed'])
|
|
}
|
|
else {
|
|
cleanSidebar(item.items as ThemeSidebarItem[])
|
|
}
|
|
}
|
|
else if (!('items' in item)) {
|
|
deleteKey(item, 'collapsed')
|
|
}
|
|
}
|
|
}
|
|
return sidebar
|
|
}
|
|
|
|
function findAutoDirList(sidebar: (string | ThemeSidebarItem)[], prefix = ''): string[] {
|
|
const list: string[] = []
|
|
if (!sidebar.length)
|
|
return list
|
|
|
|
sidebar.forEach((item) => {
|
|
if (isPlainObject(item)) {
|
|
const nextPrefix = normalizeLink(prefix, item.prefix || item.dir)
|
|
if (item.items === 'auto') {
|
|
list.push(nextPrefix)
|
|
}
|
|
else if (item.items?.length) {
|
|
list.push(...findAutoDirList(item.items, nextPrefix))
|
|
}
|
|
}
|
|
})
|
|
|
|
return list
|
|
}
|
|
|
|
function getAllSidebar(): Record<string, ThemeSidebar> {
|
|
const options = getThemeConfig()
|
|
const locales: Record<string, ThemeSidebar> = {}
|
|
|
|
for (const [locale, opt] of entries(options.locales || {})) {
|
|
const rawCollections = locale === '/' ? (opt.collections || options.collections) : opt.collections
|
|
const sidebar = locale === '/' ? (opt.sidebar || options.sidebar) : opt.sidebar
|
|
locales[locale] = {}
|
|
for (const [key, value] of entries(sidebar || {})) {
|
|
locales[locale][ensureLeadingSlash(key)] = isPlainObject(value) && 'items' in value
|
|
? { ...value, prefix: value.prefix?.startsWith('/') ? value.prefix : normalizeLink(locale, removeLeadingSlash(key)) }
|
|
: {
|
|
items: value,
|
|
prefix: normalizeLink(locale, removeLeadingSlash(key)),
|
|
}
|
|
}
|
|
const collections = rawCollections?.filter(item => item.type === 'doc')
|
|
if (collections?.length) {
|
|
for (const collection of collections) {
|
|
if (collection.sidebar) {
|
|
locales[locale][normalizeLink(collection.linkPrefix || collection.dir)] = {
|
|
items: collection.sidebar,
|
|
prefix: normalizeLink(locale, removeLeadingSlash(collection.dir)),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return locales
|
|
}
|