methanol 0.0.25 → 0.0.27

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "methanol",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
4
4
  "description": "Static site generator powered by rEFui and MDX",
5
5
  "main": "./index.js",
6
6
  "type": "module",
@@ -31,21 +31,21 @@
31
31
  "@stefanprobst/rehype-extract-toc": "^3.0.0",
32
32
  "@wooorm/starry-night": "^3.9.0",
33
33
  "chokidar": "^5.0.0",
34
- "esbuild": "^0.27.2",
34
+ "esbuild": "^0.28.0",
35
35
  "fast-glob": "^3.3.3",
36
36
  "gray-matter": "^4.0.3",
37
37
  "hast-util-is-element": "^3.0.0",
38
- "htmlparser2": "^10.1.0",
38
+ "htmlparser2": "^12.0.0",
39
39
  "json5": "^2.2.3",
40
- "null-prototype-object": "^1.2.5",
41
- "picomatch": "^4.0.3",
42
- "refui": "^0.17.1",
40
+ "null-prototype-object": "^1.2.7",
41
+ "picomatch": "^4.0.4",
42
+ "refui": "^0.17.2",
43
43
  "refurbish": "^0.1.8",
44
44
  "rehype-slug": "^6.0.0",
45
45
  "rehype-starry-night": "^2.2.0",
46
46
  "remark-gfm": "^4.0.1",
47
47
  "unist-util-visit": "^5.1.0",
48
- "vite": "^7.3.1",
48
+ "vite": "^8.0.10",
49
49
  "workbox-core": "^7.4.0",
50
50
  "workbox-routing": "^7.4.0",
51
51
  "workbox-strategies": "^7.4.0",
package/src/client/sw.js CHANGED
@@ -25,8 +25,6 @@ const MANIFEST_HASH = '__METHANOL_MANIFEST_HASH__'
25
25
  const DEFAULT_BATCH_SIZE = 5
26
26
  const REVISION_HEADER = 'X-Methanol-Revision'
27
27
 
28
- self.skipWaiting()
29
-
30
28
  const resolveBasePrefix = cached(() => normalizeBasePrefix(import.meta.env?.BASE_URL || '/'))
31
29
 
