methanol 0.0.13 → 0.0.15

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.
Files changed (39) hide show
  1. package/index.js +1 -0
  2. package/package.json +1 -1
  3. package/src/build-system.js +1 -0
  4. package/src/config.js +33 -3
  5. package/src/dev-server.js +21 -13
  6. package/src/pages-index.js +42 -0
  7. package/src/pages.js +1 -0
  8. package/src/reframe.js +1 -1
  9. package/src/state.js +8 -0
  10. package/src/text-utils.js +60 -0
  11. package/src/vite-plugins.js +9 -0
  12. package/src/workers/build-pool.js +1 -0
  13. package/themes/blog/README.md +26 -0
  14. package/themes/blog/components/CategoryView.client.jsx +164 -0
  15. package/themes/blog/components/CategoryView.static.jsx +35 -0
  16. package/themes/blog/components/CollectionView.client.jsx +151 -0
  17. package/themes/blog/components/CollectionView.static.jsx +37 -0
  18. package/themes/blog/components/PostList.client.jsx +92 -0
  19. package/themes/blog/components/PostList.static.jsx +36 -0
  20. package/themes/blog/components/ThemeSearchBox.client.jsx +427 -0
  21. package/themes/blog/components/ThemeSearchBox.static.jsx +40 -0
  22. package/themes/blog/index.js +40 -0
  23. package/themes/blog/pages/404.mdx +12 -0
  24. package/themes/blog/pages/about.mdx +14 -0
  25. package/themes/blog/pages/categories.mdx +6 -0
  26. package/themes/blog/pages/collections.mdx +6 -0
  27. package/themes/blog/pages/index.mdx +16 -0
  28. package/themes/blog/pages/offline.mdx +11 -0
  29. package/themes/blog/sources/style.css +579 -0
  30. package/themes/blog/src/date-utils.js +28 -0
  31. package/themes/blog/src/heading.jsx +37 -0
  32. package/themes/blog/src/layout-categories.jsx +66 -0
  33. package/themes/blog/src/layout-collections.jsx +65 -0
  34. package/themes/blog/src/layout-home.jsx +66 -0
  35. package/themes/blog/src/layout-post.jsx +42 -0
  36. package/themes/blog/src/page.jsx +152 -0
  37. package/themes/blog/src/post-utils.js +83 -0
  38. package/themes/default/sources/theme-prepare.js +0 -1
  39. package/themes/default/src/page.jsx +1 -1
