methanol 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/package.json +10 -1
- package/src/config.js +63 -18
- package/src/dev-server.js +80 -7
- package/src/main.js +51 -1
- package/src/mdx.js +176 -24
- package/src/pagefind.js +10 -2
- package/src/pages.js +149 -10
- package/src/register-loader.js +2 -7
- package/src/rehype-plugins/link-resolve.js +35 -8
- package/src/state.js +27 -3
- package/src/vite-plugins.js +2 -2
- package/themes/default/components/ThemeAccentSwitch.client.jsx +95 -0
- package/themes/default/components/ThemeAccentSwitch.static.jsx +23 -0
- package/themes/default/components/ThemeColorSwitch.client.jsx +1 -1
- package/themes/default/components/ThemeSearchBox.client.jsx +71 -34
- package/themes/default/components/ThemeSearchBox.static.jsx +0 -1
- package/themes/default/components/pre.client.jsx +1 -1
- package/themes/default/components/{pre.jsx → pre.static.jsx} +1 -1
- package/themes/default/index.js +4 -13
- package/themes/default/page.jsx +61 -7
- package/themes/default/pages/index.mdx +24 -2
- package/themes/default/public/favicon.png +0 -0
- package/themes/default/sources/prefetch.js +49 -0
- package/themes/default/{resources → sources}/style.css +600 -29
- package/.editorconfig +0 -19
- package/.prettierrc +0 -10
package/src/mdx.js
CHANGED
|
@@ -24,12 +24,13 @@ import * as JSXDevFactory from 'refui/jsx-dev-runtime'
|
|
|
24
24
|
import rehypeSlug from 'rehype-slug'
|
|
25
25
|
import extractToc from '@stefanprobst/rehype-extract-toc'
|
|
26
26
|
import withTocExport from '@stefanprobst/rehype-extract-toc/mdx'
|
|
27
|
+
import rehypeStarryNight from 'rehype-starry-night'
|
|
27
28
|
import { HTMLRenderer } from './renderer.js'
|
|
28
29
|
import { signal, computed, read, Suspense, nextTick } from 'refui'
|
|
29
30
|
import { createPortal } from 'refui/extras'
|
|
30
31
|
import { pathToFileURL } from 'url'
|
|
31
32
|
import { existsSync } from 'fs'
|
|
32
|
-
import { resolve } from 'path'
|
|
33
|
+
import { resolve, dirname, basename, relative } from 'path'
|
|
33
34
|
import { state } from './state.js'
|
|
34
35
|
import { resolveUserMdxConfig } from './config.js'
|
|
35
36
|
import { methanolCtx } from './rehype-plugins/methanol-ctx.js'
|
|
@@ -71,21 +72,113 @@ const resolveUserHeadAssets = () => {
|
|
|
71
72
|
return assets
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
const resolvePageAssetUrl = (page, filePath) => {
|
|
76
|
+
const root = page?.source === 'theme' && state.THEME_PAGES_DIR
|
|
77
|
+
? state.THEME_PAGES_DIR
|
|
78
|
+
: state.PAGES_DIR
|
|
79
|
+
if (!root) return null
|
|
80
|
+
const relPath = relative(root, filePath).replace(/\\/g, '/')
|
|
81
|
+
if (!relPath || relPath.startsWith('..')) return null
|
|
82
|
+
return `/${relPath}`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const resolvePageHeadAssets = (page) => {
|
|
86
|
+
if (!page?.filePath) return []
|
|
87
|
+
const baseDir = dirname(page.filePath)
|
|
88
|
+
const baseName = basename(page.filePath).replace(/\.(mdx|md)$/, '')
|
|
89
|
+
const pagesRoot = state.PAGES_DIR ? resolve(state.PAGES_DIR) : null
|
|
90
|
+
const isRootIndex =
|
|
91
|
+
pagesRoot && baseName === 'index' && resolve(baseDir) === pagesRoot && page.source !== 'theme'
|
|
92
|
+
const isRootStylePage =
|
|
93
|
+
pagesRoot && baseName === 'style' && resolve(baseDir) === pagesRoot && page.source !== 'theme'
|
|
94
|
+
const assets = []
|
|
95
|
+
const cssPath = resolve(baseDir, `${baseName}.css`)
|
|
96
|
+
if (existsSync(cssPath)) {
|
|
97
|
+
if (isRootStylePage) {
|
|
98
|
+
const rootStyle = resolve(pagesRoot, 'style.css')
|
|
99
|
+
if (cssPath === rootStyle) {
|
|
100
|
+
return assets
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const href = resolvePageAssetUrl(page, cssPath)
|
|
104
|
+
if (href) {
|
|
105
|
+
assets.push(HTMLRenderer.c('link', { rel: 'stylesheet', href }))
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const scriptExtensions = ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts']
|
|
109
|
+
let scriptPath = null
|
|
110
|
+
for (const ext of scriptExtensions) {
|
|
111
|
+
const candidate = resolve(baseDir, `${baseName}${ext}`)
|
|
112
|
+
if (existsSync(candidate)) {
|
|
113
|
+
scriptPath = candidate
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (scriptPath) {
|
|
118
|
+
if (isRootIndex) {
|
|
119
|
+
const rootIndexJs = resolve(pagesRoot, 'index.js')
|
|
120
|
+
const rootIndexTs = resolve(pagesRoot, 'index.ts')
|
|
121
|
+
if (scriptPath === rootIndexJs || scriptPath === rootIndexTs) {
|
|
122
|
+
return assets
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const src = resolvePageAssetUrl(page, scriptPath)
|
|
126
|
+
if (src) {
|
|
127
|
+
assets.push(HTMLRenderer.c('script', { type: 'module', src }))
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return assets
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const buildPageContext = ({
|
|
134
|
+
routePath,
|
|
135
|
+
filePath,
|
|
136
|
+
pageMeta,
|
|
137
|
+
pagesContext,
|
|
138
|
+
lazyPagesTree = false
|
|
139
|
+
}) => {
|
|
75
140
|
const page = pageMeta
|
|
76
|
-
const pagesTree = pagesContext?.getPagesTree ? pagesContext.getPagesTree(routePath) : pagesContext?.pagesTree || []
|
|
77
141
|
const language = pagesContext?.getLanguageForRoute ? pagesContext.getLanguageForRoute(routePath) : null
|
|
78
|
-
|
|
142
|
+
const getSiblings = pagesContext?.getSiblings
|
|
143
|
+
? () => pagesContext.getSiblings(routePath, page?.filePath || filePath)
|
|
144
|
+
: null
|
|
145
|
+
if (page && getSiblings && page.getSiblings !== getSiblings) {
|
|
146
|
+
page.getSiblings = getSiblings
|
|
147
|
+
}
|
|
148
|
+
const ctx = {
|
|
79
149
|
routePath,
|
|
80
150
|
filePath,
|
|
81
151
|
page,
|
|
82
152
|
pages: pagesContext?.pages || [],
|
|
83
|
-
pagesTree,
|
|
84
153
|
pagesByRoute: pagesContext?.pagesByRoute || new Map(),
|
|
85
154
|
languages: pagesContext?.languages || [],
|
|
86
155
|
language,
|
|
87
|
-
site: pagesContext?.site || null
|
|
156
|
+
site: pagesContext?.site || null,
|
|
157
|
+
getSiblings
|
|
158
|
+
}
|
|
159
|
+
const resolvePagesTree = () =>
|
|
160
|
+
pagesContext?.getPagesTree ? pagesContext.getPagesTree(routePath) : pagesContext?.pagesTree || []
|
|
161
|
+
if (lazyPagesTree) {
|
|
162
|
+
let cachedTree = null
|
|
163
|
+
let hasTree = false
|
|
164
|
+
Object.defineProperty(ctx, 'pagesTree', {
|
|
165
|
+
enumerable: true,
|
|
166
|
+
get() {
|
|
167
|
+
if (!hasTree) {
|
|
168
|
+
cachedTree = resolvePagesTree()
|
|
169
|
+
hasTree = true
|
|
170
|
+
}
|
|
171
|
+
return cachedTree
|
|
172
|
+
},
|
|
173
|
+
set(value) {
|
|
174
|
+
cachedTree = value
|
|
175
|
+
hasTree = true
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
} else {
|
|
179
|
+
ctx.pagesTree = resolvePagesTree()
|
|
88
180
|
}
|
|
181
|
+
return ctx
|
|
89
182
|
}
|
|
90
183
|
|
|
91
184
|
const findTitleFromToc = (toc = []) => {
|
|
@@ -121,7 +214,39 @@ const findTitleFromToc = (toc = []) => {
|
|
|
121
214
|
|
|
122
215
|
let cachedMdxConfig = null
|
|
123
216
|
|
|
124
|
-
const
|
|
217
|
+
const normalizeStarryNightConfig = (value) => {
|
|
218
|
+
if (value == null) return null
|
|
219
|
+
if (typeof value === 'boolean') {
|
|
220
|
+
return { enabled: value, options: null }
|
|
221
|
+
}
|
|
222
|
+
if (typeof value !== 'object') return null
|
|
223
|
+
const { enabled, options, ...rest } = value
|
|
224
|
+
if (enabled === false) return { enabled: false, options: null }
|
|
225
|
+
if (options && typeof options === 'object') {
|
|
226
|
+
return { enabled: true, options: { ...options } }
|
|
227
|
+
}
|
|
228
|
+
if (Object.keys(rest).length) {
|
|
229
|
+
return { enabled: true, options: { ...rest } }
|
|
230
|
+
}
|
|
231
|
+
return { enabled: true, options: null }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const resolveStarryNightForPage = (frontmatter) => {
|
|
235
|
+
const base = {
|
|
236
|
+
enabled: state.STARRY_NIGHT_ENABLED === true,
|
|
237
|
+
options: state.STARRY_NIGHT_OPTIONS || null
|
|
238
|
+
}
|
|
239
|
+
if (!frontmatter || !Object.prototype.hasOwnProperty.call(frontmatter, 'starryNight')) {
|
|
240
|
+
return base
|
|
241
|
+
}
|
|
242
|
+
const override = normalizeStarryNightConfig(frontmatter.starryNight)
|
|
243
|
+
if (!override) return base
|
|
244
|
+
if (override.enabled === false) return { enabled: false, options: null }
|
|
245
|
+
const options = override.options != null ? override.options : base.options
|
|
246
|
+
return { enabled: true, options }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const resolveBaseMdxConfig = async () => {
|
|
125
250
|
const userMdxConfig = await resolveUserMdxConfig()
|
|
126
251
|
if (cachedMdxConfig) {
|
|
127
252
|
return cachedMdxConfig
|
|
@@ -135,16 +260,33 @@ const resolveMdxConfig = async () => {
|
|
|
135
260
|
rehypePlugins: [rehypeSlug, extractToc, [withTocExport, { name: 'toc' }]]
|
|
136
261
|
}
|
|
137
262
|
const mdxConfig = { ...baseMdxConfig, ...userMdxConfig }
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
263
|
+
const userRehypePlugins = Array.isArray(userMdxConfig.rehypePlugins) ? userMdxConfig.rehypePlugins : []
|
|
264
|
+
mdxConfig.rehypePlugins = [...baseMdxConfig.rehypePlugins, ...userRehypePlugins]
|
|
141
265
|
mdxConfig.rehypePlugins.push(linkResolve)
|
|
142
266
|
mdxConfig.rehypePlugins.push(methanolCtx)
|
|
143
267
|
return (cachedMdxConfig = mdxConfig)
|
|
144
268
|
}
|
|
145
269
|
|
|
270
|
+
const resolveMdxConfigForPage = async (frontmatter) => {
|
|
271
|
+
const baseConfig = await resolveBaseMdxConfig()
|
|
272
|
+
const mdxConfig = {
|
|
273
|
+
...baseConfig,
|
|
274
|
+
rehypePlugins: [...baseConfig.rehypePlugins]
|
|
275
|
+
}
|
|
276
|
+
const starryNightConfig = resolveStarryNightForPage(frontmatter)
|
|
277
|
+
if (!starryNightConfig.enabled) return mdxConfig
|
|
278
|
+
const plugin = starryNightConfig.options ? [rehypeStarryNight, starryNightConfig.options] : [rehypeStarryNight]
|
|
279
|
+
const insertIndex = mdxConfig.rehypePlugins.indexOf(linkResolve)
|
|
280
|
+
if (insertIndex >= 0) {
|
|
281
|
+
mdxConfig.rehypePlugins.splice(insertIndex, 0, plugin)
|
|
282
|
+
} else {
|
|
283
|
+
mdxConfig.rehypePlugins.push(plugin)
|
|
284
|
+
}
|
|
285
|
+
return mdxConfig
|
|
286
|
+
}
|
|
287
|
+
|
|
146
288
|
export const compileMdx = async ({ content, filePath, ctx }) => {
|
|
147
|
-
const mdxConfig = await
|
|
289
|
+
const mdxConfig = await resolveMdxConfigForPage(ctx?.page?.frontmatter)
|
|
148
290
|
const runtimeFactory = mdxConfig.development ? JSXDevFactory : JSXFactory
|
|
149
291
|
const compiled = await compile({ value: content, path: filePath }, mdxConfig)
|
|
150
292
|
|
|
@@ -156,17 +298,22 @@ export const compileMdx = async ({ content, filePath, ctx }) => {
|
|
|
156
298
|
})
|
|
157
299
|
}
|
|
158
300
|
|
|
159
|
-
export const compilePageMdx = async (page, pagesContext) => {
|
|
301
|
+
export const compilePageMdx = async (page, pagesContext, options = {}) => {
|
|
160
302
|
if (!page || page.content == null || page.mdxComponent) return
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
303
|
+
const { ctx = null, lazyPagesTree = false, refreshPagesTree = true } = options || {}
|
|
304
|
+
const activeCtx =
|
|
305
|
+
ctx ||
|
|
306
|
+
buildPageContext({
|
|
165
307
|
routePath: page.routePath,
|
|
166
308
|
filePath: page.filePath,
|
|
167
309
|
pageMeta: page,
|
|
168
|
-
pagesContext
|
|
310
|
+
pagesContext,
|
|
311
|
+
lazyPagesTree
|
|
169
312
|
})
|
|
313
|
+
const mdxModule = await compileMdx({
|
|
314
|
+
content: page.content,
|
|
315
|
+
filePath: page.filePath,
|
|
316
|
+
ctx: activeCtx
|
|
170
317
|
})
|
|
171
318
|
page.mdxComponent = mdxModule.default
|
|
172
319
|
page.toc = mdxModule.toc
|
|
@@ -181,11 +328,10 @@ export const compilePageMdx = async (page, pagesContext) => {
|
|
|
181
328
|
}
|
|
182
329
|
}
|
|
183
330
|
if (typeof pagesContext?.setDerivedTitle === 'function') {
|
|
184
|
-
pagesContext.setDerivedTitle(
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
)
|
|
331
|
+
pagesContext.setDerivedTitle(page.filePath, shouldUseTocTitle ? page.title : null, page.toc)
|
|
332
|
+
}
|
|
333
|
+
if (ctx && refreshPagesTree && pagesContext?.getPagesTree) {
|
|
334
|
+
ctx.pagesTree = pagesContext.getPagesTree(activeCtx.routePath)
|
|
189
335
|
}
|
|
190
336
|
}
|
|
191
337
|
|
|
@@ -208,13 +354,19 @@ export const renderHtml = async ({
|
|
|
208
354
|
pageMeta,
|
|
209
355
|
pagesContext
|
|
210
356
|
})
|
|
357
|
+
await compilePageMdx(pageMeta, pagesContext, { ctx })
|
|
211
358
|
|
|
212
359
|
const [Head, Outlet] = createPortal()
|
|
213
360
|
const ExtraHead = () => {
|
|
214
|
-
return [
|
|
361
|
+
return [
|
|
362
|
+
RWND_INJECT,
|
|
363
|
+
...resolveUserHeadAssets(),
|
|
364
|
+
...resolvePageHeadAssets(pageMeta),
|
|
365
|
+
Outlet(),
|
|
366
|
+
RWND_FALLBACK
|
|
367
|
+
]
|
|
215
368
|
}
|
|
216
369
|
|
|
217
|
-
await compilePageMdx(pageMeta, pagesContext)
|
|
218
370
|
const mdxComponent = pageMeta?.mdxComponent
|
|
219
371
|
|
|
220
372
|
const Page = ({ components: extraComponents, ...props }, ...children) =>
|
package/src/pagefind.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import { access } from 'fs/promises'
|
|
22
22
|
import { constants } from 'fs'
|
|
23
|
-
import { join } from 'path'
|
|
23
|
+
import { join, delimiter } from 'path'
|
|
24
24
|
import { spawn } from 'child_process'
|
|
25
25
|
import { state } from './state.js'
|
|
26
26
|
|
|
@@ -36,6 +36,14 @@ const resolvePagefindBin = async () => {
|
|
|
36
36
|
return candidate
|
|
37
37
|
} catch {}
|
|
38
38
|
}
|
|
39
|
+
const pathEntries = (process.env.PATH || '').split(delimiter).filter(Boolean)
|
|
40
|
+
for (const entry of pathEntries) {
|
|
41
|
+
const candidate = join(entry, binName)
|
|
42
|
+
try {
|
|
43
|
+
await access(candidate, constants.X_OK)
|
|
44
|
+
return candidate
|
|
45
|
+
} catch {}
|
|
46
|
+
}
|
|
39
47
|
return null
|
|
40
48
|
}
|
|
41
49
|
|
|
@@ -88,7 +96,7 @@ export const runPagefind = async () => {
|
|
|
88
96
|
return false
|
|
89
97
|
}
|
|
90
98
|
console.log('Running Pagefind search indexing...')
|
|
91
|
-
const extraArgs = buildArgsFromOptions(state.
|
|
99
|
+
const extraArgs = buildArgsFromOptions(state.PAGEFIND_BUILD)
|
|
92
100
|
const ok = await runCommand(bin, ['--site', state.DIST_DIR, ...extraArgs], {
|
|
93
101
|
cwd: state.PROJECT_ROOT
|
|
94
102
|
})
|
package/src/pages.js
CHANGED
|
@@ -28,7 +28,7 @@ import { createStageLogger } from './stage-logger.js'
|
|
|
28
28
|
|
|
29
29
|
const isPageFile = (name) => name.endsWith('.mdx') || name.endsWith('.md')
|
|
30
30
|
const isInternalPage = (name) => name.startsWith('_') || name.startsWith('.')
|
|
31
|
-
const isIgnoredEntry = (name) => name.startsWith('.')
|
|
31
|
+
const isIgnoredEntry = (name) => name.startsWith('.') || name.startsWith('_')
|
|
32
32
|
|
|
33
33
|
const pageMetadataCache = new Map()
|
|
34
34
|
const pageDerivedCache = new Map()
|
|
@@ -41,10 +41,18 @@ const collectLanguagesFromPages = (pages = []) => {
|
|
|
41
41
|
if (label == null || label === '') continue
|
|
42
42
|
const routePath = page.routePath || '/'
|
|
43
43
|
const href = page.routeHref || routePath || '/'
|
|
44
|
+
const frontmatterCode = page?.frontmatter?.langCode
|
|
45
|
+
const code =
|
|
46
|
+
typeof frontmatterCode === 'string' && frontmatterCode.trim()
|
|
47
|
+
? frontmatterCode.trim()
|
|
48
|
+
: routePath === '/'
|
|
49
|
+
? null
|
|
50
|
+
: routePath.replace(/^\/+/, '')
|
|
44
51
|
languages.set(routePath, {
|
|
45
52
|
routePath,
|
|
46
53
|
href,
|
|
47
|
-
label: String(label)
|
|
54
|
+
label: String(label),
|
|
55
|
+
code: code || null
|
|
48
56
|
})
|
|
49
57
|
}
|
|
50
58
|
return Array.from(languages.values()).sort((a, b) => a.href.localeCompare(b.href))
|
|
@@ -103,7 +111,13 @@ export const routePathFromFile = (filePath, pagesDir = state.PAGES_DIR) => {
|
|
|
103
111
|
|
|
104
112
|
const parseFrontmatter = (raw) => {
|
|
105
113
|
const parsed = matter(raw)
|
|
106
|
-
const data = parsed.data || {}
|
|
114
|
+
const data = { ...(parsed.data || {}) }
|
|
115
|
+
if (data.excerpt == null && data.description != null) {
|
|
116
|
+
data.excerpt = data.description
|
|
117
|
+
}
|
|
118
|
+
if (data.description == null && data.excerpt != null) {
|
|
119
|
+
data.description = data.excerpt
|
|
120
|
+
}
|
|
107
121
|
const content = parsed.content ?? ''
|
|
108
122
|
return {
|
|
109
123
|
data,
|
|
@@ -150,7 +164,19 @@ const buildPagesTree = (pages, options = {}) => {
|
|
|
150
164
|
const rootPath = normalizeRoutePath(options.rootPath || '/')
|
|
151
165
|
const rootDir = rootPath === '/' ? '' : rootPath.replace(/^\/+/, '')
|
|
152
166
|
const includeHiddenRoot = Boolean(options.includeHiddenRoot)
|
|
167
|
+
const currentRoutePath = normalizeRoutePath(options.currentRoutePath || '/')
|
|
153
168
|
const rootSegments = rootDir ? rootDir.split('/') : []
|
|
169
|
+
const resolveRouteWithinRoot = (routePath) => {
|
|
170
|
+
if (!routePath) return '/'
|
|
171
|
+
if (!rootDir) return routePath
|
|
172
|
+
if (routePath === rootPath) return '/'
|
|
173
|
+
if (routePath.startsWith(`${rootPath}/`)) {
|
|
174
|
+
const stripped = routePath.slice(rootPath.length)
|
|
175
|
+
return stripped.startsWith('/') ? stripped : `/${stripped}`
|
|
176
|
+
}
|
|
177
|
+
return routePath
|
|
178
|
+
}
|
|
179
|
+
const currentRouteWithinRoot = resolveRouteWithinRoot(currentRoutePath)
|
|
154
180
|
const isUnderRoot = (page) => {
|
|
155
181
|
if (!rootDir) return true
|
|
156
182
|
return page.routePath === rootPath || page.routePath.startsWith(`${rootPath}/`)
|
|
@@ -179,6 +205,18 @@ const buildPagesTree = (pages, options = {}) => {
|
|
|
179
205
|
.filter((page) => page.isIndex && page.dir && page.hidden && !(includeHiddenRoot && page.routePath === rootPath))
|
|
180
206
|
.map((page) => page.dir)
|
|
181
207
|
)
|
|
208
|
+
const exposedHiddenDirs = new Set()
|
|
209
|
+
if (currentRoutePath && currentRoutePath !== '/' && hiddenDirs.size) {
|
|
210
|
+
for (const hiddenDir of hiddenDirs) {
|
|
211
|
+
const hiddenRoute = `/${hiddenDir}`
|
|
212
|
+
if (
|
|
213
|
+
currentRouteWithinRoot === hiddenRoute ||
|
|
214
|
+
currentRouteWithinRoot.startsWith(`${hiddenRoute}/`)
|
|
215
|
+
) {
|
|
216
|
+
exposedHiddenDirs.add(hiddenDir)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
182
220
|
if (includeHiddenRoot && rootDir) {
|
|
183
221
|
for (const hiddenDir of Array.from(hiddenDirs)) {
|
|
184
222
|
if (rootDir === hiddenDir || rootDir.startsWith(`${hiddenDir}/`)) {
|
|
@@ -186,6 +224,11 @@ const buildPagesTree = (pages, options = {}) => {
|
|
|
186
224
|
}
|
|
187
225
|
}
|
|
188
226
|
}
|
|
227
|
+
if (exposedHiddenDirs.size) {
|
|
228
|
+
for (const hiddenDir of exposedHiddenDirs) {
|
|
229
|
+
hiddenDirs.delete(hiddenDir)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
189
232
|
const isUnderHiddenDir = (dir) => {
|
|
190
233
|
if (!dir) return false
|
|
191
234
|
const parts = dir.split('/')
|
|
@@ -215,9 +258,25 @@ const buildPagesTree = (pages, options = {}) => {
|
|
|
215
258
|
dirs.set(path, dir)
|
|
216
259
|
return dir
|
|
217
260
|
}
|
|
261
|
+
const isUnderExposedHiddenDir = (dir) => {
|
|
262
|
+
if (!dir || !exposedHiddenDirs.size) return false
|
|
263
|
+
for (const hiddenDir of exposedHiddenDirs) {
|
|
264
|
+
if (dir === hiddenDir || dir.startsWith(`${hiddenDir}/`)) {
|
|
265
|
+
return true
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return false
|
|
269
|
+
}
|
|
218
270
|
for (const page of treePages) {
|
|
219
271
|
if (page.hidden && !(includeHiddenRoot && page.routePath === rootPath)) {
|
|
220
|
-
|
|
272
|
+
const isHidden404 = page.routePath === '/404'
|
|
273
|
+
const shouldExposeHidden =
|
|
274
|
+
!isHidden404 &&
|
|
275
|
+
page.hiddenByFrontmatter === true &&
|
|
276
|
+
(page.routePath === currentRoutePath || isUnderExposedHiddenDir(page.dir))
|
|
277
|
+
if (!shouldExposeHidden) {
|
|
278
|
+
continue
|
|
279
|
+
}
|
|
221
280
|
}
|
|
222
281
|
if (isUnderHiddenDir(page.dir)) {
|
|
223
282
|
continue
|
|
@@ -352,6 +411,7 @@ export const buildPageEntry = async ({ filePath, pagesDir, source }) => {
|
|
|
352
411
|
const derived = pageDerivedCache.get(filePath)
|
|
353
412
|
const exclude = Boolean(metadata.frontmatter?.exclude)
|
|
354
413
|
const frontmatterHidden = metadata.frontmatter?.hidden
|
|
414
|
+
const hiddenByFrontmatter = frontmatterHidden === true
|
|
355
415
|
const isNotFoundPage = routePath === '/404'
|
|
356
416
|
const hidden = frontmatterHidden === false
|
|
357
417
|
? false
|
|
@@ -375,6 +435,7 @@ export const buildPageEntry = async ({ filePath, pagesDir, source }) => {
|
|
|
375
435
|
date: parseDate(metadata.frontmatter?.date) || parseDate(stats.mtime),
|
|
376
436
|
isRoot: Boolean(metadata.frontmatter?.isRoot),
|
|
377
437
|
hidden,
|
|
438
|
+
hiddenByFrontmatter,
|
|
378
439
|
exclude,
|
|
379
440
|
content: metadata.content,
|
|
380
441
|
frontmatter: metadata.frontmatter,
|
|
@@ -479,6 +540,38 @@ const resolveRootPath = (routePath, pagesByRoute, pagesByRouteIndex = null) => {
|
|
|
479
540
|
return '/'
|
|
480
541
|
}
|
|
481
542
|
|
|
543
|
+
const buildNavSequence = (nodes, pagesByRoute) => {
|
|
544
|
+
const result = []
|
|
545
|
+
const seen = new Set()
|
|
546
|
+
const addEntry = (entry) => {
|
|
547
|
+
if (!entry?.routePath) return
|
|
548
|
+
const key = entry.filePath || entry.routePath
|
|
549
|
+
if (seen.has(key)) return
|
|
550
|
+
seen.add(key)
|
|
551
|
+
result.push(entry)
|
|
552
|
+
}
|
|
553
|
+
const walk = (items = []) => {
|
|
554
|
+
for (const node of items) {
|
|
555
|
+
if (node.type === 'directory') {
|
|
556
|
+
if (node.routePath) {
|
|
557
|
+
const page = pagesByRoute.get(node.routePath) || node.page || null
|
|
558
|
+
if (page) addEntry(page)
|
|
559
|
+
}
|
|
560
|
+
if (node.children?.length) {
|
|
561
|
+
walk(node.children)
|
|
562
|
+
}
|
|
563
|
+
continue
|
|
564
|
+
}
|
|
565
|
+
if (node.type === 'page') {
|
|
566
|
+
const page = pagesByRoute.get(node.routePath) || node
|
|
567
|
+
addEntry(page)
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
walk(nodes)
|
|
572
|
+
return result
|
|
573
|
+
}
|
|
574
|
+
|
|
482
575
|
export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
483
576
|
const logEnabled = state.CURRENT_MODE === 'production' && cli.command === 'build'
|
|
484
577
|
const stageLogger = createStageLogger(logEnabled)
|
|
@@ -555,18 +648,34 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
|
555
648
|
return pagesByRoute.get(routePath) || pagesByRouteIndex.get(routePath) || null
|
|
556
649
|
}
|
|
557
650
|
const pagesTreeCache = new Map()
|
|
651
|
+
const navSequenceCache = new Map()
|
|
558
652
|
const getPagesTree = (routePath) => {
|
|
559
653
|
const rootPath = resolveRootPath(routePath, pagesByRoute, pagesByRouteIndex)
|
|
560
|
-
|
|
561
|
-
|
|
654
|
+
const normalizedRoute = normalizeRoutePath(routePath || '/')
|
|
655
|
+
const cacheKey = `${rootPath}::${normalizedRoute}`
|
|
656
|
+
if (pagesTreeCache.has(cacheKey)) {
|
|
657
|
+
return pagesTreeCache.get(cacheKey)
|
|
562
658
|
}
|
|
563
659
|
const tree = buildPagesTree(pages, {
|
|
564
660
|
rootPath,
|
|
565
|
-
includeHiddenRoot: rootPath !== '/'
|
|
661
|
+
includeHiddenRoot: rootPath !== '/',
|
|
662
|
+
currentRoutePath: normalizedRoute
|
|
566
663
|
})
|
|
567
|
-
pagesTreeCache.set(
|
|
664
|
+
pagesTreeCache.set(cacheKey, tree)
|
|
568
665
|
return tree
|
|
569
666
|
}
|
|
667
|
+
const getNavSequence = (routePath) => {
|
|
668
|
+
const rootPath = resolveRootPath(routePath, pagesByRoute, pagesByRouteIndex)
|
|
669
|
+
const normalizedRoute = normalizeRoutePath(routePath || '/')
|
|
670
|
+
const cacheKey = `${rootPath}::${normalizedRoute}`
|
|
671
|
+
if (navSequenceCache.has(cacheKey)) {
|
|
672
|
+
return navSequenceCache.get(cacheKey)
|
|
673
|
+
}
|
|
674
|
+
const tree = getPagesTree(routePath)
|
|
675
|
+
const sequence = buildNavSequence(tree, pagesByRoute)
|
|
676
|
+
navSequenceCache.set(cacheKey, sequence)
|
|
677
|
+
return sequence
|
|
678
|
+
}
|
|
570
679
|
let pagesTree = getPagesTree('/')
|
|
571
680
|
const notFound = pagesByRoute.get('/404') || null
|
|
572
681
|
const languages = collectLanguagesFromPages(pages)
|
|
@@ -581,7 +690,7 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
|
581
690
|
pagefind: {
|
|
582
691
|
enabled: state.PAGEFIND_ENABLED,
|
|
583
692
|
options: state.PAGEFIND_OPTIONS || null,
|
|
584
|
-
|
|
693
|
+
build: state.PAGEFIND_BUILD || null
|
|
585
694
|
},
|
|
586
695
|
generatedAt: new Date().toISOString()
|
|
587
696
|
}
|
|
@@ -604,8 +713,35 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
|
604
713
|
},
|
|
605
714
|
refreshPagesTree: () => {
|
|
606
715
|
pagesTreeCache.clear()
|
|
716
|
+
navSequenceCache.clear()
|
|
607
717
|
pagesContext.pagesTree = getPagesTree('/')
|
|
608
718
|
},
|
|
719
|
+
getSiblings: (routePath, filePath = null) => {
|
|
720
|
+
if (!routePath) return { prev: null, next: null }
|
|
721
|
+
const sequence = getNavSequence(routePath)
|
|
722
|
+
if (!sequence.length) return { prev: null, next: null }
|
|
723
|
+
let index = -1
|
|
724
|
+
if (filePath) {
|
|
725
|
+
index = sequence.findIndex((entry) => entry.filePath === filePath)
|
|
726
|
+
}
|
|
727
|
+
if (index < 0) {
|
|
728
|
+
index = sequence.findIndex((entry) => entry.routePath === routePath)
|
|
729
|
+
}
|
|
730
|
+
if (index < 0) return { prev: null, next: null }
|
|
731
|
+
const toNavEntry = (entry) => {
|
|
732
|
+
if (!entry) return null
|
|
733
|
+
return {
|
|
734
|
+
routePath: entry.routePath,
|
|
735
|
+
routeHref: entry.routeHref || entry.routePath,
|
|
736
|
+
title: entry.title || entry.name || entry.routePath,
|
|
737
|
+
filePath: entry.filePath || null
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return {
|
|
741
|
+
prev: toNavEntry(sequence[index - 1] || null),
|
|
742
|
+
next: toNavEntry(sequence[index + 1] || null)
|
|
743
|
+
}
|
|
744
|
+
},
|
|
609
745
|
refreshLanguages: () => {
|
|
610
746
|
pagesContext.languages = collectLanguagesFromPages(pages)
|
|
611
747
|
pagesContext.getLanguageForRoute = (routePath) =>
|
|
@@ -627,7 +763,10 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
|
627
763
|
if (logEnabled) {
|
|
628
764
|
stageLogger.update(compileToken, `Compiling MDX [${i + 1}/${totalPages}] ${page.filePath}`)
|
|
629
765
|
}
|
|
630
|
-
await compilePageMdx(page, pagesContext
|
|
766
|
+
await compilePageMdx(page, pagesContext, {
|
|
767
|
+
lazyPagesTree: true,
|
|
768
|
+
refreshPagesTree: false
|
|
769
|
+
})
|
|
631
770
|
}
|
|
632
771
|
stageLogger.end(compileToken)
|
|
633
772
|
pagesTreeCache.clear()
|
package/src/register-loader.js
CHANGED
|
@@ -18,12 +18,7 @@
|
|
|
18
18
|
* under the License.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { fileURLToPath } from 'node:url'
|
|
22
|
-
import { dirname, resolve } from 'node:path'
|
|
23
21
|
import { register } from 'node:module'
|
|
24
22
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const loaderPath = resolve(__dirname, '../src/node-loader.js')
|
|
28
|
-
|
|
29
|
-
register(loaderPath, import.meta.url)
|
|
23
|
+
const loaderUrl = new URL('./node-loader.js', import.meta.url)
|
|
24
|
+
register(loaderUrl.href, import.meta.url)
|
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { existsSync } from 'fs'
|
|
22
|
-
import { dirname, resolve } from 'path'
|
|
22
|
+
import { dirname, resolve, relative, isAbsolute } from 'path'
|
|
23
23
|
import { isElement } from 'hast-util-is-element'
|
|
24
24
|
import { visit } from 'unist-util-visit'
|
|
25
|
+
import { state } from '../state.js'
|
|
25
26
|
|
|
26
27
|
const extensionRegex = /\.(mdx?|html)$/i
|
|
27
28
|
|
|
@@ -44,14 +45,40 @@ const splitHref = (href) => {
|
|
|
44
45
|
|
|
45
46
|
const resolveCandidate = (baseDir, targetPath) => resolve(baseDir, targetPath)
|
|
46
47
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
const isWithinRoot = (root, targetPath) => {
|
|
49
|
+
if (!root) return false
|
|
50
|
+
const relPath = relative(root, targetPath)
|
|
51
|
+
if (relPath === '') return true
|
|
52
|
+
if (relPath.startsWith('..') || relPath.startsWith('..\\')) return false
|
|
53
|
+
if (isAbsolute(relPath)) return false
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const hasExistingSource = (baseDir, pathWithoutSuffix, extension, root) => {
|
|
58
|
+
const targetPath = resolveCandidate(baseDir, `${pathWithoutSuffix}${extension}`)
|
|
59
|
+
if (root && !isWithinRoot(root, targetPath)) {
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
return existsSync(targetPath)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const resolvePagesRoot = (filePath) => {
|
|
66
|
+
const roots = [state.PAGES_DIR, state.THEME_PAGES_DIR].filter(Boolean).map((dir) => resolve(dir))
|
|
67
|
+
if (!roots.length) return null
|
|
68
|
+
if (!filePath) return roots[0]
|
|
69
|
+
const resolvedFile = resolve(filePath)
|
|
70
|
+
for (const root of roots) {
|
|
71
|
+
if (isWithinRoot(root, resolvedFile)) {
|
|
72
|
+
return root
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return roots[0]
|
|
50
76
|
}
|
|
51
77
|
|
|
52
78
|
export const linkResolve = () => {
|
|
53
79
|
return (tree, file) => {
|
|
54
80
|
const baseDir = file?.path ? dirname(file.path) : file?.cwd || process.cwd()
|
|
81
|
+
const pagesRoot = resolvePagesRoot(file?.path || null)
|
|
55
82
|
visit(tree, (node) => {
|
|
56
83
|
if (!isElement(node) || node.tagName !== 'a') {
|
|
57
84
|
return
|
|
@@ -71,12 +98,12 @@ export const linkResolve = () => {
|
|
|
71
98
|
|
|
72
99
|
let shouldStrip = false
|
|
73
100
|
if (/\.mdx?$/i.test(extension)) {
|
|
74
|
-
shouldStrip = hasExistingSource(baseDir, withoutExtension, extension)
|
|
101
|
+
shouldStrip = hasExistingSource(baseDir, withoutExtension, extension, pagesRoot)
|
|
75
102
|
} else if (/\.html$/i.test(extension)) {
|
|
76
103
|
shouldStrip =
|
|
77
|
-
hasExistingSource(baseDir, withoutExtension, extension) ||
|
|
78
|
-
hasExistingSource(baseDir, withoutExtension, '.md') ||
|
|
79
|
-
hasExistingSource(baseDir, withoutExtension, '.mdx')
|
|
104
|
+
hasExistingSource(baseDir, withoutExtension, extension, pagesRoot) ||
|
|
105
|
+
hasExistingSource(baseDir, withoutExtension, '.md', pagesRoot) ||
|
|
106
|
+
hasExistingSource(baseDir, withoutExtension, '.mdx', pagesRoot)
|
|
80
107
|
}
|
|
81
108
|
|
|
82
109
|
if (!shouldStrip) {
|