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
|
@@ -0,0 +1,37 @@
|
|
|
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 LinkButton({
|
|
22
|
+
children,
|
|
23
|
+
variant = 'primary', // primary, secondary, outline, ghost
|
|
24
|
+
size = 'md', // sm, md, lg
|
|
25
|
+
class: className = '',
|
|
26
|
+
...props
|
|
27
|
+
}) {
|
|
28
|
+
const classes = ['link-button', `link-button--${variant}`, `link-button--${size}`, className]
|
|
29
|
+
.filter(Boolean)
|
|
30
|
+
.join(' ')
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<a class={classes} {...props}>
|
|
34
|
+
{children}
|
|
35
|
+
</a>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -20,7 +20,13 @@
|
|
|
20
20
|
|
|
21
21
|
import { signal, $, t, If, For, onCondition } from 'refui'
|
|
22
22
|
import { createPortal } from 'refui/extras'
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
let pagefindModule = null
|
|
25
|
+
const loadPagefindModule = async () => {
|
|
26
|
+
if (pagefindModule) return pagefindModule
|
|
27
|
+
pagefindModule = import('methanol:pagefind-loader')
|
|
28
|
+
return pagefindModule
|
|
29
|
+
}
|
|
24
30
|
|
|
25
31
|
let keybindReady = false
|
|
26
32
|
let cachedPagefind = null
|
|
@@ -35,7 +41,8 @@ const resolveShortcutLabel = () => {
|
|
|
35
41
|
|
|
36
42
|
const ensurePagefind = async (options) => {
|
|
37
43
|
if (cachedPagefind) return cachedPagefind
|
|
38
|
-
const
|
|
44
|
+
const module = await loadPagefindModule()
|
|
45
|
+
const pagefind = await module?.loadPagefind?.()
|
|
39
46
|
if (!pagefind) return null
|
|
40
47
|
if (pagefind.options) {
|
|
41
48
|
const nextOptions = { excerptLength: 30, ...(options || {}) }
|
package/themes/default/page.jsx
CHANGED
|
@@ -27,11 +27,11 @@ const renderPageTree = (nodes = [], currentRoute, depth = 0) => {
|
|
|
27
27
|
const items = []
|
|
28
28
|
let hasActive = false
|
|
29
29
|
for (const node of nodes) {
|
|
30
|
-
const nodeRoute = node.routeHref ||
|
|
30
|
+
const nodeRoute = node.routeHref || ''
|
|
31
31
|
if (node.type === 'directory') {
|
|
32
32
|
const label = node.title || node.name
|
|
33
33
|
const isActive = nodeRoute === currentRoute
|
|
34
|
-
const href = node.
|
|
34
|
+
const href = node.routeHref
|
|
35
35
|
const childResult = renderPageTree(node.children || [], currentRoute, depth + 1)
|
|
36
36
|
const isOpen = depth < 1 || isActive || childResult.hasActive
|
|
37
37
|
if (isOpen) hasActive = true
|
|
@@ -55,7 +55,7 @@ const renderPageTree = (nodes = [], currentRoute, depth = 0) => {
|
|
|
55
55
|
const label = node.title || (node.isIndex ? 'Home' : node.name)
|
|
56
56
|
const isActive = nodeRoute === currentRoute
|
|
57
57
|
if (isActive) hasActive = true
|
|
58
|
-
const href =
|
|
58
|
+
const href = node.routeHref
|
|
59
59
|
items.push(
|
|
60
60
|
<li>
|
|
61
61
|
<a class={isActive ? 'active' : null} href={href}>
|
|
@@ -67,45 +67,49 @@ const renderPageTree = (nodes = [], currentRoute, depth = 0) => {
|
|
|
67
67
|
return { items, hasActive }
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
const PAGE_TEMPLATE = ({
|
|
70
|
+
const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx }) => {
|
|
71
71
|
const page = ctx.page
|
|
72
72
|
const pagesByRoute = ctx.pagesByRoute
|
|
73
73
|
const pages = ctx.pages || []
|
|
74
74
|
const pagesTree = ctx.pagesTree || []
|
|
75
|
-
const siteName = ctx.site
|
|
76
|
-
const title = page
|
|
77
|
-
const currentRoute = page
|
|
78
|
-
const baseHref = page
|
|
79
|
-
const toc = page
|
|
75
|
+
const siteName = ctx.site.name || 'Methanol Site'
|
|
76
|
+
const title = page.title || siteName
|
|
77
|
+
const currentRoute = page.routeHref || ''
|
|
78
|
+
const baseHref = page.routeHref === '/404' ? ctx.site.base || '/' : null
|
|
79
|
+
const toc = page.toc?.length ? renderToc(page.toc) : null
|
|
80
80
|
const hasToc = Boolean(toc)
|
|
81
81
|
const layoutClass = hasToc ? 'layout-container' : 'layout-container no-toc'
|
|
82
82
|
const tree = renderPageTree(pagesTree, currentRoute, 0)
|
|
83
83
|
const { ThemeSearchBox, ThemeColorSwitch, ThemeAccentSwitch, ThemeToCContainer } = components
|
|
84
|
-
const rootPage = pagesByRoute
|
|
85
|
-
const pageFrontmatter = page
|
|
86
|
-
const rootFrontmatter = rootPage
|
|
84
|
+
const rootPage = pagesByRoute.get('/') || pages.find((entry) => entry.routeHref === '/')
|
|
85
|
+
const pageFrontmatter = page.frontmatter || {}
|
|
86
|
+
const rootFrontmatter = rootPage.frontmatter || {}
|
|
87
87
|
const themeLogo = '/logo.png'
|
|
88
88
|
const themeFavIcon = '/favicon.png'
|
|
89
|
-
const logo = pageFrontmatter.logo ?? rootFrontmatter.logo ?? ctx.site
|
|
90
|
-
const favicon = pageFrontmatter.favicon ?? rootFrontmatter.favicon ?? ctx.site
|
|
89
|
+
const logo = pageFrontmatter.logo ?? rootFrontmatter.logo ?? ctx.site.logo ?? themeLogo
|
|
90
|
+
const favicon = pageFrontmatter.favicon ?? rootFrontmatter.favicon ?? ctx.site.favicon ?? themeFavIcon
|
|
91
91
|
const excerpt = pageFrontmatter.excerpt ?? `${title} | ${siteName} - Powered by Methanol`
|
|
92
|
-
const
|
|
93
|
-
const
|
|
92
|
+
const _ogTitle = pageFrontmatter.ogTitle ?? title ?? null
|
|
93
|
+
const ogTitle = _ogTitle ? `${_ogTitle} | ${siteName}` : null
|
|
94
|
+
const ogDescription = pageFrontmatter.ogDescription ?? excerpt ?? null
|
|
94
95
|
const ogImage = pageFrontmatter.ogImage ?? null
|
|
95
96
|
const ogUrl = pageFrontmatter.ogUrl ?? null
|
|
96
97
|
const twitterTitle = pageFrontmatter.twitterTitle ?? ogTitle
|
|
97
98
|
const twitterDescription = pageFrontmatter.twitterDescription ?? ogDescription ?? excerpt
|
|
98
99
|
const twitterImage = pageFrontmatter.twitterImage ?? ogImage
|
|
99
100
|
const twitterCard = pageFrontmatter.twitterCard ?? (twitterImage ? 'summary_large_image' : null)
|
|
100
|
-
const siblings =
|
|
101
|
+
const siblings = page.getSiblings()
|
|
101
102
|
const prevPage = siblings?.prev || null
|
|
102
103
|
const nextPage = siblings?.next || null
|
|
103
104
|
const languages = Array.isArray(ctx.languages) ? ctx.languages : []
|
|
104
|
-
const currentLanguageHref = ctx.language?.href || ctx.language?.
|
|
105
|
+
const currentLanguageHref = ctx.language?.href || ctx.language?.routeHref || null
|
|
105
106
|
const languageCode = pageFrontmatter.langCode ?? rootFrontmatter.langCode ?? ctx.language?.code ?? 'en'
|
|
106
107
|
const htmlLang = typeof languageCode === 'string' && languageCode.trim() ? languageCode : 'en'
|
|
107
|
-
const pagefindEnabled = ctx.site
|
|
108
|
-
const pagefindOptions = ctx.site
|
|
108
|
+
const pagefindEnabled = ctx.site.pagefind?.enabled !== false
|
|
109
|
+
const pagefindOptions = ctx.site.pagefind?.options || null
|
|
110
|
+
const repoBase = ctx.site.repoBase
|
|
111
|
+
const sourceUrl = pageFrontmatter.sourceURL
|
|
112
|
+
const editUrl = sourceUrl || (repoBase && page.relativePath ? new URL(page.relativePath, repoBase).href : null)
|
|
109
113
|
const languageSelector = languages.length ? (
|
|
110
114
|
<div class="lang-switch-wrapper">
|
|
111
115
|
<select
|
|
@@ -115,7 +119,7 @@ const PAGE_TEMPLATE = ({ Page, ExtraHead, components, ctx }) => {
|
|
|
115
119
|
value={currentLanguageHref || undefined}
|
|
116
120
|
>
|
|
117
121
|
{languages.map((lang) => {
|
|
118
|
-
const optionValue = lang.href || lang.
|
|
122
|
+
const optionValue = lang.href || lang.routeHref
|
|
119
123
|
const isSelected = optionValue && optionValue === currentLanguageHref
|
|
120
124
|
return (
|
|
121
125
|
<option value={optionValue} selected={isSelected ? true : null}>
|
|
@@ -164,7 +168,11 @@ const PAGE_TEMPLATE = ({ Page, ExtraHead, components, ctx }) => {
|
|
|
164
168
|
{twitterDescription ? <meta name="twitter:description" content={twitterDescription} /> : null}
|
|
165
169
|
{twitterImage ? <meta name="twitter:image" content={twitterImage} /> : null}
|
|
166
170
|
<ExtraHead />
|
|
167
|
-
<link
|
|
171
|
+
<link
|
|
172
|
+
rel="preload stylesheet"
|
|
173
|
+
as="style"
|
|
174
|
+
href="/.methanol_theme_default/style.css"
|
|
175
|
+
/>
|
|
168
176
|
<script src="/theme-prepare.js"></script>
|
|
169
177
|
</head>
|
|
170
178
|
<body>
|
|
@@ -245,28 +253,49 @@ const PAGE_TEMPLATE = ({ Page, ExtraHead, components, ctx }) => {
|
|
|
245
253
|
</div>
|
|
246
254
|
</aside>
|
|
247
255
|
<main class="main-content" data-pagefind-body={pagefindEnabled ? '' : null}>
|
|
248
|
-
<
|
|
256
|
+
<PageContent />
|
|
249
257
|
{prevPage || nextPage ? (
|
|
250
258
|
<nav class="page-nav">
|
|
251
259
|
{prevPage ? (
|
|
252
|
-
<a
|
|
260
|
+
<a
|
|
261
|
+
class="page-nav-card prev"
|
|
262
|
+
href={prevPage.routeHref}
|
|
263
|
+
>
|
|
253
264
|
<span class="page-nav-label">Previous</span>
|
|
254
|
-
<span class="page-nav-title">{prevPage.title || prevPage.
|
|
265
|
+
<span class="page-nav-title">{prevPage.title || prevPage.routeHref}</span>
|
|
255
266
|
</a>
|
|
256
267
|
) : (
|
|
257
268
|
<div class="page-nav-spacer"></div>
|
|
258
269
|
)}
|
|
259
270
|
{nextPage ? (
|
|
260
|
-
<a
|
|
271
|
+
<a
|
|
272
|
+
class="page-nav-card next"
|
|
273
|
+
href={nextPage.routeHref}
|
|
274
|
+
>
|
|
261
275
|
<span class="page-nav-label">Next</span>
|
|
262
|
-
<span class="page-nav-title">{nextPage.title || nextPage.
|
|
276
|
+
<span class="page-nav-title">{nextPage.title || nextPage.routeHref}</span>
|
|
263
277
|
</a>
|
|
264
278
|
) : null}
|
|
265
279
|
</nav>
|
|
266
280
|
) : null}
|
|
267
281
|
{page ? (
|
|
268
282
|
<footer class="page-meta">
|
|
269
|
-
<div class="page-meta-item">
|
|
283
|
+
<div class="page-meta-item">
|
|
284
|
+
{editUrl ? (
|
|
285
|
+
<>
|
|
286
|
+
<a
|
|
287
|
+
href={editUrl}
|
|
288
|
+
target="_blank"
|
|
289
|
+
rel="noopener noreferrer"
|
|
290
|
+
class="page-meta-link"
|
|
291
|
+
>
|
|
292
|
+
Edit this page
|
|
293
|
+
</a>
|
|
294
|
+
<span style="margin: 0 0.5rem; opacity: 0.5;">•</span>
|
|
295
|
+
</>
|
|
296
|
+
) : null}
|
|
297
|
+
Updated: {page.updatedAt || '-'}
|
|
298
|
+
</div>
|
|
270
299
|
<div class="page-meta-item">
|
|
271
300
|
Powered by{' '}
|
|
272
301
|
<a
|
|
@@ -1374,11 +1374,11 @@ a {
|
|
|
1374
1374
|
align-items: center;
|
|
1375
1375
|
}
|
|
1376
1376
|
|
|
1377
|
+
.page-meta-link,
|
|
1377
1378
|
.methanol-link {
|
|
1378
1379
|
font-weight: 600;
|
|
1379
1380
|
color: var(--text);
|
|
1380
1381
|
text-decoration: none !important;
|
|
1381
|
-
margin-left: 0.25rem;
|
|
1382
1382
|
transition: all 0.2s ease;
|
|
1383
1383
|
border-bottom: 1px solid transparent;
|
|
1384
1384
|
|
|
@@ -1388,6 +1388,10 @@ a {
|
|
|
1388
1388
|
}
|
|
1389
1389
|
}
|
|
1390
1390
|
|
|
1391
|
+
.methanol-link {
|
|
1392
|
+
margin-left: 0.25rem;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1391
1395
|
@media (max-width: 640px) {
|
|
1392
1396
|
.page-meta {
|
|
1393
1397
|
flex-direction: column;
|
|
@@ -1627,6 +1631,110 @@ a {
|
|
|
1627
1631
|
}
|
|
1628
1632
|
}
|
|
1629
1633
|
|
|
1634
|
+
/* --- Components --- */
|
|
1635
|
+
|
|
1636
|
+
.link-button {
|
|
1637
|
+
display: inline-flex;
|
|
1638
|
+
align-items: center;
|
|
1639
|
+
justify-content: center;
|
|
1640
|
+
font-weight: 600;
|
|
1641
|
+
border-radius: 999px;
|
|
1642
|
+
text-decoration: none !important;
|
|
1643
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
1644
|
+
cursor: pointer;
|
|
1645
|
+
line-height: 1;
|
|
1646
|
+
gap: 0.5rem;
|
|
1647
|
+
white-space: nowrap;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
.link-button:active {
|
|
1651
|
+
transform: scale(0.96);
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
/* Variants */
|
|
1655
|
+
.link-button--primary {
|
|
1656
|
+
background-color: var(--accent);
|
|
1657
|
+
color: var(--accent-foreground);
|
|
1658
|
+
border: 1px solid transparent;
|
|
1659
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
1660
|
+
|
|
1661
|
+
&:hover {
|
|
1662
|
+
opacity: 0.9;
|
|
1663
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
|
1664
|
+
transform: translateY(-1px);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
.link-button--secondary {
|
|
1669
|
+
background-color: var(--surface-elevated);
|
|
1670
|
+
color: var(--text);
|
|
1671
|
+
border: 1px solid var(--border);
|
|
1672
|
+
|
|
1673
|
+
&:hover {
|
|
1674
|
+
background-color: var(--surface-muted);
|
|
1675
|
+
border-color: var(--muted);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
.link-button--outline {
|
|
1680
|
+
background-color: transparent;
|
|
1681
|
+
color: var(--text);
|
|
1682
|
+
border: 1px solid var(--border);
|
|
1683
|
+
|
|
1684
|
+
&:hover {
|
|
1685
|
+
border-color: var(--accent);
|
|
1686
|
+
color: var(--accent);
|
|
1687
|
+
background-color: var(--accent-soft);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
.link-button--ghost {
|
|
1692
|
+
background-color: transparent;
|
|
1693
|
+
color: var(--muted);
|
|
1694
|
+
border: 1px solid transparent;
|
|
1695
|
+
|
|
1696
|
+
&:hover {
|
|
1697
|
+
color: var(--accent);
|
|
1698
|
+
background-color: var(--accent-soft);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
/* Sizes */
|
|
1703
|
+
.link-button--sm {
|
|
1704
|
+
padding: 0.375rem 0.75rem;
|
|
1705
|
+
font-size: 0.85rem;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
.link-button--md {
|
|
1709
|
+
padding: 0.625rem 1.25rem;
|
|
1710
|
+
font-size: 0.95rem;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
.link-button--lg {
|
|
1714
|
+
padding: 0.875rem 1.75rem;
|
|
1715
|
+
font-size: 1.1rem;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
.button-group {
|
|
1719
|
+
display: flex;
|
|
1720
|
+
flex-wrap: wrap;
|
|
1721
|
+
gap: 1rem;
|
|
1722
|
+
margin: 1.5rem 0;
|
|
1723
|
+
align-items: center;
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
.button-group--left {
|
|
1727
|
+
justify-content: flex-start;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
.button-group--center {
|
|
1731
|
+
justify-content: center;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
.button-group--right {
|
|
1735
|
+
justify-content: flex-end;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1630
1738
|
/* --- View Transitions --- */
|
|
1631
1739
|
|
|
1632
1740
|
@view-transition {
|