@@ -0,0 +1,65 @@
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 { HTMLRenderer as R } from 'methanol'
22
+ import { filterBlogPosts, mapStaticPosts, collectCollectionTitles } from './post-utils.js'
23
+ import { formatDate } from '../src/date-utils.js'
24
+
25
+ const renderPostCards = (posts = [], collectionTitles = {}) =>
26
+ posts.map((p) => {
27
+ const dateStr = formatDate(p.frontmatter?.date || p.stats?.createdAt)
28
+ const collectionLabel = p.collection ? collectionTitles?.[p.collection] || p.collection : ''
29
+ return (
30
+ <article class="post-item">
31
+ <div class="post-meta">
32
+ {dateStr && <span class="post-date">{dateStr}</span>}
33
+ {collectionLabel && <span class="post-categories"> &middot; {collectionLabel}</span>}
34
+ </div>
35
+ <h2 class="post-item-title">
36
+ <a href={p.routeHref}>{p.title || 'Untitled'}</a>
37
+ </h2>
38
+ <div class="post-excerpt">{p.excerpt || p.frontmatter?.excerpt || 'No excerpt available.'}</div>
39
+ </article>
40
+ )
41
+ })
42
+
43
+ export const LayoutCollections = ({ PageContent, title, pages, pagesByRoute, navLinks, components }) => {
44
+ const { CollectionView } = components || {}
45
+ const filteredPosts = filterBlogPosts(pages, navLinks)
46
+ const staticPosts = mapStaticPosts(filteredPosts)
47
+ const collectionTitles = collectCollectionTitles(staticPosts, pagesByRoute)
48
+ const visiblePosts = staticPosts.slice(0, 10)
49
+ const staticCards = renderPostCards(visiblePosts, collectionTitles)
50
+ return (
51
+ <div class="categories-container">
52
+ <header class="post-header">
53
+ <h1 class="post-title">{title}</h1>
54
+ </header>
55
+ <div class="categories-content">
56
+ <PageContent />
57
+ {CollectionView ? (
58
+ <CollectionView collectionTitles={collectionTitles}>{...staticCards}</CollectionView>
59
+ ) : (
60
+ <p>Error: CollectionView component not found.</p>
61
+ )}
62
+ </div>
63
+ </div>
64
+ )
65
+ }
@@ -0,0 +1,66 @@
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 { HTMLRenderer as R } from 'methanol'
22
+ import { filterBlogPosts, mapStaticPosts } from './post-utils.js'
23
+ import { formatDate } from '../src/date-utils.js'
24
+
25
+ const renderPostCards = (posts = []) =>
26
+ posts.map((p) => {
27
+ const dateStr = formatDate(p.frontmatter?.date || p.stats?.createdAt)
28
+ return (
29
+ <article class="post-item">
30
+ <div class="post-meta">{dateStr && <span class="post-date">{dateStr}</span>}</div>
31
+ <h2 class="post-item-title">
32
+ <a href={p.routeHref}>{p.title || 'Untitled'}</a>
33
+ </h2>
34
+ <div class="post-excerpt">{p.excerpt || p.frontmatter?.excerpt || 'No excerpt available.'}</div>
35
+ </article>
36
+ )
37
+ })
38
+
39
+ export const LayoutHome = ({ PageContent, pages, navLinks, components }) => {
40
+ const { PostList } = components || {}
41
+ const filteredPosts = filterBlogPosts(pages, navLinks)
42
+ const staticPosts = mapStaticPosts(filteredPosts)
43
+ const visiblePosts = staticPosts.slice(0, 10)
44
+ const hasMore = staticPosts.length > visiblePosts.length
45
+ const staticCards = renderPostCards(visiblePosts)
46
+ return (
47
+ <div class="home-container">
48
+ <div class="home-intro">
49
+ <PageContent />
50
+ </div>
51
+ <div class="post-list-wrapper">
52
+ {staticPosts.length > 0 ? (
53
+ PostList ? (
54
+ <PostList initialCount={10} hasMore={hasMore}>
55
+ {...staticCards}
56
+ </PostList>
57
+ ) : (
58
+ <p>Error: PostList component not found.</p>
59
+ )
60
+ ) : (
61
+ <p>No posts found.</p>
62
+ )}
63
+ </div>
64
+ </div>
65
+ )
66
+ }
@@ -0,0 +1,42 @@
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 { HTMLRenderer as R } from 'methanol'
22
+
23
+ export const LayoutPost = ({ PageContent, title, page }) => {
24
+ return (
25
+ <article class="post-content">
26
+ <header class="post-header">
27
+ {page.frontmatter.date && <div class="post-meta">{new Date(page.frontmatter.date).toLocaleDateString()}</div>}
28
+ <h1 class="post-title">{title}</h1>
29
+ {page.frontmatter.categories && (
30
+ <div class="post-meta">
31
+ {Array.isArray(page.frontmatter.categories)
32
+ ? page.frontmatter.categories.join(', ')
33
+ : page.frontmatter.categories}
34
+ </div>
35
+ )}
36
+ </header>
37
+ <div class="post-body">
38
+ <PageContent />
39
+ </div>
40
+ </article>
41
+ )
42
+ }
@@ -0,0 +1,152 @@
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 { HTMLRenderer as R, DOCTYPE_HTML } from 'methanol'
22
+ import { LayoutHome } from './layout-home.jsx'
23
+ import { LayoutCategories } from './layout-categories.jsx'
24
+ import { LayoutCollections } from './layout-collections.jsx'
25
+ import { LayoutPost } from './layout-post.jsx'
26
+
27
+ const Header = ({ siteName, navLinks, components, pagefindOptions }) => {
28
+ const { ThemeSearchBox } = components || {}
29
+ return (
30
+ <header class="blog-header">
31
+ <div class="container header-container">
32
+ <a href="/" class="blog-logo">
33
+ {siteName}
34
+ </a>
35
+ <div class="header-actions">
36
+ {ThemeSearchBox && <ThemeSearchBox options={pagefindOptions} />}
37
+
38
+ <input type="checkbox" id="nav-toggle" class="nav-toggle" />
39
+ <label for="nav-toggle" class="nav-toggle-label">
40
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
41
+ <line x1="3" y1="12" x2="21" y2="12"></line>
42
+ <line x1="3" y1="6" x2="21" y2="6"></line>
43
+ <line x1="3" y1="18" x2="21" y2="18"></line>
44
+ </svg>
45
+ </label>
46
+
47
+ <nav class="blog-nav">
48
+ {navLinks.map((link, i) => (
49
+ <a key={i} href={link.href}>
50
+ {link.label}
51
+ </a>
52
+ ))}
53
+ </nav>
54
+ </div>
55
+ </div>
56
+ </header>
57
+ )
58
+ }
59
+
60
+ const Footer = ({ siteName }) => (
61
+ <footer class="blog-footer">
62
+ <div class="container">
63
+ <p>
64
+ &copy; {new Date().getFullYear()} {siteName}. Powered by <a href="https://methanol.sudoMaker.com">Methanol</a>.
65
+ </p>
66
+ </div>
67
+ </footer>
68
+ )
69
+
70
+ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx, withBase }) => {
71
+ const page = ctx.page
72
+ const siteName = ctx.site.name || 'Methanol Blog'
73
+ const title = page.title || siteName
74
+
75
+ const isHome = page.routePath === '/'
76
+
77
+ const navLinks = page.frontmatter?.navLinks ||
78
+ ctx.pagesByRoute.get('/')?.frontmatter?.navLinks ||
79
+ ctx.site.navLinks || [
80
+ { label: 'Home', href: '/' },
81
+ { label: 'About', href: '/about' },
82
+ { label: 'Categories', href: '/categories' },
83
+ { label: 'Collections', href: '/collections' }
84
+ ]
85
+ const resolvedNavLinks = navLinks.map((link) => {
86
+ const href = link?.href
87
+ if (typeof href === 'string' && href.startsWith('/')) {
88
+ return { ...link, href: withBase(href) }
89
+ }
90
+ return link
91
+ })
92
+
93
+ const isCategoriesPage = page.routePath === '/categories'
94
+ const isCollectionsPage = page.routePath === '/collections'
95
+
96
+ const pagefindEnabled = ctx.site.pagefind?.enabled !== false
97
+ const pagefindOptions = ctx.site.pagefind?.options || null
98
+
99
+ return (
100
+ <>
101
+ {DOCTYPE_HTML}
102
+ <html lang="en">
103
+ <head>
104
+ <meta charset="UTF-8" />
105
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
106
+ <title>
107
+ {title} | {siteName}
108
+ </title>
109
+ <link rel="stylesheet" href="/.methanol_theme_blog/style.css" />
110
+ <ExtraHead />
111
+ </head>
112
+ <body>
113
+ <div class="layout">
114
+ <Header
115
+ siteName={siteName}
116
+ navLinks={resolvedNavLinks}
117
+ components={pagefindEnabled ? components : {}}
118
+ pagefindOptions={pagefindOptions}
119
+ />
120
+ <main class="container main-content">
121
+ {isHome ? (
122
+ <LayoutHome PageContent={PageContent} pages={ctx.pages} navLinks={navLinks} components={components} />
123
+ ) : isCategoriesPage ? (
124
+ <LayoutCategories
125
+ PageContent={PageContent}
126
+ title={title}
127
+ pages={ctx.pages}
128
+ navLinks={navLinks}
129
+ components={components}
130
+ />
131
+ ) : isCollectionsPage ? (
132
+ <LayoutCollections
133
+ PageContent={PageContent}
134
+ title={title}
135
+ pages={ctx.pages}
136
+ pagesByRoute={ctx.pagesByRoute}
137
+ navLinks={navLinks}
138
+ components={components}
139
+ />
140
+ ) : (
141
+ <LayoutPost PageContent={PageContent} title={title} page={page} />
142
+ )}
143
+ </main>
144
+ <Footer siteName={siteName} />
145
+ </div>
146
+ </body>
147
+ </html>
148
+ </>
149
+ )
150
+ }
151
+
152
+ export default PAGE_TEMPLATE
@@ -0,0 +1,83 @@
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 { extractExcerpt } from 'methanol'
22
+
23
+ const isBlogPost = (page) => {
24
+ if (!page || page.hidden) return false
25
+ const href = page.routeHref
26
+ if (!href) return false
27
+ if (href === '/' || href === '/404' || href === '/offline') return false
28
+ if (href.startsWith('/.methanol')) return false
29
+ return true
30
+ }
31
+
32
+ export const filterBlogPosts = (pages, navLinks = []) => {
33
+ const excluded = new Set((navLinks || []).map((link) => link?.href).filter(Boolean))
34
+ const list = (pages || []).filter((page) => isBlogPost(page) && !excluded.has(page.routeHref))
35
+ list.sort((a, b) => {
36
+ const dateA = new Date(a.frontmatter?.date || a.stats?.createdAt || 0)
37
+ const dateB = new Date(b.frontmatter?.date || b.stats?.createdAt || 0)
38
+ return dateB - dateA
39
+ })
40
+ return list
41
+ }
42
+
43
+ export const getExcerpt = (page) => extractExcerpt(page)
44
+
45
+ export const getCollection = (page) => {
46
+ if (!page?.dir) return null
47
+ return page.dir.split('/')[0]
48
+ }
49
+
50
+ export const mapStaticPosts = (posts = []) =>
51
+ posts.map((page) => ({
52
+ title: page.title,
53
+ routeHref: page.routeHref,
54
+ frontmatter: page.frontmatter || {},
55
+ stats: page.stats || {},
56
+ excerpt: getExcerpt(page),
57
+ collection: getCollection(page)
58
+ }))
59
+
60
+ export const collectCategories = (posts = []) =>
61
+ Array.from(
62
+ new Set(
63
+ posts.flatMap((page) => {
64
+ const c = page.frontmatter?.categories
65
+ return Array.isArray(c) ? c : c ? [c] : []
66
+ })
67
+ )
68
+ ).sort()
69
+
70
+ export const collectCollectionTitles = (posts = [], pagesByRoute = new Map()) => {
71
+ const collectionTitles = {}
72
+ for (const collection of new Set(posts.map((p) => p.collection).filter(Boolean))) {
73
+ const routePath = `/${collection}/`
74
+ const entry = pagesByRoute?.get?.(routePath) || pagesByRoute?.get?.(`/${collection}`) || null
75
+ collectionTitles[collection] = entry?.title || collection
76
+ }
77
+ for (const key of Object.keys(collectionTitles).sort()) {
78
+ if (!collectionTitles[key]) {
79
+ collectionTitles[key] = key
80
+ }
81
+ }
82
+ return collectionTitles
83
+ }
@@ -59,4 +59,3 @@
59
59
  }
60
60
  document.addEventListener('pointerover', onHover, { capture: true, passive: true })
61
61
  })
62
- console.log('theme prep')
@@ -254,7 +254,7 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx }) => {
254
254
  <div class="page-meta-item">
255
255
  Powered by{' '}
256
256
  <a
257
- href="https://github.com/SudoMaker/Methanol"
257
+ href="https://methanol.sudoMaker.com"
258
258
  target="_blank"
259
259
  rel="noopener noreferrer"
260
260
  class="methanol-link"