methanol 0.0.1 → 0.0.2
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 +59 -18
- package/src/dev-server.js +80 -7
- package/src/main.js +47 -1
- package/src/mdx.js +176 -24
- package/src/pagefind.js +10 -2
- package/src/pages.js +142 -9
- package/src/rehype-plugins/link-resolve.js +35 -8
- package/src/state.js +23 -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/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))
|
|
@@ -150,7 +158,19 @@ const buildPagesTree = (pages, options = {}) => {
|
|
|
150
158
|
const rootPath = normalizeRoutePath(options.rootPath || '/')
|
|
151
159
|
const rootDir = rootPath === '/' ? '' : rootPath.replace(/^\/+/, '')
|
|
152
160
|
const includeHiddenRoot = Boolean(options.includeHiddenRoot)
|
|
161
|
+
const currentRoutePath = normalizeRoutePath(options.currentRoutePath || '/')
|
|
153
162
|
const rootSegments = rootDir ? rootDir.split('/') : []
|
|
163
|
+
const resolveRouteWithinRoot = (routePath) => {
|
|
164
|
+
if (!routePath) return '/'
|
|
165
|
+
if (!rootDir) return routePath
|
|
166
|
+
if (routePath === rootPath) return '/'
|
|
167
|
+
if (routePath.startsWith(`${rootPath}/`)) {
|
|
168
|
+
const stripped = routePath.slice(rootPath.length)
|
|
169
|
+
return stripped.startsWith('/') ? stripped : `/${stripped}`
|
|
170
|
+
}
|
|
171
|
+
return routePath
|
|
172
|
+
}
|
|
173
|
+
const currentRouteWithinRoot = resolveRouteWithinRoot(currentRoutePath)
|
|
154
174
|
const isUnderRoot = (page) => {
|
|
155
175
|
if (!rootDir) return true
|
|
156
176
|
return page.routePath === rootPath || page.routePath.startsWith(`${rootPath}/`)
|
|
@@ -179,6 +199,18 @@ const buildPagesTree = (pages, options = {}) => {
|
|
|
179
199
|
.filter((page) => page.isIndex && page.dir && page.hidden && !(includeHiddenRoot && page.routePath === rootPath))
|
|
180
200
|
.map((page) => page.dir)
|
|
181
201
|
)
|
|
202
|
+
const exposedHiddenDirs = new Set()
|
|
203
|
+
if (currentRoutePath && currentRoutePath !== '/' && hiddenDirs.size) {
|
|
204
|
+
for (const hiddenDir of hiddenDirs) {
|
|
205
|
+
const hiddenRoute = `/${hiddenDir}`
|
|
206
|
+
if (
|
|
207
|
+
currentRouteWithinRoot === hiddenRoute ||
|
|
208
|
+
currentRouteWithinRoot.startsWith(`${hiddenRoute}/`)
|
|
209
|
+
) {
|
|
210
|
+
exposedHiddenDirs.add(hiddenDir)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
182
214
|
if (includeHiddenRoot && rootDir) {
|
|
183
215
|
for (const hiddenDir of Array.from(hiddenDirs)) {
|
|
184
216
|
if (rootDir === hiddenDir || rootDir.startsWith(`${hiddenDir}/`)) {
|
|
@@ -186,6 +218,11 @@ const buildPagesTree = (pages, options = {}) => {
|
|
|
186
218
|
}
|
|
187
219
|
}
|
|
188
220
|
}
|
|
221
|
+
if (exposedHiddenDirs.size) {
|
|
222
|
+
for (const hiddenDir of exposedHiddenDirs) {
|
|
223
|
+
hiddenDirs.delete(hiddenDir)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
189
226
|
const isUnderHiddenDir = (dir) => {
|
|
190
227
|
if (!dir) return false
|
|
191
228
|
const parts = dir.split('/')
|
|
@@ -215,9 +252,25 @@ const buildPagesTree = (pages, options = {}) => {
|
|
|
215
252
|
dirs.set(path, dir)
|
|
216
253
|
return dir
|
|
217
254
|
}
|
|
255
|
+
const isUnderExposedHiddenDir = (dir) => {
|
|
256
|
+
if (!dir || !exposedHiddenDirs.size) return false
|
|
257
|
+
for (const hiddenDir of exposedHiddenDirs) {
|
|
258
|
+
if (dir === hiddenDir || dir.startsWith(`${hiddenDir}/`)) {
|
|
259
|
+
return true
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return false
|
|
263
|
+
}
|
|
218
264
|
for (const page of treePages) {
|
|
219
265
|
if (page.hidden && !(includeHiddenRoot && page.routePath === rootPath)) {
|
|
220
|
-
|
|
266
|
+
const isHidden404 = page.routePath === '/404'
|
|
267
|
+
const shouldExposeHidden =
|
|
268
|
+
!isHidden404 &&
|
|
269
|
+
page.hiddenByFrontmatter === true &&
|
|
270
|
+
(page.routePath === currentRoutePath || isUnderExposedHiddenDir(page.dir))
|
|
271
|
+
if (!shouldExposeHidden) {
|
|
272
|
+
continue
|
|
273
|
+
}
|
|
221
274
|
}
|
|
222
275
|
if (isUnderHiddenDir(page.dir)) {
|
|
223
276
|
continue
|
|
@@ -352,6 +405,7 @@ export const buildPageEntry = async ({ filePath, pagesDir, source }) => {
|
|
|
352
405
|
const derived = pageDerivedCache.get(filePath)
|
|
353
406
|
const exclude = Boolean(metadata.frontmatter?.exclude)
|
|
354
407
|
const frontmatterHidden = metadata.frontmatter?.hidden
|
|
408
|
+
const hiddenByFrontmatter = frontmatterHidden === true
|
|
355
409
|
const isNotFoundPage = routePath === '/404'
|
|
356
410
|
const hidden = frontmatterHidden === false
|
|
357
411
|
? false
|
|
@@ -375,6 +429,7 @@ export const buildPageEntry = async ({ filePath, pagesDir, source }) => {
|
|
|
375
429
|
date: parseDate(metadata.frontmatter?.date) || parseDate(stats.mtime),
|
|
376
430
|
isRoot: Boolean(metadata.frontmatter?.isRoot),
|
|
377
431
|
hidden,
|
|
432
|
+
hiddenByFrontmatter,
|
|
378
433
|
exclude,
|
|
379
434
|
content: metadata.content,
|
|
380
435
|
frontmatter: metadata.frontmatter,
|
|
@@ -479,6 +534,38 @@ const resolveRootPath = (routePath, pagesByRoute, pagesByRouteIndex = null) => {
|
|
|
479
534
|
return '/'
|
|
480
535
|
}
|
|
481
536
|
|
|
537
|
+
const buildNavSequence = (nodes, pagesByRoute) => {
|
|
538
|
+
const result = []
|
|
539
|
+
const seen = new Set()
|
|
540
|
+
const addEntry = (entry) => {
|
|
541
|
+
if (!entry?.routePath) return
|
|
542
|
+
const key = entry.filePath || entry.routePath
|
|
543
|
+
if (seen.has(key)) return
|
|
544
|
+
seen.add(key)
|
|
545
|
+
result.push(entry)
|
|
546
|
+
}
|
|
547
|
+
const walk = (items = []) => {
|
|
548
|
+
for (const node of items) {
|
|
549
|
+
if (node.type === 'directory') {
|
|
550
|
+
if (node.routePath) {
|
|
551
|
+
const page = pagesByRoute.get(node.routePath) || node.page || null
|
|
552
|
+
if (page) addEntry(page)
|
|
553
|
+
}
|
|
554
|
+
if (node.children?.length) {
|
|
555
|
+
walk(node.children)
|
|
556
|
+
}
|
|
557
|
+
continue
|
|
558
|
+
}
|
|
559
|
+
if (node.type === 'page') {
|
|
560
|
+
const page = pagesByRoute.get(node.routePath) || node
|
|
561
|
+
addEntry(page)
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
walk(nodes)
|
|
566
|
+
return result
|
|
567
|
+
}
|
|
568
|
+
|
|
482
569
|
export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
483
570
|
const logEnabled = state.CURRENT_MODE === 'production' && cli.command === 'build'
|
|
484
571
|
const stageLogger = createStageLogger(logEnabled)
|
|
@@ -555,18 +642,34 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
|
555
642
|
return pagesByRoute.get(routePath) || pagesByRouteIndex.get(routePath) || null
|
|
556
643
|
}
|
|
557
644
|
const pagesTreeCache = new Map()
|
|
645
|
+
const navSequenceCache = new Map()
|
|
558
646
|
const getPagesTree = (routePath) => {
|
|
559
647
|
const rootPath = resolveRootPath(routePath, pagesByRoute, pagesByRouteIndex)
|
|
560
|
-
|
|
561
|
-
|
|
648
|
+
const normalizedRoute = normalizeRoutePath(routePath || '/')
|
|
649
|
+
const cacheKey = `${rootPath}::${normalizedRoute}`
|
|
650
|
+
if (pagesTreeCache.has(cacheKey)) {
|
|
651
|
+
return pagesTreeCache.get(cacheKey)
|
|
562
652
|
}
|
|
563
653
|
const tree = buildPagesTree(pages, {
|
|
564
654
|
rootPath,
|
|
565
|
-
includeHiddenRoot: rootPath !== '/'
|
|
655
|
+
includeHiddenRoot: rootPath !== '/',
|
|
656
|
+
currentRoutePath: normalizedRoute
|
|
566
657
|
})
|
|
567
|
-
pagesTreeCache.set(
|
|
658
|
+
pagesTreeCache.set(cacheKey, tree)
|
|
568
659
|
return tree
|
|
569
660
|
}
|
|
661
|
+
const getNavSequence = (routePath) => {
|
|
662
|
+
const rootPath = resolveRootPath(routePath, pagesByRoute, pagesByRouteIndex)
|
|
663
|
+
const normalizedRoute = normalizeRoutePath(routePath || '/')
|
|
664
|
+
const cacheKey = `${rootPath}::${normalizedRoute}`
|
|
665
|
+
if (navSequenceCache.has(cacheKey)) {
|
|
666
|
+
return navSequenceCache.get(cacheKey)
|
|
667
|
+
}
|
|
668
|
+
const tree = getPagesTree(routePath)
|
|
669
|
+
const sequence = buildNavSequence(tree, pagesByRoute)
|
|
670
|
+
navSequenceCache.set(cacheKey, sequence)
|
|
671
|
+
return sequence
|
|
672
|
+
}
|
|
570
673
|
let pagesTree = getPagesTree('/')
|
|
571
674
|
const notFound = pagesByRoute.get('/404') || null
|
|
572
675
|
const languages = collectLanguagesFromPages(pages)
|
|
@@ -581,7 +684,7 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
|
581
684
|
pagefind: {
|
|
582
685
|
enabled: state.PAGEFIND_ENABLED,
|
|
583
686
|
options: state.PAGEFIND_OPTIONS || null,
|
|
584
|
-
|
|
687
|
+
build: state.PAGEFIND_BUILD || null
|
|
585
688
|
},
|
|
586
689
|
generatedAt: new Date().toISOString()
|
|
587
690
|
}
|
|
@@ -604,8 +707,35 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
|
604
707
|
},
|
|
605
708
|
refreshPagesTree: () => {
|
|
606
709
|
pagesTreeCache.clear()
|
|
710
|
+
navSequenceCache.clear()
|
|
607
711
|
pagesContext.pagesTree = getPagesTree('/')
|
|
608
712
|
},
|
|
713
|
+
getSiblings: (routePath, filePath = null) => {
|
|
714
|
+
if (!routePath) return { prev: null, next: null }
|
|
715
|
+
const sequence = getNavSequence(routePath)
|
|
716
|
+
if (!sequence.length) return { prev: null, next: null }
|
|
717
|
+
let index = -1
|
|
718
|
+
if (filePath) {
|
|
719
|
+
index = sequence.findIndex((entry) => entry.filePath === filePath)
|
|
720
|
+
}
|
|
721
|
+
if (index < 0) {
|
|
722
|
+
index = sequence.findIndex((entry) => entry.routePath === routePath)
|
|
723
|
+
}
|
|
724
|
+
if (index < 0) return { prev: null, next: null }
|
|
725
|
+
const toNavEntry = (entry) => {
|
|
726
|
+
if (!entry) return null
|
|
727
|
+
return {
|
|
728
|
+
routePath: entry.routePath,
|
|
729
|
+
routeHref: entry.routeHref || entry.routePath,
|
|
730
|
+
title: entry.title || entry.name || entry.routePath,
|
|
731
|
+
filePath: entry.filePath || null
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return {
|
|
735
|
+
prev: toNavEntry(sequence[index - 1] || null),
|
|
736
|
+
next: toNavEntry(sequence[index + 1] || null)
|
|
737
|
+
}
|
|
738
|
+
},
|
|
609
739
|
refreshLanguages: () => {
|
|
610
740
|
pagesContext.languages = collectLanguagesFromPages(pages)
|
|
611
741
|
pagesContext.getLanguageForRoute = (routePath) =>
|
|
@@ -627,7 +757,10 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
|
627
757
|
if (logEnabled) {
|
|
628
758
|
stageLogger.update(compileToken, `Compiling MDX [${i + 1}/${totalPages}] ${page.filePath}`)
|
|
629
759
|
}
|
|
630
|
-
await compilePageMdx(page, pagesContext
|
|
760
|
+
await compilePageMdx(page, pagesContext, {
|
|
761
|
+
lazyPagesTree: true,
|
|
762
|
+
refreshPagesTree: false
|
|
763
|
+
})
|
|
631
764
|
}
|
|
632
765
|
stageLogger.end(compileToken)
|
|
633
766
|
pagesTreeCache.clear()
|
|
@@ -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) {
|
package/src/state.js
CHANGED
|
@@ -101,6 +101,19 @@ const withCommonOptions = (y) =>
|
|
|
101
101
|
type: 'boolean',
|
|
102
102
|
default: false
|
|
103
103
|
})
|
|
104
|
+
.option('code-highlighting', {
|
|
105
|
+
describe: 'Enable or disable code highlighting',
|
|
106
|
+
type: 'string',
|
|
107
|
+
coerce: (value) => {
|
|
108
|
+
if (value == null) return null
|
|
109
|
+
if (value === true || value === '') return true
|
|
110
|
+
if (typeof value === 'boolean') return value
|
|
111
|
+
const normalized = String(value).trim().toLowerCase()
|
|
112
|
+
if (normalized === 'true') return true
|
|
113
|
+
if (normalized === 'false') return false
|
|
114
|
+
return null
|
|
115
|
+
}
|
|
116
|
+
})
|
|
104
117
|
|
|
105
118
|
const parser = yargs(hideBin(process.argv))
|
|
106
119
|
.scriptName('methanol')
|
|
@@ -127,7 +140,8 @@ export const cli = {
|
|
|
127
140
|
CLI_ASSETS_DIR: argv.assets || null,
|
|
128
141
|
CLI_OUTPUT_DIR: argv.output || null,
|
|
129
142
|
CLI_CONFIG_PATH: argv.config || null,
|
|
130
|
-
CLI_SITE_NAME: argv['site-name'] || null
|
|
143
|
+
CLI_SITE_NAME: argv['site-name'] || null,
|
|
144
|
+
CLI_CODE_HIGHLIGHTING: typeof argv['code-highlighting'] === 'boolean' ? argv['code-highlighting'] : null
|
|
131
145
|
}
|
|
132
146
|
|
|
133
147
|
export const state = {
|
|
@@ -149,10 +163,16 @@ export const state = {
|
|
|
149
163
|
USER_VITE_CONFIG: null,
|
|
150
164
|
USER_MDX_CONFIG: null,
|
|
151
165
|
USER_PUBLIC_OVERRIDE: false,
|
|
152
|
-
|
|
166
|
+
SOURCES: [],
|
|
153
167
|
PAGEFIND_ENABLED: false,
|
|
154
168
|
PAGEFIND_OPTIONS: null,
|
|
155
|
-
|
|
169
|
+
PAGEFIND_BUILD: null,
|
|
170
|
+
USER_PRE_BUILD_HOOKS: [],
|
|
171
|
+
USER_POST_BUILD_HOOKS: [],
|
|
172
|
+
THEME_PRE_BUILD_HOOKS: [],
|
|
173
|
+
THEME_POST_BUILD_HOOKS: [],
|
|
174
|
+
STARRY_NIGHT_ENABLED: false,
|
|
175
|
+
STARRY_NIGHT_OPTIONS: null,
|
|
156
176
|
CURRENT_MODE: 'production',
|
|
157
177
|
RESOLVED_MDX_CONFIG: undefined,
|
|
158
178
|
RESOLVED_VITE_CONFIG: undefined
|
package/src/vite-plugins.js
CHANGED
|
@@ -146,9 +146,9 @@ export const methanolResolverPlugin = () => {
|
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
if (state.
|
|
149
|
+
if (state.SOURCES.length) {
|
|
150
150
|
const { pathname, search } = new URL(id, 'http://methanol')
|
|
151
|
-
for (const entry of state.
|
|
151
|
+
for (const entry of state.SOURCES) {
|
|
152
152
|
const { find, replacement } = entry
|
|
153
153
|
if (!find || !replacement) continue
|
|
154
154
|
if (typeof find === 'string') {
|
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
import { signal, $ } from 'refui'
|
|
22
|
+
|
|
23
|
+
const ACCENTS = [
|
|
24
|
+
{ id: 'default', label: 'Amber', color: '#ffa000' },
|
|
25
|
+
{ id: 'rose', label: 'Rose', color: '#f43f5e' },
|
|
26
|
+
{ id: 'blue', label: 'Indigo', color: '#818cf8' },
|
|
27
|
+
{ id: 'green', label: 'Teal', color: '#2dd4bf' },
|
|
28
|
+
{ id: 'purple', label: 'Violet', color: '#a78bfa' }
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
export default function () {
|
|
32
|
+
const currentAccent = signal('default')
|
|
33
|
+
const isOpen = signal(false)
|
|
34
|
+
|
|
35
|
+
// Initialize theme from localStorage
|
|
36
|
+
if (typeof window !== 'undefined') {
|
|
37
|
+
const saved = localStorage.getItem('methanol-accent')
|
|
38
|
+
if (saved && ACCENTS.some((a) => a.id === saved)) {
|
|
39
|
+
currentAccent.value = saved
|
|
40
|
+
if (saved !== 'default') {
|
|
41
|
+
document.documentElement.classList.add(`accent-${saved}`)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Close popup when clicking outside
|
|
46
|
+
document.addEventListener('click', (e) => {
|
|
47
|
+
if (!e.target.closest('.theme-switch-wrapper')) {
|
|
48
|
+
isOpen.value = false
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const setAccent = (id) => {
|
|
54
|
+
const oldId = currentAccent.value
|
|
55
|
+
|
|
56
|
+
// Remove old
|
|
57
|
+
if (oldId !== 'default') {
|
|
58
|
+
document.documentElement.classList.remove(`accent-${oldId}`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Add new
|
|
62
|
+
if (id !== 'default') {
|
|
63
|
+
document.documentElement.classList.add(`accent-${id}`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
currentAccent.value = id
|
|
67
|
+
localStorage.setItem('methanol-accent', id)
|
|
68
|
+
isOpen.value = false
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const togglePopup = () => {
|
|
72
|
+
isOpen.value = !isOpen.value
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div class="theme-switch-container">
|
|
77
|
+
<div class="theme-switch-wrapper">
|
|
78
|
+
<div class={$(() => `accent-popup ${isOpen.value ? 'open' : ''}`)}>
|
|
79
|
+
{ACCENTS.map((accent) => (
|
|
80
|
+
<button
|
|
81
|
+
class={$(() => `accent-option ${currentAccent.value === accent.id ? 'active' : ''}`)}
|
|
82
|
+
on:click={() => setAccent(accent.id)}
|
|
83
|
+
>
|
|
84
|
+
<span class="option-circle" style={`background-color: ${accent.color}`}></span>
|
|
85
|
+
{accent.label}
|
|
86
|
+
</button>
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
89
|
+
<button class="theme-switch-btn" on:click={togglePopup} attr:aria-label="Select accent color">
|
|
90
|
+
<div class="accent-circle"></div>
|
|
91
|
+
</button>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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 () {
|
|
22
|
+
// render nothing on server side
|
|
23
|
+
}
|
|
@@ -87,7 +87,7 @@ export default function () {
|
|
|
87
87
|
|
|
88
88
|
return (
|
|
89
89
|
<div class="theme-switch-container">
|
|
90
|
-
<button class="theme-switch-btn" on:click={toggle} aria-label="Toggle theme">
|
|
90
|
+
<button class="theme-switch-btn" on:click={toggle} attr:aria-label="Toggle theme">
|
|
91
91
|
<CurrentIcon />
|
|
92
92
|
</button>
|
|
93
93
|
</div>
|