methanol 0.0.4 → 0.0.6
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 +1 -1
- package/package.json +1 -1
- package/src/build-system.js +6 -6
- package/src/config.js +30 -8
- package/src/dev-server.js +31 -6
- package/src/main.js +2 -0
- package/src/pages.js +2 -0
- package/src/public-assets.js +105 -34
- package/src/state.js +1 -0
- package/themes/default/components/ThemeToCContainer.client.jsx +22 -15
- package/themes/default/components/ThemeToCContainer.static.jsx +1 -1
- package/themes/default/heading.jsx +1 -1
- package/themes/default/page.jsx +23 -34
- package/themes/default/{sources/prefetch.js → public/theme-prepare.js} +13 -1
- package/themes/default/sources/style.css +5 -24
package/README.md
CHANGED
package/package.json
CHANGED
package/src/build-system.js
CHANGED
|
@@ -28,7 +28,7 @@ import { renderHtml } from './mdx.js'
|
|
|
28
28
|
import { buildComponentRegistry } from './components.js'
|
|
29
29
|
import { methanolVirtualHtmlPlugin, methanolResolverPlugin } from './vite-plugins.js'
|
|
30
30
|
import { createStageLogger } from './stage-logger.js'
|
|
31
|
-
import {
|
|
31
|
+
import { preparePublicAssets } from './public-assets.js'
|
|
32
32
|
|
|
33
33
|
const ensureDir = async (dir) => {
|
|
34
34
|
await mkdir(dir, { recursive: true })
|
|
@@ -163,11 +163,11 @@ export const buildHtmlEntries = async () => {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
export const runViteBuild = async (entry, htmlCache) => {
|
|
166
|
-
if (state.STATIC_DIR !== false) {
|
|
167
|
-
await
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
166
|
+
if (state.STATIC_DIR !== false && state.MERGED_ASSETS_DIR) {
|
|
167
|
+
await preparePublicAssets({
|
|
168
|
+
themeDir: state.THEME_ASSETS_DIR,
|
|
169
|
+
userDir: state.USER_ASSETS_DIR,
|
|
170
|
+
targetDir: state.MERGED_ASSETS_DIR
|
|
171
171
|
})
|
|
172
172
|
}
|
|
173
173
|
const copyPublicDirEnabled = state.STATIC_DIR !== false
|
package/src/config.js
CHANGED
|
@@ -216,6 +216,7 @@ export const applyConfig = async (config, mode) => {
|
|
|
216
216
|
state.ROOT_DIR = root
|
|
217
217
|
const configSiteName = cli.CLI_SITE_NAME ?? config.site?.name ?? null
|
|
218
218
|
state.SITE_NAME = configSiteName || basename(root) || 'Methanol Site'
|
|
219
|
+
state.USER_SITE = config.site && typeof config.site === 'object' ? { ...config.site } : null
|
|
219
220
|
if (mode) {
|
|
220
221
|
state.CURRENT_MODE = mode
|
|
221
222
|
}
|
|
@@ -289,15 +290,36 @@ export const applyConfig = async (config, mode) => {
|
|
|
289
290
|
if (hasOwn(state.USER_THEME, 'publicDir') && state.THEME_PUBLIC_DIR && !existsSync(state.THEME_PUBLIC_DIR)) {
|
|
290
291
|
throw new Error(`Theme public directory not found: ${state.THEME_PUBLIC_DIR}`)
|
|
291
292
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
)
|
|
299
|
-
|
|
293
|
+
|
|
294
|
+
// Asset Merging Logic
|
|
295
|
+
const userAssetsDir = userSpecifiedPublicDir
|
|
296
|
+
? resolveFromRoot(root, publicDirValue, 'public')
|
|
297
|
+
: resolveFromRoot(root, 'public', 'public')
|
|
298
|
+
|
|
299
|
+
const hasUserAssets = existsSync(userAssetsDir)
|
|
300
|
+
state.USER_ASSETS_DIR = hasUserAssets ? userAssetsDir : null
|
|
301
|
+
state.THEME_ASSETS_DIR = state.THEME_PUBLIC_DIR && existsSync(state.THEME_PUBLIC_DIR) ? state.THEME_PUBLIC_DIR : null
|
|
302
|
+
|
|
303
|
+
if (state.STATIC_DIR !== false) {
|
|
304
|
+
if (!hasUserAssets) {
|
|
305
|
+
// Optimization: No user assets, just use theme assets directly
|
|
306
|
+
state.STATIC_DIR = state.THEME_ASSETS_DIR
|
|
307
|
+
state.MERGED_ASSETS_DIR = null
|
|
308
|
+
} else {
|
|
309
|
+
// We need to merge
|
|
310
|
+
const nodeModulesPath = resolve(root, 'node_modules')
|
|
311
|
+
if (existsSync(nodeModulesPath)) {
|
|
312
|
+
state.MERGED_ASSETS_DIR = resolve(nodeModulesPath, '.methanol/assets')
|
|
313
|
+
} else {
|
|
314
|
+
state.MERGED_ASSETS_DIR = resolve(state.PAGES_DIR || resolve(root, 'pages'), '.methanol/assets')
|
|
315
|
+
}
|
|
316
|
+
state.STATIC_DIR = state.MERGED_ASSETS_DIR
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
state.STATIC_DIR = false
|
|
320
|
+
state.MERGED_ASSETS_DIR = null
|
|
300
321
|
}
|
|
322
|
+
|
|
301
323
|
state.SOURCES = normalizeSources(state.USER_THEME.sources, themeRoot)
|
|
302
324
|
state.USER_VITE_CONFIG = config.vite || null
|
|
303
325
|
state.USER_MDX_CONFIG = config.mdx || null
|
package/src/dev-server.js
CHANGED
|
@@ -39,10 +39,13 @@ import {
|
|
|
39
39
|
import { buildPagesContext, buildPageEntry, routePathFromFile } from './pages.js'
|
|
40
40
|
import { compilePageMdx, renderHtml } from './mdx.js'
|
|
41
41
|
import { methanolResolverPlugin } from './vite-plugins.js'
|
|
42
|
-
import {
|
|
42
|
+
import { preparePublicAssets, updateAsset } from './public-assets.js'
|
|
43
43
|
|
|
44
44
|
export const runViteDev = async () => {
|
|
45
45
|
const baseFsAllow = [state.ROOT_DIR, state.USER_THEME.root].filter(Boolean)
|
|
46
|
+
if (state.MERGED_ASSETS_DIR) {
|
|
47
|
+
baseFsAllow.push(state.MERGED_ASSETS_DIR)
|
|
48
|
+
}
|
|
46
49
|
const baseConfig = {
|
|
47
50
|
configFile: false,
|
|
48
51
|
root: state.PAGES_DIR,
|
|
@@ -63,11 +66,11 @@ export const runViteDev = async () => {
|
|
|
63
66
|
}
|
|
64
67
|
const userConfig = await resolveUserViteConfig('serve')
|
|
65
68
|
const finalConfig = userConfig ? mergeConfig(baseConfig, userConfig) : baseConfig
|
|
66
|
-
if (state.STATIC_DIR !== false) {
|
|
67
|
-
await
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
if (state.STATIC_DIR !== false && state.MERGED_ASSETS_DIR) {
|
|
70
|
+
await preparePublicAssets({
|
|
71
|
+
themeDir: state.THEME_ASSETS_DIR,
|
|
72
|
+
userDir: state.USER_ASSETS_DIR,
|
|
73
|
+
targetDir: state.MERGED_ASSETS_DIR
|
|
71
74
|
})
|
|
72
75
|
}
|
|
73
76
|
if (cli.CLI_PORT != null) {
|
|
@@ -94,6 +97,28 @@ export const runViteDev = async () => {
|
|
|
94
97
|
}
|
|
95
98
|
const server = await createServer(finalConfig)
|
|
96
99
|
|
|
100
|
+
if (state.MERGED_ASSETS_DIR && state.USER_ASSETS_DIR) {
|
|
101
|
+
const assetWatcher = chokidar.watch(state.USER_ASSETS_DIR, {
|
|
102
|
+
ignoreInitial: true
|
|
103
|
+
})
|
|
104
|
+
const handleAssetUpdate = (type, filePath) => {
|
|
105
|
+
const relPath = relative(state.USER_ASSETS_DIR, filePath)
|
|
106
|
+
enqueue(async () => {
|
|
107
|
+
await updateAsset({
|
|
108
|
+
type,
|
|
109
|
+
filePath,
|
|
110
|
+
relPath,
|
|
111
|
+
themeDir: state.THEME_ASSETS_DIR,
|
|
112
|
+
userDir: state.USER_ASSETS_DIR,
|
|
113
|
+
targetDir: state.MERGED_ASSETS_DIR
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
assetWatcher.on('add', (filePath) => handleAssetUpdate('add', filePath))
|
|
118
|
+
assetWatcher.on('change', (filePath) => handleAssetUpdate('change', filePath))
|
|
119
|
+
assetWatcher.on('unlink', (filePath) => handleAssetUpdate('unlink', filePath))
|
|
120
|
+
}
|
|
121
|
+
|
|
97
122
|
const themeComponentsDir = state.THEME_COMPONENTS_DIR
|
|
98
123
|
const themeEnv = state.THEME_ENV
|
|
99
124
|
const themeRegistry = themeComponentsDir
|
package/src/main.js
CHANGED
|
@@ -62,6 +62,7 @@ const main = async () => {
|
|
|
62
62
|
const mode = isDev ? 'development' : 'production'
|
|
63
63
|
const config = await loadUserConfig(mode, cli.CLI_CONFIG_PATH)
|
|
64
64
|
await applyConfig(config, mode)
|
|
65
|
+
const userSite = state.USER_SITE || {}
|
|
65
66
|
const hookContext = {
|
|
66
67
|
mode,
|
|
67
68
|
root: state.ROOT_DIR,
|
|
@@ -71,6 +72,7 @@ const main = async () => {
|
|
|
71
72
|
isPreview,
|
|
72
73
|
HTMLRenderer,
|
|
73
74
|
site: {
|
|
75
|
+
...userSite,
|
|
74
76
|
name: state.SITE_NAME,
|
|
75
77
|
root: state.ROOT_DIR,
|
|
76
78
|
pagesDir: state.PAGES_DIR,
|
package/src/pages.js
CHANGED
|
@@ -679,7 +679,9 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
|
|
|
679
679
|
let pagesTree = getPagesTree('/')
|
|
680
680
|
const notFound = pagesByRoute.get('/404') || null
|
|
681
681
|
const languages = collectLanguagesFromPages(pages)
|
|
682
|
+
const userSite = state.USER_SITE || {}
|
|
682
683
|
const site = {
|
|
684
|
+
...userSite,
|
|
683
685
|
name: state.SITE_NAME,
|
|
684
686
|
root: state.ROOT_DIR,
|
|
685
687
|
pagesDir: state.PAGES_DIR,
|
package/src/public-assets.js
CHANGED
|
@@ -18,56 +18,127 @@
|
|
|
18
18
|
* under the License.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { readdir, stat, copyFile, mkdir } from 'fs/promises'
|
|
21
|
+
import { readdir, stat, lstat, copyFile, mkdir, symlink, unlink, rm, link } from 'fs/promises'
|
|
22
22
|
import { existsSync } from 'fs'
|
|
23
|
-
import { resolve, dirname, relative } from 'path'
|
|
23
|
+
import { resolve, dirname, relative, parse } from 'path'
|
|
24
24
|
|
|
25
25
|
const ensureDir = async (dir) => {
|
|
26
26
|
await mkdir(dir, { recursive: true })
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const
|
|
30
|
-
|
|
29
|
+
const isWindows = process.platform === 'win32'
|
|
30
|
+
|
|
31
|
+
const linkOrCopyFile = async (src, dest) => {
|
|
32
|
+
try {
|
|
33
|
+
try {
|
|
34
|
+
await lstat(dest)
|
|
35
|
+
await unlink(dest)
|
|
36
|
+
} catch (e) {
|
|
37
|
+
if (e.code !== 'ENOENT') {
|
|
38
|
+
try {
|
|
39
|
+
await rm(dest, { recursive: true, force: true })
|
|
40
|
+
} catch (e2) {
|
|
41
|
+
console.error(`Methanol: Failed to clean destination ${dest}`, e2)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error(`Methanol: Failed to remove existing file at ${dest}`, err)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await ensureDir(dirname(dest))
|
|
51
|
+
|
|
52
|
+
if (isWindows) {
|
|
53
|
+
// Windows: Check for different drives first
|
|
54
|
+
if (parse(src).root.toLowerCase() !== parse(dest).root.toLowerCase()) {
|
|
55
|
+
await copyFile(src, dest)
|
|
56
|
+
return 'copied'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Try hard link (no admin required)
|
|
60
|
+
try {
|
|
61
|
+
await link(src, dest)
|
|
62
|
+
return 'hardlinked'
|
|
63
|
+
} catch (err) {
|
|
64
|
+
// Fallback to copy
|
|
65
|
+
// console.warn(`Methanol: Hardlink failed for ${src} -> ${dest}. Falling back to copy.`, err.message)
|
|
66
|
+
await copyFile(src, dest)
|
|
67
|
+
return 'copied (fallback)'
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
// macOS/Linux: Symlink
|
|
71
|
+
await symlink(src, dest)
|
|
72
|
+
return 'symlinked'
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error(`Methanol: Failed to link ${src} to ${dest}`, err)
|
|
76
|
+
return 'failed'
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const processDir = async (sourceDir, targetDir, accumulated = new Set()) => {
|
|
81
|
+
if (!existsSync(sourceDir)) return
|
|
31
82
|
const entries = await readdir(sourceDir)
|
|
32
83
|
for (const entry of entries) {
|
|
33
|
-
if (entry.startsWith('.'))
|
|
34
|
-
continue
|
|
35
|
-
}
|
|
84
|
+
if (entry.startsWith('.')) continue
|
|
36
85
|
const sourcePath = resolve(sourceDir, entry)
|
|
37
86
|
const targetPath = resolve(targetDir, entry)
|
|
38
87
|
const stats = await stat(sourcePath)
|
|
88
|
+
|
|
39
89
|
if (stats.isDirectory()) {
|
|
40
|
-
await
|
|
90
|
+
await processDir(sourcePath, targetPath, accumulated)
|
|
41
91
|
} else {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
onFile(sourcePath, targetPath, { skipped: true })
|
|
45
|
-
}
|
|
46
|
-
continue
|
|
47
|
-
}
|
|
48
|
-
await ensureDir(dirname(targetPath))
|
|
49
|
-
await copyFile(sourcePath, targetPath)
|
|
50
|
-
if (onFile) {
|
|
51
|
-
onFile(sourcePath, targetPath, { skipped: false })
|
|
52
|
-
}
|
|
92
|
+
await linkOrCopyFile(sourcePath, targetPath)
|
|
93
|
+
accumulated.add(relative(targetDir, targetPath))
|
|
53
94
|
}
|
|
54
95
|
}
|
|
55
96
|
}
|
|
56
97
|
|
|
57
|
-
export const
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
98
|
+
export const preparePublicAssets = async ({ themeDir, userDir, targetDir }) => {
|
|
99
|
+
if (existsSync(targetDir)) {
|
|
100
|
+
await rm(targetDir, { recursive: true, force: true })
|
|
101
|
+
}
|
|
102
|
+
await ensureDir(targetDir)
|
|
103
|
+
|
|
104
|
+
if (themeDir) {
|
|
105
|
+
await processDir(themeDir, targetDir)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (userDir) {
|
|
109
|
+
await processDir(userDir, targetDir)
|
|
67
110
|
}
|
|
68
|
-
await copyDir(resolvedSource, resolvedTarget, (sourcePath, targetPath, info) => {
|
|
69
|
-
const rel = relative(resolvedSource, sourcePath).replace(/\\/g, '/')
|
|
70
|
-
if (info?.skipped) return
|
|
71
|
-
console.log(`Methanol: copied ${label}/${rel}`)
|
|
72
|
-
})
|
|
73
111
|
}
|
|
112
|
+
|
|
113
|
+
export const updateAsset = async ({ type, filePath, themeDir, userDir, targetDir, relPath }) => {
|
|
114
|
+
const targetPath = resolve(targetDir, relPath)
|
|
115
|
+
|
|
116
|
+
if (type === 'unlink') {
|
|
117
|
+
try {
|
|
118
|
+
try {
|
|
119
|
+
await unlink(targetPath)
|
|
120
|
+
} catch (e) {
|
|
121
|
+
if (e.code !== 'ENOENT') {
|
|
122
|
+
await rm(targetPath, { recursive: true, force: true })
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (themeDir) {
|
|
127
|
+
const themePath = resolve(themeDir, relPath)
|
|
128
|
+
if (existsSync(themePath)) {
|
|
129
|
+
await linkOrCopyFile(themePath, targetPath)
|
|
130
|
+
return 'restored theme asset'
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.error(`Methanol: Error updating asset ${relPath}`, err)
|
|
135
|
+
}
|
|
136
|
+
return 'removed'
|
|
137
|
+
} else {
|
|
138
|
+
const sourcePath = userDir ? resolve(userDir, relPath) : null
|
|
139
|
+
if (sourcePath && existsSync(sourcePath)) {
|
|
140
|
+
await linkOrCopyFile(sourcePath, targetPath)
|
|
141
|
+
return 'updated'
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
package/src/state.js
CHANGED
|
@@ -29,19 +29,21 @@ export default function (props, ...children) {
|
|
|
29
29
|
const top = signal(0)
|
|
30
30
|
const height = signal(0)
|
|
31
31
|
const opacity = signal(0)
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
const updateActive = () => {
|
|
34
34
|
if (!el.value) return
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
const links = Array.from(el.value.querySelectorAll('a'))
|
|
37
37
|
if (!links.length) return
|
|
38
38
|
|
|
39
39
|
// Map links to their corresponding content anchors
|
|
40
|
-
const anchors = links
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
const anchors = links
|
|
41
|
+
.map((link) => {
|
|
42
|
+
const href = link.getAttribute('href')
|
|
43
|
+
if (!href || !href.startsWith('#')) return null
|
|
44
|
+
return document.getElementById(href.slice(1))
|
|
45
|
+
})
|
|
46
|
+
.filter(Boolean)
|
|
45
47
|
|
|
46
48
|
if (!anchors.length) return
|
|
47
49
|
|
|
@@ -56,13 +58,13 @@ export default function (props, ...children) {
|
|
|
56
58
|
for (let i = 0; i < anchors.length; i++) {
|
|
57
59
|
const anchor = anchors[i]
|
|
58
60
|
const nextAnchor = anchors[i + 1]
|
|
59
|
-
|
|
61
|
+
|
|
60
62
|
const sectionStart = anchor.offsetTop - threshold
|
|
61
63
|
const sectionEnd = nextAnchor ? nextAnchor.offsetTop - threshold : document.body.offsetHeight
|
|
62
64
|
|
|
63
65
|
// A section is visible if its range overlaps with the viewport [scrollY, scrollY + windowHeight]
|
|
64
|
-
const isVisible = sectionStart <
|
|
65
|
-
|
|
66
|
+
const isVisible = sectionStart < scrollY + windowHeight - threshold && sectionEnd > scrollY
|
|
67
|
+
|
|
66
68
|
if (isVisible) {
|
|
67
69
|
visibleAnchors.add(anchor)
|
|
68
70
|
}
|
|
@@ -77,10 +79,10 @@ export default function (props, ...children) {
|
|
|
77
79
|
let firstActiveLink = null
|
|
78
80
|
let lastActiveLink = null
|
|
79
81
|
|
|
80
|
-
links.forEach(l => {
|
|
82
|
+
links.forEach((l) => {
|
|
81
83
|
const href = l.getAttribute('href')
|
|
82
84
|
const anchorId = href ? href.slice(1) : null
|
|
83
|
-
const anchor = anchors.find(a => a.id === anchorId)
|
|
85
|
+
const anchor = anchors.find((a) => a.id === anchorId)
|
|
84
86
|
if (visibleAnchors.has(anchor)) {
|
|
85
87
|
l.classList.add('active')
|
|
86
88
|
if (!firstActiveLink) firstActiveLink = l
|
|
@@ -130,7 +132,7 @@ export default function (props, ...children) {
|
|
|
130
132
|
ticking = true
|
|
131
133
|
}
|
|
132
134
|
}
|
|
133
|
-
|
|
135
|
+
|
|
134
136
|
// Wait for mount/layout
|
|
135
137
|
useEffect(() => {
|
|
136
138
|
updateActive()
|
|
@@ -144,9 +146,14 @@ export default function (props, ...children) {
|
|
|
144
146
|
|
|
145
147
|
return (
|
|
146
148
|
<aside class="toc-panel" $ref={el}>
|
|
147
|
-
<div
|
|
149
|
+
<div
|
|
150
|
+
class="toc-indicator"
|
|
151
|
+
style:top={t`${top}px`}
|
|
152
|
+
style:height={t`${height}px`}
|
|
153
|
+
style:opacity={t`${opacity}`}
|
|
154
|
+
></div>
|
|
148
155
|
<div class="toc">
|
|
149
|
-
<
|
|
156
|
+
<div class="toc-heading">On this page</div>
|
|
150
157
|
<ul>{...children}</ul>
|
|
151
158
|
</div>
|
|
152
159
|
</aside>
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
export function heading(Tag) {
|
|
22
22
|
return (props, ...children) => (
|
|
23
23
|
<Tag {...props}>
|
|
24
|
-
{props.id && <a class="heading-anchor" href={`#${props.id}`} aria-
|
|
24
|
+
{props.id && <a class="heading-anchor" href={`#${props.id}`} aria-label={`Link to ${props.id}`}/>}
|
|
25
25
|
{...children}
|
|
26
26
|
</Tag>
|
|
27
27
|
)
|
package/themes/default/page.jsx
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
import { HTMLRenderer as R } from 'methanol'
|
|
22
22
|
import { renderToc } from './components/ThemeToCContainer.static.jsx'
|
|
23
23
|
|
|
24
|
+
const DOCHTML = R.rawHTML`<!DOCTYPE html>`
|
|
25
|
+
|
|
24
26
|
const renderPageTree = (nodes = [], currentRoute, depth = 0) => {
|
|
25
27
|
const items = []
|
|
26
28
|
let hasActive = false
|
|
@@ -86,7 +88,7 @@ const PAGE_TEMPLATE = ({ Page, ExtraHead, components, ctx }) => {
|
|
|
86
88
|
const themeFavIcon = '/favicon.png'
|
|
87
89
|
const logo = pageFrontmatter.logo ?? rootFrontmatter.logo ?? ctx.site?.logo ?? themeLogo
|
|
88
90
|
const favicon = pageFrontmatter.favicon ?? rootFrontmatter.favicon ?? ctx.site?.favicon ?? themeFavIcon
|
|
89
|
-
const excerpt = pageFrontmatter.excerpt ??
|
|
91
|
+
const excerpt = pageFrontmatter.excerpt ?? `${title} | ${siteName} - Powered by Methanol`
|
|
90
92
|
const ogTitle = pageFrontmatter.ogTitle ?? null
|
|
91
93
|
const ogDescription = pageFrontmatter.ogDescription ?? null
|
|
92
94
|
const ogImage = pageFrontmatter.ogImage ?? null
|
|
@@ -100,8 +102,7 @@ const PAGE_TEMPLATE = ({ Page, ExtraHead, components, ctx }) => {
|
|
|
100
102
|
const nextPage = siblings?.next || null
|
|
101
103
|
const languages = Array.isArray(ctx.languages) ? ctx.languages : []
|
|
102
104
|
const currentLanguageHref = ctx.language?.href || ctx.language?.routePath || null
|
|
103
|
-
const languageCode =
|
|
104
|
-
pageFrontmatter.langCode ?? rootFrontmatter.langCode ?? ctx.language?.code ?? 'en'
|
|
105
|
+
const languageCode = pageFrontmatter.langCode ?? rootFrontmatter.langCode ?? ctx.language?.code ?? 'en'
|
|
105
106
|
const htmlLang = typeof languageCode === 'string' && languageCode.trim() ? languageCode : 'en'
|
|
106
107
|
const pagefindEnabled = ctx.site?.pagefind?.enabled !== false
|
|
107
108
|
const pagefindOptions = ctx.site?.pagefind?.options || null
|
|
@@ -143,7 +144,7 @@ const PAGE_TEMPLATE = ({ Page, ExtraHead, components, ctx }) => {
|
|
|
143
144
|
) : null
|
|
144
145
|
return (
|
|
145
146
|
<>
|
|
146
|
-
{
|
|
147
|
+
{DOCHTML}
|
|
147
148
|
<html lang={htmlLang}>
|
|
148
149
|
<head>
|
|
149
150
|
<meta charset="UTF-8" />
|
|
@@ -152,8 +153,8 @@ const PAGE_TEMPLATE = ({ Page, ExtraHead, components, ctx }) => {
|
|
|
152
153
|
{title} | {siteName}
|
|
153
154
|
</title>
|
|
154
155
|
{baseHref ? <base href={baseHref} /> : null}
|
|
155
|
-
<link rel="icon" href={favicon} />
|
|
156
|
-
|
|
156
|
+
{favicon ? <link rel="icon" href={favicon} /> : null}
|
|
157
|
+
<meta name="description" content={excerpt} />
|
|
157
158
|
{ogTitle ? <meta property="og:title" content={ogTitle} /> : null}
|
|
158
159
|
{ogDescription ? <meta property="og:description" content={ogDescription} /> : null}
|
|
159
160
|
{ogImage ? <meta property="og:image" content={ogImage} /> : null}
|
|
@@ -164,23 +165,7 @@ const PAGE_TEMPLATE = ({ Page, ExtraHead, components, ctx }) => {
|
|
|
164
165
|
{twitterImage ? <meta name="twitter:image" content={twitterImage} /> : null}
|
|
165
166
|
<ExtraHead />
|
|
166
167
|
<link rel="preload stylesheet" as="style" href="/.methanol_theme_default/style.css" />
|
|
167
|
-
|
|
168
|
-
<script>
|
|
169
|
-
(function() {
|
|
170
|
-
const savedTheme = localStorage.getItem('methanol-theme');
|
|
171
|
-
const systemTheme = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
|
172
|
-
const theme = savedTheme || systemTheme;
|
|
173
|
-
document.documentElement.classList.toggle('light', theme === 'light');
|
|
174
|
-
document.documentElement.classList.toggle('dark', theme === 'dark');
|
|
175
|
-
|
|
176
|
-
const savedAccent = localStorage.getItem('methanol-accent');
|
|
177
|
-
if (savedAccent && savedAccent !== 'default') {
|
|
178
|
-
document.documentElement.classList.add('accent-' + savedAccent);
|
|
179
|
-
}
|
|
180
|
-
})();
|
|
181
|
-
</script>
|
|
182
|
-
`}
|
|
183
|
-
<script type="module" src="/.methanol_theme_default/prefetch.js" defer></script>
|
|
168
|
+
<script src="/theme-prepare.js"></script>
|
|
184
169
|
</head>
|
|
185
170
|
<body>
|
|
186
171
|
<input type="checkbox" id="nav-toggle" class="nav-toggle" />
|
|
@@ -201,11 +186,7 @@ const PAGE_TEMPLATE = ({ Page, ExtraHead, components, ctx }) => {
|
|
|
201
186
|
</svg>
|
|
202
187
|
</label>
|
|
203
188
|
{pagefindEnabled ? (
|
|
204
|
-
<button
|
|
205
|
-
class="search-toggle-label"
|
|
206
|
-
aria-label="Open search"
|
|
207
|
-
onclick="window.__methanolSearchOpen()"
|
|
208
|
-
>
|
|
189
|
+
<button class="search-toggle-label" aria-label="Open search" onclick="window.__methanolSearchOpen()">
|
|
209
190
|
<svg
|
|
210
191
|
width="24"
|
|
211
192
|
height="24"
|
|
@@ -249,7 +230,7 @@ const PAGE_TEMPLATE = ({ Page, ExtraHead, components, ctx }) => {
|
|
|
249
230
|
<aside class="sidebar">
|
|
250
231
|
<div class="sidebar-header">
|
|
251
232
|
<div class="logo">
|
|
252
|
-
<img src={logo} />
|
|
233
|
+
{logo ? <img src={logo} alt="logo" fetchpriority="high"/> : null}
|
|
253
234
|
<span>{siteName}</span>
|
|
254
235
|
</div>
|
|
255
236
|
{pagefindEnabled ? <ThemeSearchBox options={pagefindOptions} /> : null}
|
|
@@ -272,7 +253,9 @@ const PAGE_TEMPLATE = ({ Page, ExtraHead, components, ctx }) => {
|
|
|
272
253
|
<span class="page-nav-label">Previous</span>
|
|
273
254
|
<span class="page-nav-title">{prevPage.title || prevPage.routePath}</span>
|
|
274
255
|
</a>
|
|
275
|
-
) :
|
|
256
|
+
) : (
|
|
257
|
+
<div class="page-nav-spacer"></div>
|
|
258
|
+
)}
|
|
276
259
|
{nextPage ? (
|
|
277
260
|
<a class="page-nav-card next" href={nextPage.routeHref || nextPage.routePath}>
|
|
278
261
|
<span class="page-nav-label">Next</span>
|
|
@@ -283,11 +266,17 @@ const PAGE_TEMPLATE = ({ Page, ExtraHead, components, ctx }) => {
|
|
|
283
266
|
) : null}
|
|
284
267
|
{page ? (
|
|
285
268
|
<footer class="page-meta">
|
|
269
|
+
<div class="page-meta-item">Updated: {page.updatedAt || '-'}</div>
|
|
286
270
|
<div class="page-meta-item">
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
271
|
+
Powered by{' '}
|
|
272
|
+
<a
|
|
273
|
+
href="https://github.com/SudoMaker/Methanol"
|
|
274
|
+
target="_blank"
|
|
275
|
+
rel="noopener noreferrer"
|
|
276
|
+
class="methanol-link"
|
|
277
|
+
>
|
|
278
|
+
Methanol
|
|
279
|
+
</a>
|
|
291
280
|
</div>
|
|
292
281
|
</footer>
|
|
293
282
|
) : null}
|
|
@@ -18,7 +18,19 @@
|
|
|
18
18
|
* under the License.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
(()
|
|
21
|
+
;(function initThemeColor() {
|
|
22
|
+
const savedTheme = localStorage.getItem('methanol-theme')
|
|
23
|
+
const systemTheme = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'
|
|
24
|
+
const theme = savedTheme || systemTheme
|
|
25
|
+
document.documentElement.classList.toggle('light', theme === 'light')
|
|
26
|
+
document.documentElement.classList.toggle('dark', theme === 'dark')
|
|
27
|
+
|
|
28
|
+
const savedAccent = localStorage.getItem('methanol-accent')
|
|
29
|
+
if (savedAccent && savedAccent !== 'default') {
|
|
30
|
+
document.documentElement.classList.add('accent-' + savedAccent)
|
|
31
|
+
}
|
|
32
|
+
})()
|
|
33
|
+
;(function initPrefetch() {
|
|
22
34
|
const prefetched = new Set()
|
|
23
35
|
const canPrefetch = (anchor) => {
|
|
24
36
|
if (!anchor || !anchor.href) return false
|
|
@@ -270,7 +270,7 @@ a {
|
|
|
270
270
|
.sidebar {
|
|
271
271
|
position: sticky;
|
|
272
272
|
top: 0;
|
|
273
|
-
height:
|
|
273
|
+
height: 100dvh;
|
|
274
274
|
overflow: visible;
|
|
275
275
|
padding: 2rem 0;
|
|
276
276
|
display: flex;
|
|
@@ -1105,7 +1105,7 @@ a {
|
|
|
1105
1105
|
}
|
|
1106
1106
|
|
|
1107
1107
|
.toc {
|
|
1108
|
-
|
|
1108
|
+
.toc-heading {
|
|
1109
1109
|
margin: 0 0 1rem 0.5rem;
|
|
1110
1110
|
font-size: 0.8rem;
|
|
1111
1111
|
font-weight: 600;
|
|
@@ -1420,6 +1420,7 @@ a {
|
|
|
1420
1420
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
1421
1421
|
position: relative;
|
|
1422
1422
|
overflow: hidden;
|
|
1423
|
+
-webkit-tap-highlight-color: transparent;
|
|
1423
1424
|
|
|
1424
1425
|
&:hover {
|
|
1425
1426
|
border-color: var(--accent);
|
|
@@ -1473,7 +1474,7 @@ a {
|
|
|
1473
1474
|
|
|
1474
1475
|
.page-nav-card.next {
|
|
1475
1476
|
text-align: right;
|
|
1476
|
-
align-items:
|
|
1477
|
+
align-items: stretch;
|
|
1477
1478
|
|
|
1478
1479
|
.page-nav-label {
|
|
1479
1480
|
justify-content: flex-end;
|
|
@@ -1493,26 +1494,6 @@ a {
|
|
|
1493
1494
|
grid-template-columns: 1fr;
|
|
1494
1495
|
gap: 1rem;
|
|
1495
1496
|
}
|
|
1496
|
-
|
|
1497
|
-
.page-nav-card.next {
|
|
1498
|
-
text-align: left;
|
|
1499
|
-
align-items: flex-start;
|
|
1500
|
-
|
|
1501
|
-
.page-nav-label {
|
|
1502
|
-
flex-direction: row;
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
.page-nav-label::after {
|
|
1506
|
-
display: none;
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
.page-nav-label::before {
|
|
1510
|
-
content: '→';
|
|
1511
|
-
font-family: system-ui;
|
|
1512
|
-
margin-right: 0.1rem;
|
|
1513
|
-
transition: transform 0.2s ease;
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
1497
|
}
|
|
1517
1498
|
|
|
1518
1499
|
/* --- Mobile / Toggles --- */
|
|
@@ -1566,7 +1547,7 @@ a {
|
|
|
1566
1547
|
.toc-panel {
|
|
1567
1548
|
position: fixed;
|
|
1568
1549
|
top: 0;
|
|
1569
|
-
|
|
1550
|
+
height: 100dvh;
|
|
1570
1551
|
width: 280px;
|
|
1571
1552
|
background: var(--surface);
|
|
1572
1553
|
border: 1px solid var(--border);
|