32
30
  const withBase = cachedStr((path) => {
@@ -126,6 +124,7 @@ async function getManifestIndex() {
126
124
 
127
125
  // Precache prioritized entries during install
128
126
  self.addEventListener('install', (event) => {
127
+ self.skipWaiting()
129
128
  event.waitUntil(
130
129
  (async () => {
131
130
  try {
package/src/dev-server.js CHANGED
@@ -532,16 +532,18 @@ export const runViteDev = async () => {
532
532
 
533
533
  let html = ''
534
534
  try {
535
- html = await renderHtml({
536
- routePath: renderRoutePath,
537
- path,
538
- components: {
539
- ...themeComponents,
540
- ...components
541
- },
542
- pagesContext,
543
- pageMeta
544
- })
535
+ html = await enqueueRender(() =>
536
+ renderHtml({
537
+ routePath: renderRoutePath,
538
+ path,
539
+ components: {
540
+ ...themeComponents,
541
+ ...components
542
+ },
543
+ pagesContext,
544
+ pageMeta
545
+ })
546
+ )
545
547
  } catch (err) {
546
548
  logMdxError('MDX render', err, pageMeta || { path, routePath: renderRoutePath })
547
549
  await sendDevError(res, err, req.url)
@@ -581,6 +583,13 @@ export const runViteDev = async () => {
581
583
  return queue
582
584
  }
583
585
 
586
+ let renderQueue = Promise.resolve()
587
+ const enqueueRender = (task) => {
588
+ const next = renderQueue.then(task, task)
589
+ renderQueue = next.catch(() => {})
590
+ return next
591
+ }
592
+
584
593
  const reload = () => {
585
594
  server.ws.send({ type: 'full-reload' })
586
595
  }
package/src/feed.js CHANGED
@@ -111,7 +111,7 @@ export const selectFeedPages = (pages, options) => {
111
111
  const limit = resolveFeedLimit(options)
112
112
  if (!limit) return []
113
113
  return (Array.isArray(pages) ? pages : [])
114
- .filter((page) => page && !page.hidden && !page.hiddenByParent)
114
+ .filter((page) => page && !page.hidden && !page.hiddenByParents)
115
115
  .sort((a, b) => getSortTime(b) - getSortTime(a))
116
116
  .slice(0, limit)
117
117
  }
@@ -44,7 +44,7 @@ const sanitizePage = (page) => {
44
44
 
45
45
  export const serializePagesIndex = (pages) => {
46
46
  const list = Array.isArray(pages)
47
- ? pages.filter((page) => !page?.hidden).map(sanitizePage)
47
+ ? pages.filter((page) => !page.hidden && !page.hiddenByParents).map(sanitizePage)
48
48
  : []
49
49
  return JSON.stringify(JSON.stringify(list))
50
50
  }
package/src/pages.js CHANGED
@@ -526,7 +526,7 @@ export const buildPageEntry = async ({ path, pagesDir, source }) => {
526
526
  const hidden =
527
527
  frontmatterHidden === false
528
528
  ? false
529
- : frontmatterHidden === true
529
+ : hiddenByFrontmatter
530
530
  ? true
531
531
  : isSpecialPage
532
532
  ? true
@@ -695,37 +695,48 @@ const buildNavSequence = (nodes, pagesByRoute) => {
695
695
 
696
696
  export const createPagesContextFromPages = ({ pages, excludedRoutes, excludedDirs } = {}) => {
697
697
  const pageList = Array.isArray(pages) ? pages : []
698
- const hiddenPrefixes = []
698
+ const pagesAll = pageList
699
+ const pagesByRoute = new Map()
699
700
  for (const page of pageList) {
700
- if (page.isIndex && page.hidden) {
701
- const prefix = page.routePath.endsWith('/') ? page.routePath : `${page.routePath}/`
702
- if (prefix !== '/') {
703
- hiddenPrefixes.push(prefix)
704
- }
701
+ if (!pagesByRoute.has(page.routePath)) {
702
+ pagesByRoute.set(page.routePath, page)
705
703
  }
706
704
  }
707
- for (const page of pageList) {
708
- page.hiddenByParent = false
709
- for (const prefix of hiddenPrefixes) {
710
- if (page.routePath.startsWith(prefix) && page.routePath !== prefix) {
711
- page.hiddenByParent = true
712
- break
705
+ const resolveParentDirRoute = (routePath) => {
706
+ const normalized = normalizeRoutePath(routePath || '/')
707
+ if (normalized === '/') return null
708
+ if (normalized.endsWith('/')) {
709
+ const stripped = stripTrailingSlash(normalized)
710
+ const index = stripped.lastIndexOf('/')
711
+ if (index <= 0) return '/'
712
+ return `${stripped.slice(0, index)}/`
713
+ }
714
+ const index = normalized.lastIndexOf('/')
715
+ if (index <= 0) return '/'
716
+ return `${normalized.slice(0, index)}/`
717
+ }
718
+ const resolveHiddenAncestor = (routePath) => {
719
+ let cursor = resolveParentDirRoute(routePath)
720
+ while (cursor && cursor !== '/') {
721
+ const page = pagesByRoute.get(cursor)
722
+ if (page?.isIndex && page?.hidden) {
723
+ return cursor
713
724
  }
725
+ cursor = resolveParentDirRoute(cursor)
714
726
  }
727
+ return null
728
+ }
729
+ for (const page of pageList) {
730
+ const nearestHidden = resolveHiddenAncestor(page.routePath)
731
+ page.hiddenByParent = nearestHidden
732
+ page.hiddenByParents = Boolean(nearestHidden)
715
733
  }
716
- const pagesAll = pageList
717
734
  const isSpecialPage = (page) => page?.routePath === '/404' || page?.routePath === '/offline'
718
735
  const listForNavigation = pageList.filter(
719
736
  (page) => !(isSpecialPage(page) && page.hidden !== false)
720
737
  )
721
738
  const routeExcludes = excludedRoutes || new Set()
722
739
  const dirExcludes = excludedDirs || new Set()
723
- const pagesByRoute = new Map()
724
- for (const page of pageList) {
725
- if (!pagesByRoute.has(page.routePath)) {
726
- pagesByRoute.set(page.routePath, page)
727
- }
728
- }
729
740
  const getPageByRoute = (routePath, options = {}) => {
730
741
  const { path } = options || {}
731
742
  if (path) {
@@ -869,10 +880,31 @@ export const createPagesContextFromPages = ({ pages, excludedRoutes, excludedDir
869
880
  index = sequence.findIndex((entry) => entry.routePath === routePath)
870
881
  }
871
882
  if (index < 0) return { prev: null, next: null }
883
+ const normalizedRoutePath = normalizeRoutePath(routePath)
884
+ const isUnderRoute = (routeValue, baseValue) => {
885
+ if (!routeValue || !baseValue) return false
886
+ const route = normalizeRoutePath(routeValue)
887
+ const base = normalizeRoutePath(baseValue)
888
+ if (stripTrailingSlash(route) === stripTrailingSlash(base)) return true
889
+ const basePrefix = base.endsWith('/') ? base : `${base}/`
890
+ return route.startsWith(basePrefix)
891
+ }
872
892
  const isVisible = (entry) => {
873
893
  if (!entry) return false
874
- if (entry.isRoot === true && entry.hidden !== false) return false
875
- return !entry.hidden
894
+ if (entry.isRoot) {
895
+ if (!entry.hidden || routePath.startsWith(entry.routePath)) {
896
+ return true
897
+ }
898
+ return false
899
+ }
900
+ if (entry.hidden) {
901
+ return isUnderRoute(normalizedRoutePath, entry.routePath)
902
+ }
903
+ const entryHiddenRoot = entry.hiddenByParent
904
+ if (entryHiddenRoot && !isUnderRoute(normalizedRoutePath, entryHiddenRoot)) {
905
+ return false
906
+ }
907
+ return true
876
908
  }
877
909
  let prevIndex = index - 1
878
910
  while (prevIndex >= 0 && !isVisible(sequence[prevIndex])) {
@@ -145,8 +145,7 @@ const virtualModuleMap = {
145
145
  },
146
146
  get pages() {
147
147
  const pages = state.PAGES_CONTEXT?.pages || []
148
- const filtered = pages.filter((p) => !p.hiddenByParent)
149
- return `export const pages = ${serializePagesIndex(filtered)}\nexport default pages`
148
+ return `export const pages = JSON.parse(${serializePagesIndex(pages)})\nexport default pages`
150
149
  }
151
150
  }
152
151
 
@@ -360,7 +360,7 @@ const handleRewrite = async (message) => {
360
360
  }
361
361
  }
362
362
 
363
- parentPort?.on('message', async (message) => {
363
+ const handleMessage = async (message) => {
364
364
  const { type, stage } = message || {}
365
365
  try {
366
366
  await ensureInit()
@@ -397,4 +397,13 @@ parentPort?.on('message', async (message) => {
397
397
  } catch (error) {
398
398
  parentPort?.postMessage({ type: 'error', stage, error: serializeError(error) })
399
399
  }
400
+ }
401
+
402
+ let messageQueue = Promise.resolve()
403
+ parentPort?.on('message', (message) => {
404
+ messageQueue = messageQueue
405
+ .then(() => handleMessage(message))
406
+ .catch((error) => {
407
+ parentPort?.postMessage({ type: 'error', stage: message?.stage, error: serializeError(error) })
408
+ })
400
409
  })
@@ -5,31 +5,51 @@ html {
5
5
  }
6
6
 
7
7
  :root {
8
- --bg: #ffffff;
9
- --text: #111827;
10
- --text-muted: #6b7280;
11
- --primary: #2563eb;
12
- --primary-soft: #eff6ff;
13
- --border: #f3f4f6;
14
- --bg-soft: #f9fafb;
15
- --selection-bg: #dbeafe;
8
+ color-scheme: light dark;
9
+ --bg: light-dark(#ffffff, #030712);
10
+ --text: light-dark(#111827, #f9fafb);
11
+ --text-muted: light-dark(#6b7280, #9ca3af);
12
+ --primary: light-dark(#2563eb, #60a5fa);
13
+ --primary-soft: light-dark(#eff6ff, #1e3a8a33);
14
+ --border: light-dark(#f3f4f6, #1f2937);
15
+ --bg-soft: light-dark(#f9fafb, #111827);
16
+ --selection-bg: light-dark(#dbeafe, #1e3a8a);
16
17
 
17
18
  --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
18
19
  --max-width: 820px;
19
20
  --container-px: 1.5rem;
20
- }
21
21
 
22
- @media (prefers-color-scheme: dark) {
23
- :root {
24
- --bg: #030712;
25
- --text: #f9fafb;
26
- --text-muted: #9ca3af;
27
- --primary: #60a5fa;
28
- --primary-soft: #1e3a8a33;
29
- --border: #1f2937;
30
- --bg-soft: #111827;
31
- --selection-bg: #1e3a8a;
32
- }
22
+ /* Starry Night Syntax Highlighting */
23
+ --color-prettylights-syntax-brackethighlighter-angle: light-dark(#59636e, #9198a1);
24
+ --color-prettylights-syntax-brackethighlighter-unmatched: light-dark(#82071e, #f85149);
25
+ --color-prettylights-syntax-carriage-return-bg: light-dark(#cf222e, #b62324);
26
+ --color-prettylights-syntax-carriage-return-text: light-dark(#f6f8fa, #f0f6fc);
27
+ --color-prettylights-syntax-comment: light-dark(#59636e, #9198a1);
28
+ --color-prettylights-syntax-constant: light-dark(#0550ae, #79c0ff);
29
+ --color-prettylights-syntax-constant-other-reference-link: light-dark(#0a3069, #a5d6ff);
30
+ --color-prettylights-syntax-entity: light-dark(#6639ba, #d2a8ff);
31
+ --color-prettylights-syntax-entity-tag: light-dark(#0550ae, #7ee787);
32
+ --color-prettylights-syntax-invalid-illegal-bg: light-dark(#82071e, #8e1519);
33
+ --color-prettylights-syntax-invalid-illegal-text: light-dark(#f6f8fa, #f0f6fc);
34
+ --color-prettylights-syntax-keyword: light-dark(#cf222e, #ff7b72);
35
+ --color-prettylights-syntax-markup-changed-bg: light-dark(#ffd8b5, #5a1e02);
36
+ --color-prettylights-syntax-markup-changed-text: light-dark(#953800, #ffdfb6);
37
+ --color-prettylights-syntax-markup-deleted-bg: light-dark(#ffebe9, #67060c);
38
+ --color-prettylights-syntax-markup-deleted-text: light-dark(#82071e, #ffdcd7);
39
+ --color-prettylights-syntax-markup-heading: light-dark(#0550ae, #1f6feb);
40
+ --color-prettylights-syntax-markup-ignored-bg: light-dark(#0550ae, #1158c7);
41
+ --color-prettylights-syntax-markup-ignored-text: light-dark(#d1d9e0, #f0f6fc);
42
+ --color-prettylights-syntax-markup-inserted-bg: light-dark(#dafbe1, #033a16);
43
+ --color-prettylights-syntax-markup-inserted-text: light-dark(#116329, #aff5b4);
44
+ --color-prettylights-syntax-markup-list: light-dark(#3b2300, #f2cc60);
45
+ --color-prettylights-syntax-meta-diff-range: light-dark(#8250df, #d2a8ff);
46
+ --color-prettylights-syntax-string: light-dark(#0a3069, #a5d6ff);
47
+ --color-prettylights-syntax-string-regexp: light-dark(#116329, #7ee787);
48
+ --color-prettylights-syntax-sublimelinter-gutter-mark: light-dark(#818b98, #3d444d);
49
+ --color-prettylights-syntax-variable: light-dark(#953800, #ffa657);
50
+ --color-prettylights-syntax-markup-bold: light-dark(#1f2328, #f0f6fc);
51
+ --color-prettylights-syntax-markup-italic: light-dark(#1f2328, #f0f6fc);
52
+ --color-prettylights-syntax-storage-modifier-import: light-dark(#1f2328, #f0f6fc);
33
53
  }
34
54
 
35
55
  *, *::before, *::after {
@@ -21,7 +21,7 @@
21
21
  import { extractExcerpt } from 'methanol'
22
22
 
23
23
  const isBlogPost = (page) => {
24
- if (!page || page.hidden || page.hiddenByParent) return false
24
+ if (!page || page.hidden || page.hiddenByParents) return false
25
25
  const href = page.routeHref
26
26
  if (!href) return false
27
27
  if (href === '/' || href === '/404' || href === '/offline') return false
@@ -18,7 +18,7 @@
18
18
  * under the License.
19
19
  */
20
20
 
21
- import { If, For, $, signal, onCondition, read, extract } from 'refui'
21
+ import { If, For, $, signal, onCondition, read, extract, nextTick } from 'refui'
22
22
  import NullProtoObj from 'null-prototype-object'
23
23
  import { HTMLRenderer } from 'methanol'
24
24
 
@@ -50,21 +50,20 @@ const NavTree = ({ nodes, depth }) => {
50
50
  <For entries={nodes}>
51
51
  {({ item }) => {
52
52
  const node = read(item)
53
- const { routeHref: href, routePath, type, name, isRoot } = node
54
- const { title, hidden } = extract(item, 'title', 'hidden')
53
+ const { routeHref: href, routePath, type, name, isRoot, hidden } = node
54
+ const { title } = extract(item, 'title')
55
55
 
56
56
  const isActive = matchCurrentPath(routePath)
57
57
  let show = isActive
58
- if (type === 'directory') {
58
+ if (isRoot || type === 'directory') {
59
59
  show = $(() => {
60
- const _active = isActive.value
61
60
  const _currentPath = currentPath.value
62
- return _active || _currentPath.startsWith(routePath)
61
+ return _currentPath.startsWith(routePath)
63
62
  })
64
63
  }
65
64
 
66
65
  return (
67
- <If condition={hidden.inverseOr(show)}>
66
+ <If condition={!hidden || show}>
68
67
  {() => {
69
68
  if (type === 'directory') {
70
69
  const label = title.or(name)
@@ -114,8 +113,9 @@ const _rootNodes = signal()
114
113
  const rootNodes = signal(_rootNodes, (nodes) => nodes?.map(toSignal))
115
114
  const rootTree = HTMLRenderer.createElement(NavTree, { nodes: rootNodes, depth: 0 })
116
115
 
117
- export const renderNavTree = (nodes, path) => {
116
+ export const renderNavTree = async (nodes, path) => {
118
117
  currentPath.value = path
119
118
  _rootNodes.value = nodes
119
+ await nextTick()
120
120
  return rootTree
121
121
  }
@@ -22,7 +22,7 @@ import { DOCTYPE_HTML } from 'methanol'
22
22
  import { renderToc } from '../components/ThemeToCContainer.static.jsx'
23
23
  import { renderNavTree } from './nav-tree.jsx'
24
24
 
25
- const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx }) => {
25
+ const PAGE_TEMPLATE = async ({ PageContent, ExtraHead, components, ctx }) => {
26
26
  const page = ctx.page
27
27
  const pagesByRoute = ctx.pagesByRoute
28
28
  const pages = ctx.pages || []
@@ -204,7 +204,7 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx }) => {
204
204
  {pagefindEnabled ? <ThemeSearchBox options={pagefindOptions} /> : null}
205
205
  </div>
206
206
  <nav>
207
- <ul data-depth="0">{renderNavTree(pagesTree, page.routePath)}</ul>
207
+ <ul data-depth="0">{await renderNavTree(pagesTree, page.routePath)}</ul>
208
208
  </nav>
209
209
  <div class="sidebar-footer">
210
210
  {languageSelector}