import type { I18nConfig, LocalConfig } from '.' import { ensureStartSlash, getStaticDataValue, removeStartSlash } from './utils' // map groups -> subgroups -> pages export type PageGroups = { [groupKey: string]: { [subGroupKey: string]: PageMeta[] } } export type PageMeta = { pageStaticData: any // pagePath is parsed into three part: // /${groupKey}/${subGroupKey}/${pageKeyInGroup} pagePath: string groupKey: string subGroupKey: string pageKeyInGroup: string // pageTitle is extracted from frontmatter or markdown title // use pageKeyInGroup as fallback pageTitle: string pageLocale: LocalConfig | undefined pageLocaleKey: string | undefined } export function getPageGroups(staticData: any, i18n: I18nConfig | undefined) { const groups: PageGroups = {} function ensureGroup(group: string): Record function ensureGroup(group: string, subGroup: string): PageMeta[] function ensureGroup(group: string, subGroup?: string) { const subGroups = (groups[group] ||= {}) if (!subGroup) return subGroups return (subGroups[subGroup] ||= []) } Object.entries(staticData).forEach(([pagePath, pageStaticData]) => { if (pagePath === '/404') return const pageInfo = analyzePageInfo(pagePath, pageStaticData, i18n) ensureGroup(pageInfo.group, pageInfo.subGroup).push({ pageStaticData, pagePath, groupKey: pageInfo.group, subGroupKey: pageInfo.subGroup, pageKeyInGroup: pageInfo.pageKeyInGroup, pageTitle: pageInfo.pageTitle, pageLocale: pageInfo.locale, pageLocaleKey: pageInfo.localeKey, }) }) const rootGroup = groups['/']?.['/'] if (rootGroup) { const filtered = rootGroup.filter((page) => { if (page.pageKeyInGroup === '/') return true if (groups[page.pageKeyInGroup]) { // it is explicit grouped if (getStaticDataValue(page.pageStaticData, 'group')) return true // if there is also pages like /guide/start // then /guide should not be grouped with /faq // instead, /guide should be moved to the "guide" group ensureGroup(page.pageKeyInGroup, '/').push(page) return false } return true }) groups['/']['/'] = filtered } return groups } export function analyzePageInfo( pagePath: string, pageStaticData: any, i18n: I18nConfig | undefined ): { group: string subGroup: string pageKeyInGroup: string pageTitle: string locale: LocalConfig | undefined localeKey: string | undefined } { if (!pagePath.startsWith('/')) throw new Error(`expect pagePath.startsWith('/'). pagePath: ${pagePath}`) const { pagePathWithoutLocalePrefix, locale, localeKey } = matchPagePathLocalePrefix(pagePath, i18n) const seg = removeStartSlash(pagePathWithoutLocalePrefix).split('/') let group: string = getStaticDataValue(pageStaticData, 'group') let subGroup: string = getStaticDataValue(pageStaticData, 'subGroup') // used as default title const pageKeyInGroup: string = seg[seg.length - 1] || '/' if (seg.length === 1) { group ||= '/' subGroup ||= '/' } else if (seg.length === 2) { group ||= seg[0] subGroup ||= '/' } else if (seg.length >= 3) { group ||= seg[0] subGroup ||= seg[1] } else { throw new Error('getPageGroup assertion fail') } return { group, subGroup, pageKeyInGroup, pageTitle: getStaticDataValue(pageStaticData, 'title') ?? pageKeyInGroup, locale, localeKey, } } export function matchPagePathLocalePrefix( pagePath: string, i18n: I18nConfig | undefined ) { const result = { pagePathWithoutLocalePrefix: pagePath, localeKey: undefined as string | undefined, locale: undefined as LocalConfig | undefined, } if (!i18n?.locales) return result Object.entries(i18n.locales).forEach(([localeKey, locale]) => { const prefix = locale.routePrefix || ensureStartSlash(localeKey) if ( pagePath.startsWith(prefix) && // routePrefix '/' has lower priority than '/any-prefix' (!result.locale || result.locale.routePrefix === '/') ) { result.pagePathWithoutLocalePrefix = ensureStartSlash( pagePath.slice(prefix.length) ) result.localeKey = localeKey result.locale = locale } }) // fallback to defaultLocale if ( !result.locale && i18n.defaultLocale && i18n.locales[i18n.defaultLocale] ) { result.localeKey = i18n.defaultLocale result.locale = i18n.locales[i18n.defaultLocale] } return result }