|
|
@ -1,4 +1,9 @@
|
|
|
|
import type { HeadConfig, PageData, SiteData } from '../../types/shared'
|
|
|
|
import type {
|
|
|
|
|
|
|
|
AdditionalConfig,
|
|
|
|
|
|
|
|
HeadConfig,
|
|
|
|
|
|
|
|
PageData,
|
|
|
|
|
|
|
|
SiteData
|
|
|
|
|
|
|
|
} from '../../types/shared'
|
|
|
|
|
|
|
|
|
|
|
|
export type {
|
|
|
|
export type {
|
|
|
|
Awaitable,
|
|
|
|
Awaitable,
|
|
|
@ -13,12 +18,16 @@ export type {
|
|
|
|
SiteData,
|
|
|
|
SiteData,
|
|
|
|
SSGContext,
|
|
|
|
SSGContext,
|
|
|
|
AdditionalConfig,
|
|
|
|
AdditionalConfig,
|
|
|
|
AdditionalConfigDict
|
|
|
|
AdditionalConfigDict,
|
|
|
|
|
|
|
|
AdditionalConfigLoader
|
|
|
|
} from '../../types/shared'
|
|
|
|
} from '../../types/shared'
|
|
|
|
|
|
|
|
|
|
|
|
export const EXTERNAL_URL_RE = /^(?:[a-z]+:|\/\/)/i
|
|
|
|
export const EXTERNAL_URL_RE = /^(?:[a-z]+:|\/\/)/i
|
|
|
|
export const APPEARANCE_KEY = 'vitepress-theme-appearance'
|
|
|
|
export const APPEARANCE_KEY = 'vitepress-theme-appearance'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const VP_SOURCE_KEY = '[VP_SOURCE]'
|
|
|
|
|
|
|
|
const UnpackStackView = Symbol('stack-view:unpack')
|
|
|
|
|
|
|
|
|
|
|
|
const HASH_RE = /#.*$/
|
|
|
|
const HASH_RE = /#.*$/
|
|
|
|
const HASH_OR_QUERY_RE = /[?#].*$/
|
|
|
|
const HASH_OR_QUERY_RE = /[?#].*$/
|
|
|
|
const INDEX_OR_EXT_RE = /(?:(^|\/)index)?\.(?:md|html)$/
|
|
|
|
const INDEX_OR_EXT_RE = /(?:(^|\/)index)?\.(?:md|html)$/
|
|
|
@ -98,15 +107,18 @@ export function resolveSiteDataByRoute(
|
|
|
|
const localeIndex = getLocaleForPath(siteData, relativePath)
|
|
|
|
const localeIndex = getLocaleForPath(siteData, relativePath)
|
|
|
|
const { label, link, ...localeConfig } = siteData.locales[localeIndex] ?? {}
|
|
|
|
const { label, link, ...localeConfig } = siteData.locales[localeIndex] ?? {}
|
|
|
|
Object.assign(localeConfig, { localeIndex })
|
|
|
|
Object.assign(localeConfig, { localeIndex })
|
|
|
|
|
|
|
|
|
|
|
|
const additionalConfigs = resolveAdditionalConfig(siteData, relativePath)
|
|
|
|
const additionalConfigs = resolveAdditionalConfig(siteData, relativePath)
|
|
|
|
|
|
|
|
|
|
|
|
if (inBrowser && (import.meta as any).env?.DEV) {
|
|
|
|
if (inBrowser && (import.meta as any).env?.DEV) {
|
|
|
|
;(localeConfig as any)[VP_SOURCE_KEY] = `locale config (${localeIndex})`
|
|
|
|
;(localeConfig as any)[VP_SOURCE_KEY] = `locale config (${localeIndex})`
|
|
|
|
reportConfigLayers(relativePath, [
|
|
|
|
reportConfigLayers(relativePath, [
|
|
|
|
...additionalConfigs,
|
|
|
|
...additionalConfigs,
|
|
|
|
localeConfig as SiteData,
|
|
|
|
localeConfig,
|
|
|
|
siteData
|
|
|
|
siteData
|
|
|
|
])
|
|
|
|
])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const topLayer = {
|
|
|
|
const topLayer = {
|
|
|
|
head: mergeHead(
|
|
|
|
head: mergeHead(
|
|
|
|
siteData.head ?? [],
|
|
|
|
siteData.head ?? [],
|
|
|
@ -114,6 +126,7 @@ export function resolveSiteDataByRoute(
|
|
|
|
...additionalConfigs.map((data) => data?.head ?? []).reverse()
|
|
|
|
...additionalConfigs.map((data) => data?.head ?? []).reverse()
|
|
|
|
)
|
|
|
|
)
|
|
|
|
} as SiteData
|
|
|
|
} as SiteData
|
|
|
|
|
|
|
|
|
|
|
|
return stackView<SiteData>(
|
|
|
|
return stackView<SiteData>(
|
|
|
|
topLayer,
|
|
|
|
topLayer,
|
|
|
|
...additionalConfigs,
|
|
|
|
...additionalConfigs,
|
|
|
@ -256,33 +269,36 @@ export function escapeHtml(str: string): string {
|
|
|
|
.replace(/&(?![\w#]+;)/g, '&')
|
|
|
|
.replace(/&(?![\w#]+;)/g, '&')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function resolveAdditionalConfig(site: SiteData, path: string) {
|
|
|
|
function resolveAdditionalConfig(
|
|
|
|
if (!path.startsWith('/')) path = `/${path}`
|
|
|
|
{ additionalConfig }: SiteData,
|
|
|
|
const additionalConfig = site.additionalConfig
|
|
|
|
path: string
|
|
|
|
|
|
|
|
): AdditionalConfig[] {
|
|
|
|
if (additionalConfig === undefined) return []
|
|
|
|
if (additionalConfig === undefined) return []
|
|
|
|
else if (typeof additionalConfig === 'function')
|
|
|
|
if (typeof additionalConfig === 'function') return additionalConfig(path)
|
|
|
|
return additionalConfig(path) as SiteData[]
|
|
|
|
|
|
|
|
const configs: SiteData[] = []
|
|
|
|
const configs: AdditionalConfig[] = []
|
|
|
|
const segments = path.split('/').slice(1, -1)
|
|
|
|
const segments = path.split('/').slice(0, -1) // remove file name
|
|
|
|
|
|
|
|
|
|
|
|
while (segments.length) {
|
|
|
|
while (segments.length) {
|
|
|
|
const key = `/${segments.join('/')}/`
|
|
|
|
const key = `/${segments.join('/')}/`
|
|
|
|
if (key in additionalConfig) configs.push(additionalConfig[key] as SiteData)
|
|
|
|
configs.push(additionalConfig[key])
|
|
|
|
segments.pop()
|
|
|
|
segments.pop()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ('/' in additionalConfig) configs.push(additionalConfig['/'] as SiteData)
|
|
|
|
|
|
|
|
return configs
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const VP_SOURCE_KEY = '[VP_SOURCE]'
|
|
|
|
configs.push(additionalConfig['/'])
|
|
|
|
|
|
|
|
return configs.filter((config) => config !== undefined)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function reportConfigLayers(path: string, layers: SiteData[]) {
|
|
|
|
// This helps users to understand which configuration files are active
|
|
|
|
// This helps users to understand which configuration files are active
|
|
|
|
function reportConfigLayers(path: string, layers: Partial<SiteData>[]) {
|
|
|
|
const summaryTitle = `Config Layers for ${path}:`
|
|
|
|
const summaryTitle = `Config Layers for ${path}:`
|
|
|
|
|
|
|
|
|
|
|
|
const summary = layers.map((c, i, arr) => {
|
|
|
|
const summary = layers.map((c, i, arr) => {
|
|
|
|
const n = i + 1
|
|
|
|
const n = i + 1
|
|
|
|
if (n === arr.length) return `${n}. .vitepress/config (root)`
|
|
|
|
if (n === arr.length) return `${n}. .vitepress/config (root)`
|
|
|
|
return `${n}. ${(c as any)?.[VP_SOURCE_KEY] ?? '(Unknown Source)'}`
|
|
|
|
return `${n}. ${(c as any)?.[VP_SOURCE_KEY] ?? '(Unknown Source)'}`
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
console.debug(
|
|
|
|
console.debug(
|
|
|
|
[summaryTitle, ''.padEnd(summaryTitle.length, '='), ...summary].join('\n')
|
|
|
|
[summaryTitle, ''.padEnd(summaryTitle.length, '='), ...summary].join('\n')
|
|
|
|
)
|
|
|
|
)
|
|
|
@ -293,53 +309,45 @@ function reportConfigLayers(path: string, layers: SiteData[]) {
|
|
|
|
* Returns a readonly proxy behaving like a merged object of the input objects.
|
|
|
|
* Returns a readonly proxy behaving like a merged object of the input objects.
|
|
|
|
* Layers are merged in descending precedence, i.e. earlier layer is on top.
|
|
|
|
* Layers are merged in descending precedence, i.e. earlier layer is on top.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function stackView<T extends object>(...layers: Partial<T>[]): T {
|
|
|
|
export function stackView<T extends ObjectType>(..._layers: Partial<T>[]): T {
|
|
|
|
layers = layers.filter((layer) => layer !== undefined)
|
|
|
|
const layers = _layers.filter((layer) => isObject(layer))
|
|
|
|
if (!isObject(layers[0])) return layers[0] as T
|
|
|
|
if (layers.length <= 1) return _layers[0] as T
|
|
|
|
layers = layers.filter(isObject)
|
|
|
|
|
|
|
|
if (layers.length <= 1) return layers[0] as T
|
|
|
|
const allKeys = new Set(layers.flatMap((layer) => Reflect.ownKeys(layer)))
|
|
|
|
return new Proxy(
|
|
|
|
const allKeysArray = [...allKeys]
|
|
|
|
{},
|
|
|
|
|
|
|
|
{
|
|
|
|
return new Proxy({} as T, {
|
|
|
|
get(_, key) {
|
|
|
|
get(_, prop) {
|
|
|
|
return key === UnpackStackView
|
|
|
|
if (prop === UnpackStackView) return layers
|
|
|
|
? layers
|
|
|
|
return stackView(
|
|
|
|
: stackView(...layers.map((layer) => (layer as any)?.[key]))
|
|
|
|
...layers
|
|
|
|
},
|
|
|
|
.map((layer) => layer[prop])
|
|
|
|
set(_, key, value) {
|
|
|
|
.filter((v): v is NonNullable<T[string | symbol]> => v !== undefined)
|
|
|
|
throw new Error('StackView is read-only and cannot be mutated.')
|
|
|
|
)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
has(_, key) {
|
|
|
|
set() {
|
|
|
|
for (const layer of layers) {
|
|
|
|
throw new Error('StackView is read-only and cannot be mutated.')
|
|
|
|
if (key in layer) return true
|
|
|
|
},
|
|
|
|
}
|
|
|
|
has(_, prop) {
|
|
|
|
return false
|
|
|
|
return allKeys.has(prop)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ownKeys(_) {
|
|
|
|
ownKeys() {
|
|
|
|
const keys = new Set<string>()
|
|
|
|
return allKeysArray
|
|
|
|
for (const layer of layers) {
|
|
|
|
},
|
|
|
|
for (const key of Object.keys(layer)) {
|
|
|
|
getOwnPropertyDescriptor(_, prop) {
|
|
|
|
keys.add(key)
|
|
|
|
for (const layer of layers) {
|
|
|
|
}
|
|
|
|
const descriptor = Object.getOwnPropertyDescriptor(layer, prop)
|
|
|
|
}
|
|
|
|
if (descriptor) return descriptor
|
|
|
|
return Array.from(keys)
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
getOwnPropertyDescriptor(_, key) {
|
|
|
|
|
|
|
|
for (const layer of layers) {
|
|
|
|
|
|
|
|
if (key in layer) {
|
|
|
|
|
|
|
|
return Object.getOwnPropertyDescriptor(layer, key)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
) as T
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const UnpackStackView = Symbol('stack-view:unpack')
|
|
|
|
|
|
|
|
stackView.unpack = function <T>(obj: T): T[] | undefined {
|
|
|
|
stackView.unpack = function <T>(obj: T): T[] | undefined {
|
|
|
|
return (obj as any)?.[UnpackStackView]
|
|
|
|
return (obj as any)?.[UnpackStackView]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function isObject(value: unknown): value is Record<string, any> {
|
|
|
|
type ObjectType = Record<PropertyKey, any>
|
|
|
|
|
|
|
|
export function isObject(value: unknown): value is ObjectType {
|
|
|
|
return Object.prototype.toString.call(value) === '[object Object]'
|
|
|
|
return Object.prototype.toString.call(value) === '[object Object]'
|
|
|
|
}
|
|
|
|
}
|
|
|
|