methanol 0.0.6 → 0.0.8
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 +5 -0
- package/package.json +1 -1
- package/src/assets.js +1 -1
- package/src/build-system.js +2 -1
- package/src/config.js +122 -4
- package/src/dev-server.js +49 -27
- package/src/main.js +3 -1
- package/src/mdx.js +31 -32
- package/src/pages.js +89 -77
- package/src/preview-server.js +1 -1
- package/src/public-assets.js +6 -6
- package/src/rehype-plugins/link-resolve.js +0 -1
- package/src/state.js +2 -0
- package/src/utils.js +9 -0
- package/src/virtual-module/inject.js +3 -4
- package/src/virtual-module/{pagefind.js → pagefind-loader.js} +23 -2
- package/src/vite-plugins.js +50 -6
- package/themes/default/components/ButtonGroup.jsx +38 -0
- package/themes/default/components/LinkButton.jsx +37 -0
- package/themes/default/components/ThemeSearchBox.client.jsx +9 -2
- package/themes/default/page.jsx +57 -28
- package/themes/default/sources/style.css +109 -1
package/src/pages.js
CHANGED
|
@@ -23,6 +23,7 @@ import { readdir, readFile, stat } from 'fs/promises'
|
|
|
23
23
|
import { existsSync } from 'fs'
|
|
24
24
|
import { resolve, join, relative } from 'path'
|
|
25
25
|
import { state, cli } from './state.js'
|
|
26
|
+
import { withBase } from './config.js'
|
|
26
27
|
import { compilePageMdx } from './mdx.js'
|
|
27
28
|
import { createStageLogger } from './stage-logger.js'
|
|
28
29
|
|
|
@@ -36,12 +37,11 @@ const pageDerivedCache = new Map()
|
|
|
36
37
|
const collectLanguagesFromPages = (pages = []) => {
|
|
37
38
|
const languages = new Map()
|
|
38
39
|
for (const page of pages) {
|
|
39
|
-
if (!page
|
|
40
|
-
const label = page
|
|
40
|
+
if (!page.isIndex) continue
|
|
41
|
+
const label = page.frontmatter?.lang
|
|
41
42
|
if (label == null || label === '') continue
|
|
42
43
|
const routePath = page.routePath || '/'
|
|
43
|
-
const
|
|
44
|
-
const frontmatterCode = page?.frontmatter?.langCode
|
|
44
|
+
const frontmatterCode = page.frontmatter?.langCode
|
|
45
45
|
const code =
|
|
46
46
|
typeof frontmatterCode === 'string' && frontmatterCode.trim()
|
|
47
47
|
? frontmatterCode.trim()
|
|
@@ -50,19 +50,39 @@ const collectLanguagesFromPages = (pages = []) => {
|
|
|
50
50
|
: routePath.replace(/^\/+/, '')
|
|
51
51
|
languages.set(routePath, {
|
|
52
52
|
routePath,
|
|
53
|
-
|
|
53
|
+
routeHref: withBase(routePath),
|
|
54
54
|
label: String(label),
|
|
55
55
|
code: code || null
|
|
56
56
|
})
|
|
57
57
|
}
|
|
58
|
-
return Array.from(languages.values()).sort((a, b) => a.
|
|
58
|
+
return Array.from(languages.values()).sort((a, b) => a.routePath.localeCompare(b.routePath))
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
const normalizeRoutePath = (value) => {
|
|
62
|
+
if (!value) return '/'
|
|
63
|
+
let normalized = value
|
|
64
|
+
if (!normalized.startsWith('/')) {
|
|
65
|
+
normalized = `/${normalized}`
|
|
66
|
+
}
|
|
67
|
+
normalized = normalized.replace(/\/{2,}/g, '/')
|
|
68
|
+
if (normalized === '/') return '/'
|
|
69
|
+
if (normalized.endsWith('/')) {
|
|
70
|
+
return normalized.replace(/\/+$/, '/')
|
|
71
|
+
}
|
|
72
|
+
return normalized
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const stripTrailingSlash = (value) => {
|
|
62
76
|
if (!value || value === '/') return '/'
|
|
63
77
|
return value.replace(/\/+$/, '')
|
|
64
78
|
}
|
|
65
79
|
|
|
80
|
+
const toRoutePrefix = (value) => {
|
|
81
|
+
const normalized = normalizeRoutePath(value)
|
|
82
|
+
const stripped = stripTrailingSlash(normalized)
|
|
83
|
+
return stripped === '/' ? '' : stripped
|
|
84
|
+
}
|
|
85
|
+
|
|
66
86
|
const resolveLanguageForRoute = (languages = [], routePath = '/') => {
|
|
67
87
|
if (!languages.length) return null
|
|
68
88
|
const normalizedRoute = normalizeRoutePath(routePath)
|
|
@@ -70,16 +90,17 @@ const resolveLanguageForRoute = (languages = [], routePath = '/') => {
|
|
|
70
90
|
let bestLength = -1
|
|
71
91
|
let rootLanguage = null
|
|
72
92
|
for (const lang of languages) {
|
|
73
|
-
const base = normalizeRoutePath(lang
|
|
93
|
+
const base = normalizeRoutePath(lang.routePath)
|
|
94
|
+
const basePrefix = toRoutePrefix(base)
|
|
74
95
|
if (!base) continue
|
|
75
96
|
if (base === '/') {
|
|
76
97
|
rootLanguage = lang
|
|
77
98
|
continue
|
|
78
99
|
}
|
|
79
|
-
if (normalizedRoute === base || normalizedRoute.startsWith(`${
|
|
80
|
-
if (
|
|
100
|
+
if (normalizedRoute === base || (basePrefix && normalizedRoute.startsWith(`${basePrefix}/`))) {
|
|
101
|
+
if (basePrefix.length > bestLength) {
|
|
81
102
|
best = lang
|
|
82
|
-
bestLength =
|
|
103
|
+
bestLength = basePrefix.length
|
|
83
104
|
}
|
|
84
105
|
}
|
|
85
106
|
}
|
|
@@ -104,7 +125,7 @@ export const routePathFromFile = (filePath, pagesDir = state.PAGES_DIR) => {
|
|
|
104
125
|
return '/'
|
|
105
126
|
}
|
|
106
127
|
if (normalized.endsWith('/index')) {
|
|
107
|
-
return `/${normalized.slice(0, -'/index'.length)}
|
|
128
|
+
return `/${normalized.slice(0, -'/index'.length)}/`
|
|
108
129
|
}
|
|
109
130
|
return `/${normalized}`
|
|
110
131
|
}
|
|
@@ -162,7 +183,8 @@ const stripRootPrefix = (value, rootDir) => {
|
|
|
162
183
|
|
|
163
184
|
const buildPagesTree = (pages, options = {}) => {
|
|
164
185
|
const rootPath = normalizeRoutePath(options.rootPath || '/')
|
|
165
|
-
const
|
|
186
|
+
const rootPrefix = toRoutePrefix(rootPath)
|
|
187
|
+
const rootDir = rootPrefix ? rootPrefix.replace(/^\/+/, '') : ''
|
|
166
188
|
const includeHiddenRoot = Boolean(options.includeHiddenRoot)
|
|
167
189
|
const currentRoutePath = normalizeRoutePath(options.currentRoutePath || '/')
|
|
168
190
|
const rootSegments = rootDir ? rootDir.split('/') : []
|
|
@@ -170,8 +192,8 @@ const buildPagesTree = (pages, options = {}) => {
|
|
|
170
192
|
if (!routePath) return '/'
|
|
171
193
|
if (!rootDir) return routePath
|
|
172
194
|
if (routePath === rootPath) return '/'
|
|
173
|
-
if (routePath.startsWith(`${
|
|
174
|
-
const stripped = routePath.slice(
|
|
195
|
+
if (rootPrefix && routePath.startsWith(`${rootPrefix}/`)) {
|
|
196
|
+
const stripped = routePath.slice(rootPrefix.length)
|
|
175
197
|
return stripped.startsWith('/') ? stripped : `/${stripped}`
|
|
176
198
|
}
|
|
177
199
|
return routePath
|
|
@@ -179,7 +201,7 @@ const buildPagesTree = (pages, options = {}) => {
|
|
|
179
201
|
const currentRouteWithinRoot = resolveRouteWithinRoot(currentRoutePath)
|
|
180
202
|
const isUnderRoot = (page) => {
|
|
181
203
|
if (!rootDir) return true
|
|
182
|
-
return page.routePath === rootPath || page.routePath.startsWith(`${
|
|
204
|
+
return page.routePath === rootPath || (rootPrefix && page.routePath.startsWith(`${rootPrefix}/`))
|
|
183
205
|
}
|
|
184
206
|
const treePages = pages
|
|
185
207
|
.filter((page) => !page.isInternal)
|
|
@@ -249,10 +271,10 @@ const buildPagesTree = (pages, options = {}) => {
|
|
|
249
271
|
children: [],
|
|
250
272
|
depth,
|
|
251
273
|
routePath: null,
|
|
274
|
+
routeHref: null,
|
|
252
275
|
title: null,
|
|
253
276
|
weight: null,
|
|
254
277
|
date: null,
|
|
255
|
-
routeHref: null,
|
|
256
278
|
isRoot: false
|
|
257
279
|
}
|
|
258
280
|
dirs.set(path, dir)
|
|
@@ -273,7 +295,10 @@ const buildPagesTree = (pages, options = {}) => {
|
|
|
273
295
|
const shouldExposeHidden =
|
|
274
296
|
!isHidden404 &&
|
|
275
297
|
page.hiddenByFrontmatter === true &&
|
|
276
|
-
(
|
|
298
|
+
(
|
|
299
|
+
page.routePath === currentRoutePath ||
|
|
300
|
+
(page.isIndex && page.dir && isUnderExposedHiddenDir(page.dir))
|
|
301
|
+
)
|
|
277
302
|
if (!shouldExposeHidden) {
|
|
278
303
|
continue
|
|
279
304
|
}
|
|
@@ -303,7 +328,7 @@ const buildPagesTree = (pages, options = {}) => {
|
|
|
303
328
|
if (page.isIndex && page.dir) {
|
|
304
329
|
const dir = getDirNode(page.dir, page.dir.split('/').pop(), page.depth)
|
|
305
330
|
dir.routePath = page.routePath
|
|
306
|
-
dir.routeHref = page.routeHref
|
|
331
|
+
dir.routeHref = page.routeHref
|
|
307
332
|
dir.title = page.title
|
|
308
333
|
dir.weight = page.weight ?? null
|
|
309
334
|
dir.date = page.date ?? null
|
|
@@ -378,9 +403,8 @@ const walkPages = async function* (dir, basePath = '') {
|
|
|
378
403
|
const name = entry.replace(/\.(mdx|md)$/, '')
|
|
379
404
|
const relativePath = join(basePath, name).replace(/\\/g, '/')
|
|
380
405
|
const isIndex = name === 'index'
|
|
381
|
-
const routePath = isIndex ? (basePath ? `/${basePath}
|
|
382
|
-
|
|
383
|
-
yield { routePath, routeHref, filePath: fullPath, isIndex }
|
|
406
|
+
const routePath = isIndex ? (basePath ? `/${basePath}/` : '/') : `/${relativePath}`
|
|
407
|
+
yield { routePath, filePath: fullPath, isIndex }
|
|
384
408
|
}
|
|
385
409
|
|
|
386
410
|
for (const { entry, fullPath } of dirs) {
|
|
@@ -397,7 +421,6 @@ export const buildPageEntry = async ({ filePath, pagesDir, source }) => {
|
|
|
397
421
|
const dir = name.split('/').slice(0, -1).join('/')
|
|
398
422
|
const dirName = dir ? dir.split('/').pop() : ''
|
|
399
423
|
const isIndex = baseName === 'index'
|
|
400
|
-
const routeHref = isIndex && dir ? `/${dir}/` : routePath
|
|
401
424
|
const segments = routePath.split('/').filter(Boolean)
|
|
402
425
|
const stats = await stat(filePath)
|
|
403
426
|
const cached = pageMetadataCache.get(filePath)
|
|
@@ -420,7 +443,7 @@ export const buildPageEntry = async ({ filePath, pagesDir, source }) => {
|
|
|
420
443
|
: isNotFoundPage || Boolean(metadata.frontmatter?.isRoot)
|
|
421
444
|
return {
|
|
422
445
|
routePath,
|
|
423
|
-
routeHref,
|
|
446
|
+
routeHref: withBase(routePath),
|
|
424
447
|
filePath,
|
|
425
448
|
source,
|
|
426
449
|
relativePath: relPath,
|
|
@@ -463,7 +486,6 @@ const collectPagesFromDir = async (pagesDir, source) => {
|
|
|
463
486
|
source
|
|
464
487
|
})
|
|
465
488
|
if (entry) {
|
|
466
|
-
entry.routeHref = page.routeHref || entry.routeHref
|
|
467
489
|
entry.isIndex = page.isIndex || entry.isIndex
|
|
468
490
|
pages.push(entry)
|
|
469
491
|
}
|
|
@@ -526,15 +548,22 @@ const buildIndexFallback = (pages, siteName) => {
|
|
|
526
548
|
return lines.join('\n')
|
|
527
549
|
}
|
|
528
550
|
|
|
529
|
-
const resolveRootPath = (routePath, pagesByRoute
|
|
551
|
+
const resolveRootPath = (routePath, pagesByRoute) => {
|
|
530
552
|
const normalized = normalizeRoutePath(routePath || '/')
|
|
531
553
|
const segments = normalized.split('/').filter(Boolean)
|
|
532
|
-
const lookup = pagesByRouteIndex || pagesByRoute
|
|
533
554
|
for (let i = segments.length; i >= 1; i--) {
|
|
534
555
|
const candidate = `/${segments.slice(0, i).join('/')}`
|
|
535
|
-
const page =
|
|
556
|
+
const page = pagesByRoute.get(candidate)
|
|
536
557
|
if (page?.isIndex && page?.isRoot) {
|
|
537
|
-
return
|
|
558
|
+
return page.routePath
|
|
559
|
+
}
|
|
560
|
+
const isExact = normalized === candidate
|
|
561
|
+
if (!isExact) {
|
|
562
|
+
const indexCandidate = candidate === '/' ? '/' : `${candidate}/`
|
|
563
|
+
const indexPage = pagesByRoute.get(indexCandidate)
|
|
564
|
+
if (indexPage?.isIndex && indexPage?.isRoot) {
|
|
565
|
+
return indexPage.routePath
|
|
566
|
+
}
|
|
538
567
|
}
|
|
539
568
|
}
|
|
540
569
|
return '/'
|
|
@@ -544,7 +573,7 @@ const buildNavSequence = (nodes, pagesByRoute) => {
|
|
|
544
573
|
const result = []
|
|
545
574
|
const seen = new Set()
|
|
546
575
|
const addEntry = (entry) => {
|
|
547
|
-
if (!entry
|
|
576
|
+
if (!entry.routePath) return
|
|
548
577
|
const key = entry.filePath || entry.routePath
|
|
549
578
|
if (seen.has(key)) return
|
|
550
579
|
seen.add(key)
|
|
@@ -584,54 +613,42 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
|
584
613
|
const hasIndex = pages.some((page) => page.routePath === '/')
|
|
585
614
|
if (!hasIndex) {
|
|
586
615
|
const content = buildIndexFallback(pages, state.SITE_NAME)
|
|
587
|
-
pages
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
},
|
|
611
|
-
...pages
|
|
612
|
-
]
|
|
616
|
+
pages.unshift({
|
|
617
|
+
routePath: '/',
|
|
618
|
+
routeHref: withBase('/'),
|
|
619
|
+
filePath: resolve(state.PAGES_DIR, 'index.md'),
|
|
620
|
+
relativePath: 'index.md',
|
|
621
|
+
name: 'index',
|
|
622
|
+
dir: '',
|
|
623
|
+
segments: [],
|
|
624
|
+
depth: 0,
|
|
625
|
+
isIndex: true,
|
|
626
|
+
isInternal: false,
|
|
627
|
+
title: state.SITE_NAME || 'Methanol Site',
|
|
628
|
+
weight: null,
|
|
629
|
+
date: null,
|
|
630
|
+
isRoot: false,
|
|
631
|
+
hidden: false,
|
|
632
|
+
content,
|
|
633
|
+
frontmatter: {},
|
|
634
|
+
matter: null,
|
|
635
|
+
stats: { size: content.length, createdAt: null, updatedAt: null },
|
|
636
|
+
createdAt: null,
|
|
637
|
+
updatedAt: null
|
|
638
|
+
})
|
|
613
639
|
if (excludedRoutes?.has('/')) {
|
|
614
640
|
excludedRoutes.delete('/')
|
|
615
641
|
}
|
|
616
642
|
}
|
|
617
643
|
|
|
618
644
|
const pagesByRoute = new Map()
|
|
619
|
-
const pagesByRouteIndex = new Map()
|
|
620
645
|
for (const page of pages) {
|
|
621
|
-
if (page.
|
|
622
|
-
pagesByRouteIndex.set(page.routePath, page)
|
|
623
|
-
if (!pagesByRoute.has(page.routePath)) {
|
|
624
|
-
pagesByRoute.set(page.routePath, page)
|
|
625
|
-
}
|
|
626
|
-
continue
|
|
627
|
-
}
|
|
628
|
-
const existing = pagesByRoute.get(page.routePath)
|
|
629
|
-
if (!existing || existing.isIndex) {
|
|
646
|
+
if (!pagesByRoute.has(page.routePath)) {
|
|
630
647
|
pagesByRoute.set(page.routePath, page)
|
|
631
648
|
}
|
|
632
649
|
}
|
|
633
650
|
const getPageByRoute = (routePath, options = {}) => {
|
|
634
|
-
const { filePath
|
|
651
|
+
const { filePath } = options || {}
|
|
635
652
|
if (filePath) {
|
|
636
653
|
for (const page of pages) {
|
|
637
654
|
if (page.routePath === routePath && page.filePath === filePath) {
|
|
@@ -639,18 +656,12 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
|
639
656
|
}
|
|
640
657
|
}
|
|
641
658
|
}
|
|
642
|
-
|
|
643
|
-
return pagesByRouteIndex.get(routePath) || pagesByRoute.get(routePath) || null
|
|
644
|
-
}
|
|
645
|
-
if (preferIndex === false) {
|
|
646
|
-
return pagesByRoute.get(routePath) || pagesByRouteIndex.get(routePath) || null
|
|
647
|
-
}
|
|
648
|
-
return pagesByRoute.get(routePath) || pagesByRouteIndex.get(routePath) || null
|
|
659
|
+
return pagesByRoute.get(routePath) || null
|
|
649
660
|
}
|
|
650
661
|
const pagesTreeCache = new Map()
|
|
651
662
|
const navSequenceCache = new Map()
|
|
652
663
|
const getPagesTree = (routePath) => {
|
|
653
|
-
const rootPath = resolveRootPath(routePath, pagesByRoute
|
|
664
|
+
const rootPath = resolveRootPath(routePath, pagesByRoute)
|
|
654
665
|
const normalizedRoute = normalizeRoutePath(routePath || '/')
|
|
655
666
|
const cacheKey = `${rootPath}::${normalizedRoute}`
|
|
656
667
|
if (pagesTreeCache.has(cacheKey)) {
|
|
@@ -665,7 +676,7 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
|
665
676
|
return tree
|
|
666
677
|
}
|
|
667
678
|
const getNavSequence = (routePath) => {
|
|
668
|
-
const rootPath = resolveRootPath(routePath, pagesByRoute
|
|
679
|
+
const rootPath = resolveRootPath(routePath, pagesByRoute)
|
|
669
680
|
const normalizedRoute = normalizeRoutePath(routePath || '/')
|
|
670
681
|
const cacheKey = `${rootPath}::${normalizedRoute}`
|
|
671
682
|
if (navSequenceCache.has(cacheKey)) {
|
|
@@ -680,8 +691,10 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
|
680
691
|
const notFound = pagesByRoute.get('/404') || null
|
|
681
692
|
const languages = collectLanguagesFromPages(pages)
|
|
682
693
|
const userSite = state.USER_SITE || {}
|
|
694
|
+
const siteBase = state.VITE_BASE ?? userSite.base ?? null
|
|
683
695
|
const site = {
|
|
684
696
|
...userSite,
|
|
697
|
+
base: siteBase,
|
|
685
698
|
name: state.SITE_NAME,
|
|
686
699
|
root: state.ROOT_DIR,
|
|
687
700
|
pagesDir: state.PAGES_DIR,
|
|
@@ -700,7 +713,6 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
|
700
713
|
const pagesContext = {
|
|
701
714
|
pages,
|
|
702
715
|
pagesByRoute,
|
|
703
|
-
pagesByRouteIndex,
|
|
704
716
|
getPageByRoute,
|
|
705
717
|
pagesTree,
|
|
706
718
|
getPagesTree,
|
|
@@ -734,7 +746,7 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
|
734
746
|
if (!entry) return null
|
|
735
747
|
return {
|
|
736
748
|
routePath: entry.routePath,
|
|
737
|
-
routeHref: entry.routeHref
|
|
749
|
+
routeHref: entry.routeHref,
|
|
738
750
|
title: entry.title || entry.name || entry.routePath,
|
|
739
751
|
filePath: entry.filePath || null
|
|
740
752
|
}
|
package/src/preview-server.js
CHANGED
|
@@ -28,8 +28,8 @@ import { methanolPreviewRoutingPlugin } from './vite-plugins.js'
|
|
|
28
28
|
export const runVitePreview = async () => {
|
|
29
29
|
const baseConfig = {
|
|
30
30
|
configFile: false,
|
|
31
|
+
appType: 'mpa',
|
|
31
32
|
root: state.PAGES_DIR,
|
|
32
|
-
base: '/',
|
|
33
33
|
build: {
|
|
34
34
|
outDir: state.DIST_DIR
|
|
35
35
|
}
|
package/src/public-assets.js
CHANGED
|
@@ -48,7 +48,7 @@ const linkOrCopyFile = async (src, dest) => {
|
|
|
48
48
|
|
|
49
49
|
try {
|
|
50
50
|
await ensureDir(dirname(dest))
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
if (isWindows) {
|
|
53
53
|
// Windows: Check for different drives first
|
|
54
54
|
if (parse(src).root.toLowerCase() !== parse(dest).root.toLowerCase()) {
|
|
@@ -85,7 +85,7 @@ const processDir = async (sourceDir, targetDir, accumulated = new Set()) => {
|
|
|
85
85
|
const sourcePath = resolve(sourceDir, entry)
|
|
86
86
|
const targetPath = resolve(targetDir, entry)
|
|
87
87
|
const stats = await stat(sourcePath)
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
if (stats.isDirectory()) {
|
|
90
90
|
await processDir(sourcePath, targetPath, accumulated)
|
|
91
91
|
} else {
|
|
@@ -104,7 +104,7 @@ export const preparePublicAssets = async ({ themeDir, userDir, targetDir }) => {
|
|
|
104
104
|
if (themeDir) {
|
|
105
105
|
await processDir(themeDir, targetDir)
|
|
106
106
|
}
|
|
107
|
-
|
|
107
|
+
|
|
108
108
|
if (userDir) {
|
|
109
109
|
await processDir(userDir, targetDir)
|
|
110
110
|
}
|
|
@@ -112,7 +112,7 @@ export const preparePublicAssets = async ({ themeDir, userDir, targetDir }) => {
|
|
|
112
112
|
|
|
113
113
|
export const updateAsset = async ({ type, filePath, themeDir, userDir, targetDir, relPath }) => {
|
|
114
114
|
const targetPath = resolve(targetDir, relPath)
|
|
115
|
-
|
|
115
|
+
|
|
116
116
|
if (type === 'unlink') {
|
|
117
117
|
try {
|
|
118
118
|
try {
|
|
@@ -122,7 +122,7 @@ export const updateAsset = async ({ type, filePath, themeDir, userDir, targetDir
|
|
|
122
122
|
await rm(targetPath, { recursive: true, force: true })
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
-
|
|
125
|
+
|
|
126
126
|
if (themeDir) {
|
|
127
127
|
const themePath = resolve(themeDir, relPath)
|
|
128
128
|
if (existsSync(themePath)) {
|
|
@@ -141,4 +141,4 @@ export const updateAsset = async ({ type, filePath, themeDir, userDir, targetDir
|
|
|
141
141
|
return 'updated'
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
-
}
|
|
144
|
+
}
|
|
@@ -27,7 +27,6 @@ import { state } from '../state.js'
|
|
|
27
27
|
const extensionRegex = /\.(mdx?|html)$/i
|
|
28
28
|
|
|
29
29
|
const isRelativeHref = (href) => {
|
|
30
|
-
if (typeof href !== 'string') return false
|
|
31
30
|
if (!href) return false
|
|
32
31
|
if (href.startsWith('#') || href.startsWith('?') || href.startsWith('/')) return false
|
|
33
32
|
if (href.startsWith('//')) return false
|
package/src/state.js
CHANGED
|
@@ -148,6 +148,8 @@ export const state = {
|
|
|
148
148
|
PROJECT_ROOT,
|
|
149
149
|
ROOT_DIR: PROJECT_ROOT,
|
|
150
150
|
SITE_NAME: 'Methanol Site',
|
|
151
|
+
SITE_BASE: null,
|
|
152
|
+
VITE_BASE: null,
|
|
151
153
|
PAGES_DIR: resolve(PROJECT_ROOT, 'pages'),
|
|
152
154
|
COMPONENTS_DIR: resolve(PROJECT_ROOT, 'components'),
|
|
153
155
|
STATIC_DIR: resolve(PROJECT_ROOT, 'public'),
|
package/src/utils.js
ADDED
|
@@ -21,10 +21,9 @@
|
|
|
21
21
|
import { createDOMRenderer } from 'refui/dom'
|
|
22
22
|
import { defaults } from 'refui/browser'
|
|
23
23
|
import { lazy } from 'refui'
|
|
24
|
-
import { init } from '
|
|
25
|
-
|
|
26
|
-
const reg = import('/.methanol_virtual_module/registry.js')
|
|
24
|
+
import { init } from 'methanol:loader'
|
|
25
|
+
import { registry } from 'methanol:registry'
|
|
27
26
|
|
|
28
27
|
const R = createDOMRenderer(defaults)
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
init(registry, R)
|
|
@@ -22,6 +22,27 @@ let pagefindInit = null
|
|
|
22
22
|
let pagefindUiInit = null
|
|
23
23
|
let pagefindUiReady = false
|
|
24
24
|
|
|
25
|
+
const resolveBasePrefix = () => {
|
|
26
|
+
let base = import.meta.env?.BASE_URL || '/'
|
|
27
|
+
if (!base || base === '/' || base === './') return ''
|
|
28
|
+
if (base.startsWith('http://') || base.startsWith('https://')) {
|
|
29
|
+
try {
|
|
30
|
+
base = new URL(base).pathname
|
|
31
|
+
} catch {
|
|
32
|
+
return ''
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!base.startsWith('/')) return ''
|
|
36
|
+
if (base.endsWith('/')) base = base.slice(0, -1)
|
|
37
|
+
return base
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const withBase = (path) => {
|
|
41
|
+
const prefix = resolveBasePrefix()
|
|
42
|
+
if (!prefix || path.startsWith(`${prefix}/`)) return path
|
|
43
|
+
return `${prefix}${path}`
|
|
44
|
+
}
|
|
45
|
+
|
|
25
46
|
const dynamicImport = (path) => {
|
|
26
47
|
try {
|
|
27
48
|
const importer = new Function('p', 'return import(p)')
|
|
@@ -38,7 +59,7 @@ export const loadPagefind = async () => {
|
|
|
38
59
|
resolve(null)
|
|
39
60
|
return
|
|
40
61
|
}
|
|
41
|
-
dynamicImport('/pagefind/pagefind.js')
|
|
62
|
+
dynamicImport(withBase('/pagefind/pagefind.js'))
|
|
42
63
|
.then((mod) => {
|
|
43
64
|
if (!mod) return resolve(null)
|
|
44
65
|
if (mod.search) return resolve(mod)
|
|
@@ -89,7 +110,7 @@ export const loadPagefindUI = async (options = {}) => {
|
|
|
89
110
|
return
|
|
90
111
|
}
|
|
91
112
|
const script = document.createElement('script')
|
|
92
|
-
script.src = '/pagefind/pagefind-ui.js'
|
|
113
|
+
script.src = withBase('/pagefind/pagefind-ui.js')
|
|
93
114
|
script.async = true
|
|
94
115
|
script.onload = () => done(initPagefindUI(options))
|
|
95
116
|
script.onerror = () => done(false)
|
package/src/vite-plugins.js
CHANGED
|
@@ -24,8 +24,9 @@ import { existsSync } from 'node:fs'
|
|
|
24
24
|
import { resolve } from 'node:path'
|
|
25
25
|
import { normalizePath } from 'vite'
|
|
26
26
|
import { state } from './state.js'
|
|
27
|
+
import { resolveBasePrefix } from './config.js'
|
|
27
28
|
import { genRegistryScript } from './components.js'
|
|
28
|
-
import { INJECT_SCRIPT, LOADER_SCRIPT,
|
|
29
|
+
import { INJECT_SCRIPT, LOADER_SCRIPT, PAGEFIND_LOADER_SCRIPT } from './assets.js'
|
|
29
30
|
import { projectRequire } from './node-loader.js'
|
|
30
31
|
|
|
31
32
|
const require = createRequire(import.meta.url)
|
|
@@ -60,6 +61,9 @@ export const methanolPreviewRoutingPlugin = (distDir, notFoundPath) => ({
|
|
|
60
61
|
cachedHtml = await readFile(notFoundPath, 'utf-8')
|
|
61
62
|
return cachedHtml
|
|
62
63
|
}
|
|
64
|
+
|
|
65
|
+
const basePrefix = resolveBasePrefix()
|
|
66
|
+
|
|
63
67
|
const handler = async (req, res, next) => {
|
|
64
68
|
if (!req.url || req.method !== 'GET') {
|
|
65
69
|
return next()
|
|
@@ -69,6 +73,13 @@ export const methanolPreviewRoutingPlugin = (distDir, notFoundPath) => ({
|
|
|
69
73
|
try {
|
|
70
74
|
pathname = new URL(req.url, 'http://methanol').pathname
|
|
71
75
|
pathname = decodeURIComponent(pathname)
|
|
76
|
+
if (basePrefix) {
|
|
77
|
+
if (pathname.startsWith(basePrefix)) {
|
|
78
|
+
pathname = pathname.slice(basePrefix.length)
|
|
79
|
+
} else {
|
|
80
|
+
return next()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
72
83
|
} catch {}
|
|
73
84
|
const hasTrailingSlash = pathname.endsWith('/') && pathname !== '/'
|
|
74
85
|
if (pathname.includes('.') && !pathname.endsWith('.html')) {
|
|
@@ -109,20 +120,43 @@ export const methanolPreviewRoutingPlugin = (distDir, notFoundPath) => ({
|
|
|
109
120
|
|
|
110
121
|
const virtualModulePrefix = '/.methanol_virtual_module/'
|
|
111
122
|
const resolvedVirtualModulePrefix = '\0' + virtualModulePrefix
|
|
123
|
+
const virtualModuleScheme = 'methanol:'
|
|
112
124
|
|
|
113
125
|
const virtualModuleMap = {
|
|
126
|
+
get registry() {
|
|
127
|
+
return `export const registry = ${genRegistryScript()}`
|
|
128
|
+
},
|
|
114
129
|
get 'registry.js'() {
|
|
115
130
|
return `export const registry = ${genRegistryScript()}`
|
|
116
131
|
},
|
|
117
|
-
|
|
132
|
+
loader: LOADER_SCRIPT,
|
|
118
133
|
'inject.js': INJECT_SCRIPT,
|
|
119
|
-
'pagefind
|
|
134
|
+
'pagefind-loader': PAGEFIND_LOADER_SCRIPT
|
|
120
135
|
}
|
|
121
136
|
|
|
122
137
|
const getModuleIdSegment = (id, start) => {
|
|
123
138
|
return new URL(id.slice(start), 'http://methanol').pathname.slice(1)
|
|
124
139
|
}
|
|
125
140
|
|
|
141
|
+
const getSchemeModuleKey = (id) => {
|
|
142
|
+
if (!id.startsWith(virtualModuleScheme)) return null
|
|
143
|
+
return id.slice(virtualModuleScheme.length)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const resolveVirtualModuleId = (id) => {
|
|
147
|
+
if (id.startsWith(virtualModulePrefix)) {
|
|
148
|
+
return id
|
|
149
|
+
}
|
|
150
|
+
const basePrefix = resolveBasePrefix()
|
|
151
|
+
if (basePrefix) {
|
|
152
|
+
const prefixed = `${basePrefix}${virtualModulePrefix}`
|
|
153
|
+
if (id.startsWith(prefixed)) {
|
|
154
|
+
return id.slice(basePrefix.length)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return null
|
|
158
|
+
}
|
|
159
|
+
|
|
126
160
|
export const methanolResolverPlugin = () => {
|
|
127
161
|
return {
|
|
128
162
|
name: 'methanol-resolver',
|
|
@@ -139,10 +173,16 @@ export const methanolResolverPlugin = () => {
|
|
|
139
173
|
return require.resolve(id)
|
|
140
174
|
}
|
|
141
175
|
|
|
142
|
-
|
|
143
|
-
|
|
176
|
+
const schemeKey = getSchemeModuleKey(id)
|
|
177
|
+
if (schemeKey && Object.prototype.hasOwnProperty.call(virtualModuleMap, schemeKey)) {
|
|
178
|
+
return '\0' + id
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const virtualId = resolveVirtualModuleId(id)
|
|
182
|
+
if (virtualId) {
|
|
183
|
+
const _moduleId = getModuleIdSegment(virtualId, virtualModulePrefix.length)
|
|
144
184
|
if (Object.prototype.hasOwnProperty.call(virtualModuleMap, _moduleId)) {
|
|
145
|
-
return '\0' +
|
|
185
|
+
return '\0' + virtualId
|
|
146
186
|
}
|
|
147
187
|
}
|
|
148
188
|
|
|
@@ -164,6 +204,10 @@ export const methanolResolverPlugin = () => {
|
|
|
164
204
|
}
|
|
165
205
|
},
|
|
166
206
|
load(id) {
|
|
207
|
+
if (id.startsWith('\0' + virtualModuleScheme)) {
|
|
208
|
+
const key = id.slice(1 + virtualModuleScheme.length)
|
|
209
|
+
return virtualModuleMap[key]
|
|
210
|
+
}
|
|
167
211
|
if (id.startsWith(resolvedVirtualModulePrefix)) {
|
|
168
212
|
const _moduleId = getModuleIdSegment(id, resolvedVirtualModulePrefix.length)
|
|
169
213
|
return virtualModuleMap[_moduleId]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* Copyright Yukino Song, SudoMaker Ltd.
|
|
2
|
+
*
|
|
3
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
4
|
+
* or more contributor license agreements. See the NOTICE file
|
|
5
|
+
* distributed with this work for additional information
|
|
6
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
7
|
+
* to you under the Apache License, Version 2.0 (the
|
|
8
|
+
* "License"); you may not use this file except in compliance
|
|
9
|
+
* with the License. You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing,
|
|
14
|
+
* software distributed under the License is distributed on an
|
|
15
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
16
|
+
* KIND, either express or implied. See the License for the
|
|
17
|
+
* specific language governing permissions and limitations
|
|
18
|
+
* under the License.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export default function ButtonGroup({
|
|
22
|
+
children,
|
|
23
|
+
className = '',
|
|
24
|
+
align = 'left', // left, center, right
|
|
25
|
+
...props
|
|
26
|
+
}) {
|
|
27
|
+
const classes = [
|
|
28
|
+
'button-group',
|
|
29
|
+
`button-group--${align}`,
|
|
30
|
+
className
|
|
31
|
+
].filter(Boolean).join(' ')
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div class={classes} {...props}>
|
|
35
|
+
{children}
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|