methanol 0.0.19 → 0.0.21
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 +1 -1
- package/src/build-system.js +53 -22
- package/src/components.js +2 -0
- package/src/config.js +44 -0
- package/src/dev-server.js +6 -4
- package/src/feed.js +189 -0
- package/src/main.js +6 -1
- package/src/mdx.js +49 -24
- package/src/pages-index.js +10 -1
- package/src/pages.js +105 -111
- package/src/reframe.js +19 -2
- package/src/state.js +31 -3
- package/src/templates/atom-feed.jsx +61 -0
- package/src/{error-page.jsx → templates/error-page.jsx} +2 -2
- package/src/templates/rss-feed.jsx +60 -0
- package/src/workers/build-pool.js +10 -1
- package/src/workers/build-worker.js +30 -23
- package/themes/blog/sources/style.css +44 -4
- package/themes/blog/src/layout-categories.jsx +2 -2
- package/themes/blog/src/layout-collections.jsx +2 -2
- package/themes/blog/src/layout-home.jsx +2 -2
- package/themes/blog/src/page.jsx +55 -6
- package/themes/blog/src/post-utils.js +13 -3
- package/themes/default/sources/style.css +27 -1
- package/themes/default/src/nav-tree.jsx +74 -58
- package/themes/default/src/page.jsx +23 -0
|
@@ -28,6 +28,7 @@ let initPromise = null
|
|
|
28
28
|
let pages = []
|
|
29
29
|
let pagesContext = null
|
|
30
30
|
let components = null
|
|
31
|
+
let mdxPageIds = new Set()
|
|
31
32
|
|
|
32
33
|
const ensureInit = async () => {
|
|
33
34
|
if (initPromise) return initPromise
|
|
@@ -112,31 +113,27 @@ const logPageError = (phase, page, error) => {
|
|
|
112
113
|
const handleSetPages = async (message) => {
|
|
113
114
|
const { pages: nextPages, excludedRoutes = [], excludedDirs = [] } = message || {}
|
|
114
115
|
pages = Array.isArray(nextPages) ? nextPages : []
|
|
116
|
+
mdxPageIds = new Set()
|
|
115
117
|
await rebuildPagesContext(new Set(excludedRoutes), new Set(excludedDirs))
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
const handleSyncUpdates = async (message) => {
|
|
119
|
-
const { updates = [],
|
|
120
|
-
if (Array.isArray(titles)) {
|
|
121
|
-
for (let i = 0; i < titles.length; i += 1) {
|
|
122
|
-
const page = pages[i]
|
|
123
|
-
if (!page) continue
|
|
124
|
-
if (titles[i] !== undefined) {
|
|
125
|
-
page.title = titles[i]
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
121
|
+
const { updates = [], excludedRoutes = null, excludedDirs = null } = message || {}
|
|
129
122
|
for (const update of updates) {
|
|
130
123
|
const page = pages[update.id]
|
|
131
124
|
if (!page) continue
|
|
132
125
|
if (update.title !== undefined) page.title = update.title
|
|
133
|
-
if (update.toc !== undefined) page.toc = update.toc
|
|
134
126
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
if (!pagesContext || excludedRoutes || excludedDirs) {
|
|
128
|
+
await rebuildPagesContext(
|
|
129
|
+
excludedRoutes ? new Set(excludedRoutes) : pagesContext?.excludedRoutes || new Set(),
|
|
130
|
+
excludedDirs ? new Set(excludedDirs) : pagesContext?.excludedDirs || new Set()
|
|
131
|
+
)
|
|
132
|
+
} else {
|
|
133
|
+
pagesContext.refreshPagesTree?.(true)
|
|
134
|
+
}
|
|
135
|
+
for (const id of mdxPageIds) {
|
|
136
|
+
const page = pages[id]
|
|
140
137
|
if (!page?.mdxCtx) continue
|
|
141
138
|
refreshMdxCtx(page)
|
|
142
139
|
}
|
|
@@ -159,6 +156,7 @@ const handleCompile = async (message) => {
|
|
|
159
156
|
lazyPagesTree: true,
|
|
160
157
|
refreshPagesTree: false
|
|
161
158
|
})
|
|
159
|
+
mdxPageIds.add(id)
|
|
162
160
|
updates.push({ id, title: page.title, toc: page.toc || null })
|
|
163
161
|
} catch (error) {
|
|
164
162
|
logPageError('MDX compile', page, error)
|
|
@@ -171,9 +169,9 @@ const handleCompile = async (message) => {
|
|
|
171
169
|
}
|
|
172
170
|
|
|
173
171
|
const handleRender = async (message) => {
|
|
174
|
-
const { ids = [], stage } = message || {}
|
|
175
|
-
const { renderHtml } = await import('../mdx.js')
|
|
176
|
-
const
|
|
172
|
+
const { ids = [], stage, feedIds = [] } = message || {}
|
|
173
|
+
const { renderHtml, renderPageContent } = await import('../mdx.js')
|
|
174
|
+
const feedSet = new Set(Array.isArray(feedIds) ? feedIds : [])
|
|
177
175
|
let completed = 0
|
|
178
176
|
for (const id of ids) {
|
|
179
177
|
const page = pages[id]
|
|
@@ -190,7 +188,17 @@ const handleRender = async (message) => {
|
|
|
190
188
|
pagesContext,
|
|
191
189
|
pageMeta: page
|
|
192
190
|
})
|
|
193
|
-
|
|
191
|
+
let feedContent = null
|
|
192
|
+
if (feedSet.has(id)) {
|
|
193
|
+
feedContent = await renderPageContent({
|
|
194
|
+
routePath: page.routePath,
|
|
195
|
+
path: page.path,
|
|
196
|
+
components,
|
|
197
|
+
pagesContext,
|
|
198
|
+
pageMeta: page
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
parentPort?.postMessage({ type: 'result', stage, result: { id, html, feedContent } })
|
|
194
202
|
} catch (error) {
|
|
195
203
|
logPageError('MDX render', page, error)
|
|
196
204
|
throw error
|
|
@@ -198,7 +206,6 @@ const handleRender = async (message) => {
|
|
|
198
206
|
completed += 1
|
|
199
207
|
parentPort?.postMessage({ type: 'progress', stage, completed })
|
|
200
208
|
}
|
|
201
|
-
return results
|
|
202
209
|
}
|
|
203
210
|
|
|
204
211
|
parentPort?.on('message', async (message) => {
|
|
@@ -221,8 +228,8 @@ parentPort?.on('message', async (message) => {
|
|
|
221
228
|
return
|
|
222
229
|
}
|
|
223
230
|
if (type === 'render') {
|
|
224
|
-
|
|
225
|
-
parentPort?.postMessage({ type: 'done', stage
|
|
231
|
+
await handleRender(message)
|
|
232
|
+
parentPort?.postMessage({ type: 'done', stage })
|
|
226
233
|
return
|
|
227
234
|
}
|
|
228
235
|
} catch (error) {
|
|
@@ -98,9 +98,31 @@ a {
|
|
|
98
98
|
flex-shrink: 0;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
.header-actions .rss-link {
|
|
102
|
+
color: var(--text-muted);
|
|
103
|
+
text-decoration: none;
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: center;
|
|
107
|
+
width: 2.25rem;
|
|
108
|
+
height: 2.25rem;
|
|
109
|
+
border-radius: 999px;
|
|
110
|
+
transition: all 0.2s;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.header-actions .rss-link:hover {
|
|
114
|
+
color: var(--text);
|
|
115
|
+
background: var(--bg-soft);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.rss-label {
|
|
119
|
+
display: none;
|
|
120
|
+
}
|
|
121
|
+
|
|
101
122
|
.blog-nav {
|
|
102
123
|
display: flex;
|
|
103
124
|
gap: 1.25rem;
|
|
125
|
+
align-items: center;
|
|
104
126
|
}
|
|
105
127
|
|
|
106
128
|
.blog-nav a {
|
|
@@ -551,11 +573,29 @@ h6:hover .heading-anchor, h6:active .heading-anchor {
|
|
|
551
573
|
visibility: visible;
|
|
552
574
|
}
|
|
553
575
|
|
|
554
|
-
|
|
555
|
-
|
|
576
|
+
.nav-toggle:checked ~ .nav-toggle-label {
|
|
577
|
+
color: var(--primary);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.header-actions .rss-link {
|
|
581
|
+
width: 100%;
|
|
582
|
+
height: auto;
|
|
583
|
+
border-radius: 0;
|
|
584
|
+
justify-content: flex-start;
|
|
585
|
+
padding: 0.75rem var(--container-px);
|
|
586
|
+
background: transparent !important;
|
|
587
|
+
font-weight: 500;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.header-actions .rss-link:hover {
|
|
591
|
+
color: var(--primary);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.rss-label {
|
|
595
|
+
display: inline;
|
|
596
|
+
margin-left: 0.5rem;
|
|
597
|
+
}
|
|
556
598
|
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
599
|
@media (max-width: 640px) {
|
|
560
600
|
.blog-header { padding: 1rem 0; }
|
|
561
601
|
.header-container { gap: 1rem; }
|
|
@@ -40,9 +40,9 @@ const renderPostCards = (posts = []) =>
|
|
|
40
40
|
)
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
-
export const LayoutCategories = ({ PageContent, title, pages, navLinks, components }) => {
|
|
43
|
+
export const LayoutCategories = ({ PageContent, title, pages, navLinks, components, currentRoutePath, hiddenPrefixes }) => {
|
|
44
44
|
const { CategoryView } = components || {}
|
|
45
|
-
const filteredPosts = filterBlogPosts(pages, navLinks)
|
|
45
|
+
const filteredPosts = filterBlogPosts(pages, navLinks, { currentRoutePath, hiddenPrefixes })
|
|
46
46
|
const staticPosts = mapStaticPosts(filteredPosts)
|
|
47
47
|
const categories = collectCategories(filteredPosts)
|
|
48
48
|
const visiblePosts = staticPosts.slice(0, 10)
|
|
@@ -39,9 +39,9 @@ const renderPostCards = (posts = [], collectionTitles = {}) =>
|
|
|
39
39
|
)
|
|
40
40
|
})
|
|
41
41
|
|
|
42
|
-
export const LayoutCollections = ({ PageContent, title, pages, pagesByRoute, navLinks, components }) => {
|
|
42
|
+
export const LayoutCollections = ({ PageContent, title, pages, pagesByRoute, navLinks, components, currentRoutePath, hiddenPrefixes }) => {
|
|
43
43
|
const { CollectionView } = components || {}
|
|
44
|
-
const filteredPosts = filterBlogPosts(pages, navLinks)
|
|
44
|
+
const filteredPosts = filterBlogPosts(pages, navLinks, { currentRoutePath, hiddenPrefixes })
|
|
45
45
|
const staticPosts = mapStaticPosts(filteredPosts)
|
|
46
46
|
const collectionTitles = collectCollectionTitles(staticPosts, pagesByRoute)
|
|
47
47
|
const visiblePosts = staticPosts.slice(0, 10)
|
|
@@ -35,9 +35,9 @@ const renderPostCards = (posts = []) =>
|
|
|
35
35
|
)
|
|
36
36
|
})
|
|
37
37
|
|
|
38
|
-
export const LayoutHome = ({ PageContent, pages, navLinks, components }) => {
|
|
38
|
+
export const LayoutHome = ({ PageContent, pages, navLinks, components, currentRoutePath, hiddenPrefixes }) => {
|
|
39
39
|
const { PostList } = components || {}
|
|
40
|
-
const filteredPosts = filterBlogPosts(pages, navLinks)
|
|
40
|
+
const filteredPosts = filterBlogPosts(pages, navLinks, { currentRoutePath, hiddenPrefixes })
|
|
41
41
|
const staticPosts = mapStaticPosts(filteredPosts)
|
|
42
42
|
const visiblePosts = staticPosts.slice(0, 10)
|
|
43
43
|
const hasMore = staticPosts.length > visiblePosts.length
|
package/themes/blog/src/page.jsx
CHANGED
|
@@ -24,7 +24,7 @@ import { LayoutCategories } from './layout-categories.jsx'
|
|
|
24
24
|
import { LayoutCollections } from './layout-collections.jsx'
|
|
25
25
|
import { LayoutPost } from './layout-post.jsx'
|
|
26
26
|
|
|
27
|
-
const Header = ({ siteName, navLinks, components, pagefindOptions }) => {
|
|
27
|
+
const Header = ({ siteName, navLinks, components, pagefindOptions, rssHref, feedLabel }) => {
|
|
28
28
|
const { ThemeSearchBox } = components || {}
|
|
29
29
|
return (
|
|
30
30
|
<header class="blog-header">
|
|
@@ -34,7 +34,6 @@ const Header = ({ siteName, navLinks, components, pagefindOptions }) => {
|
|
|
34
34
|
</a>
|
|
35
35
|
<div class="header-actions">
|
|
36
36
|
{ThemeSearchBox && <ThemeSearchBox options={pagefindOptions} />}
|
|
37
|
-
|
|
38
37
|
<input type="checkbox" id="nav-toggle" class="nav-toggle" />
|
|
39
38
|
<label for="nav-toggle" class="nav-toggle-label">
|
|
40
39
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -50,6 +49,25 @@ const Header = ({ siteName, navLinks, components, pagefindOptions }) => {
|
|
|
50
49
|
{link.label}
|
|
51
50
|
</a>
|
|
52
51
|
))}
|
|
52
|
+
{rssHref ? (
|
|
53
|
+
<a class="rss-link" href={rssHref} aria-label={feedLabel} title={feedLabel}>
|
|
54
|
+
<svg
|
|
55
|
+
width="20"
|
|
56
|
+
height="20"
|
|
57
|
+
viewBox="0 0 24 24"
|
|
58
|
+
fill="none"
|
|
59
|
+
stroke="currentColor"
|
|
60
|
+
stroke-width="2"
|
|
61
|
+
stroke-linecap="round"
|
|
62
|
+
stroke-linejoin="round"
|
|
63
|
+
>
|
|
64
|
+
<path d="M4 11a9 9 0 0 1 9 9"></path>
|
|
65
|
+
<path d="M4 4a16 16 0 0 1 16 16"></path>
|
|
66
|
+
<circle cx="5" cy="19" r="1"></circle>
|
|
67
|
+
</svg>
|
|
68
|
+
<span class="rss-label">{feedLabel}</span>
|
|
69
|
+
</a>
|
|
70
|
+
) : null}
|
|
53
71
|
</nav>
|
|
54
72
|
</div>
|
|
55
73
|
</div>
|
|
@@ -57,11 +75,11 @@ const Header = ({ siteName, navLinks, components, pagefindOptions }) => {
|
|
|
57
75
|
)
|
|
58
76
|
}
|
|
59
77
|
|
|
60
|
-
const Footer = ({ siteName }) => (
|
|
78
|
+
const Footer = ({ siteName, owner }) => (
|
|
61
79
|
<footer class="blog-footer">
|
|
62
80
|
<div class="container">
|
|
63
81
|
<p>
|
|
64
|
-
© {new Date().getFullYear()} {siteName}. Powered by <a href="https://methanol.sudoMaker.com">Methanol</a>.
|
|
82
|
+
© {new Date().getFullYear()} {owner || siteName}. Powered by <a href="https://methanol.sudoMaker.com">Methanol</a>.
|
|
65
83
|
</p>
|
|
66
84
|
</div>
|
|
67
85
|
</footer>
|
|
@@ -70,6 +88,7 @@ const Footer = ({ siteName }) => (
|
|
|
70
88
|
const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx, withBase }) => {
|
|
71
89
|
const page = ctx.page
|
|
72
90
|
const siteName = ctx.site.name || 'Methanol Blog'
|
|
91
|
+
const siteOwner = ctx.site.owner || null
|
|
73
92
|
const title = page.title || siteName
|
|
74
93
|
|
|
75
94
|
const isHome = page.routePath === '/'
|
|
@@ -90,11 +109,27 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx, withBase }) =>
|
|
|
90
109
|
return link
|
|
91
110
|
})
|
|
92
111
|
|
|
112
|
+
const currentRoutePath = page.routePath || '/'
|
|
93
113
|
const isCategoriesPage = page.routePath === '/categories'
|
|
94
114
|
const isCollectionsPage = page.routePath === '/collections'
|
|
115
|
+
const hiddenPrefixes = (ctx.pages || [])
|
|
116
|
+
.filter(
|
|
117
|
+
(entry) =>
|
|
118
|
+
entry.isIndex &&
|
|
119
|
+
entry.hidden &&
|
|
120
|
+
entry.routePath &&
|
|
121
|
+
entry.routePath !== '/' &&
|
|
122
|
+
entry.routePath !== '/404' &&
|
|
123
|
+
entry.routePath !== '/offline'
|
|
124
|
+
)
|
|
125
|
+
.map((entry) => (entry.routePath.endsWith('/') ? entry.routePath : `${entry.routePath}/`))
|
|
95
126
|
|
|
96
127
|
const pagefindEnabled = ctx.site.pagefind?.enabled !== false
|
|
97
128
|
const pagefindOptions = ctx.site.pagefind?.options || null
|
|
129
|
+
const feedInfo = ctx.site.feed
|
|
130
|
+
const rssHref = feedInfo?.enabled ? feedInfo.href : null
|
|
131
|
+
const feedType = feedInfo?.atom ? 'application/atom+xml' : 'application/rss+xml'
|
|
132
|
+
const feedLabel = feedInfo?.atom ? 'Atom' : 'RSS'
|
|
98
133
|
|
|
99
134
|
return (
|
|
100
135
|
<>
|
|
@@ -107,6 +142,7 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx, withBase }) =>
|
|
|
107
142
|
{title} | {siteName}
|
|
108
143
|
</title>
|
|
109
144
|
<link rel="stylesheet" href="/.methanol_theme_blog/style.css" />
|
|
145
|
+
{rssHref ? <link rel="alternate" type={feedType} title={`${siteName} ${feedLabel}`} href={rssHref} /> : null}
|
|
110
146
|
<ExtraHead />
|
|
111
147
|
</head>
|
|
112
148
|
<body>
|
|
@@ -116,10 +152,19 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx, withBase }) =>
|
|
|
116
152
|
navLinks={resolvedNavLinks}
|
|
117
153
|
components={pagefindEnabled ? components : {}}
|
|
118
154
|
pagefindOptions={pagefindOptions}
|
|
155
|
+
rssHref={rssHref}
|
|
156
|
+
feedLabel={feedLabel}
|
|
119
157
|
/>
|
|
120
158
|
<main class="container main-content">
|
|
121
159
|
{isHome ? (
|
|
122
|
-
<LayoutHome
|
|
160
|
+
<LayoutHome
|
|
161
|
+
PageContent={PageContent}
|
|
162
|
+
pages={ctx.pages}
|
|
163
|
+
navLinks={navLinks}
|
|
164
|
+
components={components}
|
|
165
|
+
currentRoutePath={currentRoutePath}
|
|
166
|
+
hiddenPrefixes={hiddenPrefixes}
|
|
167
|
+
/>
|
|
123
168
|
) : isCategoriesPage ? (
|
|
124
169
|
<LayoutCategories
|
|
125
170
|
PageContent={PageContent}
|
|
@@ -127,6 +172,8 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx, withBase }) =>
|
|
|
127
172
|
pages={ctx.pages}
|
|
128
173
|
navLinks={navLinks}
|
|
129
174
|
components={components}
|
|
175
|
+
currentRoutePath={currentRoutePath}
|
|
176
|
+
hiddenPrefixes={hiddenPrefixes}
|
|
130
177
|
/>
|
|
131
178
|
) : isCollectionsPage ? (
|
|
132
179
|
<LayoutCollections
|
|
@@ -136,12 +183,14 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx, withBase }) =>
|
|
|
136
183
|
pagesByRoute={ctx.pagesByRoute}
|
|
137
184
|
navLinks={navLinks}
|
|
138
185
|
components={components}
|
|
186
|
+
currentRoutePath={currentRoutePath}
|
|
187
|
+
hiddenPrefixes={hiddenPrefixes}
|
|
139
188
|
/>
|
|
140
189
|
) : (
|
|
141
190
|
<LayoutPost PageContent={PageContent} title={title} page={page} />
|
|
142
191
|
)}
|
|
143
192
|
</main>
|
|
144
|
-
<Footer siteName={siteName} />
|
|
193
|
+
<Footer siteName={siteName} owner={siteOwner} />
|
|
145
194
|
</div>
|
|
146
195
|
</body>
|
|
147
196
|
</html>
|
|
@@ -29,15 +29,25 @@ const isBlogPost = (page) => {
|
|
|
29
29
|
return true
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export const filterBlogPosts = (pages, navLinks = []) => {
|
|
32
|
+
export const filterBlogPosts = (pages, navLinks = [], options = {}) => {
|
|
33
33
|
const excluded = new Set((navLinks || []).map((link) => link?.href).filter(Boolean))
|
|
34
34
|
const list = (pages || []).filter((page) => isBlogPost(page) && !excluded.has(page.routeHref))
|
|
35
|
-
|
|
35
|
+
const { currentRoutePath, hiddenPrefixes } = options || {}
|
|
36
|
+
const activeRoute = typeof currentRoutePath === 'string' ? currentRoutePath : ''
|
|
37
|
+
const hiddenScopes = Array.isArray(hiddenPrefixes) ? hiddenPrefixes.filter(Boolean) : []
|
|
38
|
+
const filtered = hiddenScopes.length
|
|
39
|
+
? list.filter((page) => {
|
|
40
|
+
const hiddenScope = hiddenScopes.find((prefix) => page.routePath?.startsWith(prefix))
|
|
41
|
+
if (!hiddenScope) return true
|
|
42
|
+
return activeRoute.startsWith(hiddenScope)
|
|
43
|
+
})
|
|
44
|
+
: list
|
|
45
|
+
filtered.sort((a, b) => {
|
|
36
46
|
const dateA = new Date(a.frontmatter?.date || a.stats?.createdAt || 0)
|
|
37
47
|
const dateB = new Date(b.frontmatter?.date || b.stats?.createdAt || 0)
|
|
38
48
|
return dateB - dateA
|
|
39
49
|
})
|
|
40
|
-
return
|
|
50
|
+
return filtered
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
export const getExcerpt = (page) => extractExcerpt(page)
|
|
@@ -470,6 +470,33 @@ a {
|
|
|
470
470
|
flex-shrink: 0; /* Prevent footer from shrinking */
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
+
.sidebar-footer .rss-link {
|
|
474
|
+
display: flex;
|
|
475
|
+
align-items: center;
|
|
476
|
+
justify-content: center;
|
|
477
|
+
width: 2.25rem;
|
|
478
|
+
height: 2.25rem;
|
|
479
|
+
background: var(--surface-muted);
|
|
480
|
+
border: 1px solid var(--border);
|
|
481
|
+
border-radius: var(--radius);
|
|
482
|
+
color: var(--text);
|
|
483
|
+
transition: all 0.2s ease;
|
|
484
|
+
flex-shrink: 0;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.sidebar-footer .rss-link:hover {
|
|
488
|
+
background: var(--hover-bg);
|
|
489
|
+
border-color: var(--muted);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.sidebar-footer .rss-link:active {
|
|
493
|
+
transform: scale(0.96);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.sidebar-footer .rss-link svg {
|
|
497
|
+
opacity: 0.8;
|
|
498
|
+
}
|
|
499
|
+
|
|
473
500
|
.lang-switch-wrapper {
|
|
474
501
|
flex: 1;
|
|
475
502
|
position: relative;
|
|
@@ -1825,4 +1852,3 @@ a {
|
|
|
1825
1852
|
size: auto;
|
|
1826
1853
|
}
|
|
1827
1854
|
}
|
|
1828
|
-
|
|
@@ -45,69 +45,85 @@ const toSignal = (i) => {
|
|
|
45
45
|
return sig
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
</ul>
|
|
87
|
-
)}
|
|
88
|
-
</If>
|
|
89
|
-
</details>
|
|
90
|
-
</li>
|
|
91
|
-
)
|
|
92
|
-
} else {
|
|
93
|
-
const label = title.or(node.isIndex ? 'Home' : name)
|
|
94
|
-
return (
|
|
95
|
-
<li>
|
|
96
|
-
<a class={isActive.choose('active', null)} href={href}>
|
|
48
|
+
const filterVisible = (items) => {
|
|
49
|
+
if (!Array.isArray(items)) return []
|
|
50
|
+
return items.filter(({ value: item }) => {
|
|
51
|
+
if (!item.hidden) {
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const _currentPath = currentPath.value
|
|
56
|
+
return _currentPath.startsWith(item.routePath)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const NavTree = ({ nodes, depth }) => {
|
|
61
|
+
const filtered = signal(nodes, filterVisible)
|
|
62
|
+
return (
|
|
63
|
+
<For entries={filtered}>
|
|
64
|
+
{({ item }) => {
|
|
65
|
+
const node = read(item)
|
|
66
|
+
const { routeHref: href, routePath, type, name, isRoot } = node
|
|
67
|
+
const { title } = extract(item, 'title')
|
|
68
|
+
|
|
69
|
+
const isActive = matchCurrentPath(routePath)
|
|
70
|
+
|
|
71
|
+
if (type === 'directory') {
|
|
72
|
+
const label = title.or(name)
|
|
73
|
+
const { children } = extract(item, 'children')
|
|
74
|
+
|
|
75
|
+
const isOpen = $(() => {
|
|
76
|
+
if (depth < 1) {
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
const _active = isActive.value
|
|
80
|
+
const _currentPath = currentPath.value
|
|
81
|
+
return _active || _currentPath.startsWith(routePath)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const header = href ? (
|
|
85
|
+
<a class={isActive.choose('nav-dir-link active', 'nav-dir-link')} href={href}>
|
|
97
86
|
{label}
|
|
98
87
|
</a>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
|
|
88
|
+
) : (
|
|
89
|
+
<span class="nav-dir-label">{label}</span>
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<li class={isActive.choose('is-active', null)}>
|
|
94
|
+
<details class="sidebar-collapsible" open={isOpen.choose(true, null)}>
|
|
95
|
+
<summary class="sb-dir-header">{header}</summary>
|
|
96
|
+
<If condition={() => children.value.length}>
|
|
97
|
+
{() => (
|
|
98
|
+
<ul data-depth={depth + 1}>
|
|
99
|
+
<NavTree nodes={children} depth={depth + 1} />
|
|
100
|
+
</ul>
|
|
101
|
+
)}
|
|
102
|
+
</If>
|
|
103
|
+
</details>
|
|
104
|
+
</li>
|
|
105
|
+
)
|
|
106
|
+
} else {
|
|
107
|
+
const label = title.or(node.isIndex ? 'Home' : name)
|
|
108
|
+
return (
|
|
109
|
+
<li>
|
|
110
|
+
<a class={isActive.choose('active', null)} href={href}>
|
|
111
|
+
{label}
|
|
112
|
+
</a>
|
|
113
|
+
</li>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
}}
|
|
117
|
+
</For>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const _rootNodes = signal()
|
|
122
|
+
const rootNodes = signal(_rootNodes, (nodes) => nodes?.map(toSignal))
|
|
107
123
|
const rootTree = HTMLRenderer.createElement(NavTree, { nodes: rootNodes, depth: 0 })
|
|
108
124
|
|
|
109
125
|
export const renderNavTree = (nodes, path) => {
|
|
110
126
|
currentPath.value = path
|
|
111
|
-
|
|
127
|
+
_rootNodes.value = nodes
|
|
112
128
|
return rootTree
|
|
113
129
|
}
|
|
@@ -61,6 +61,10 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx }) => {
|
|
|
61
61
|
const htmlLang = typeof languageCode === 'string' && languageCode.trim() ? languageCode : 'en'
|
|
62
62
|
const pagefindEnabled = ctx.site.pagefind?.enabled !== false
|
|
63
63
|
const pagefindOptions = ctx.site.pagefind?.options || null
|
|
64
|
+
const feedInfo = ctx.site.feed
|
|
65
|
+
const rssHref = feedInfo?.enabled ? feedInfo.href : null
|
|
66
|
+
const feedType = feedInfo?.atom ? 'application/atom+xml' : 'application/rss+xml'
|
|
67
|
+
const feedLabel = feedInfo?.atom ? 'Atom' : 'RSS'
|
|
64
68
|
const repoBase = ctx.site.repoBase
|
|
65
69
|
const sourceUrl = pageFrontmatter.sourceURL
|
|
66
70
|
const editUrl = sourceUrl || (repoBase && page.relativePath ? new URL(page.relativePath, repoBase).href : null)
|
|
@@ -124,6 +128,7 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx }) => {
|
|
|
124
128
|
{twitterTitle ? <meta name="twitter:title" content={twitterTitle} /> : null}
|
|
125
129
|
{twitterDescription ? <meta name="twitter:description" content={twitterDescription} /> : null}
|
|
126
130
|
{twitterImage ? <meta name="twitter:image" content={twitterImage} /> : null}
|
|
131
|
+
{rssHref ? <link rel="alternate" type={feedType} title={`${siteName} ${feedLabel}`} href={rssHref} /> : null}
|
|
127
132
|
<link
|
|
128
133
|
rel="preload stylesheet"
|
|
129
134
|
as="style"
|
|
@@ -207,6 +212,24 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx }) => {
|
|
|
207
212
|
{languageSelector}
|
|
208
213
|
<ThemeColorSwitch />
|
|
209
214
|
<ThemeAccentSwitch />
|
|
215
|
+
{rssHref ? (
|
|
216
|
+
<a class="rss-link" href={rssHref} aria-label={feedLabel} title={feedLabel}>
|
|
217
|
+
<svg
|
|
218
|
+
width="18"
|
|
219
|
+
height="18"
|
|
220
|
+
viewBox="3 3 18 18"
|
|
221
|
+
fill="none"
|
|
222
|
+
stroke="currentColor"
|
|
223
|
+
stroke-width="2"
|
|
224
|
+
stroke-linecap="round"
|
|
225
|
+
stroke-linejoin="round"
|
|
226
|
+
>
|
|
227
|
+
<path d="M4 11a9 9 0 0 1 9 9"></path>
|
|
228
|
+
<path d="M4 4a16 16 0 0 1 16 16"></path>
|
|
229
|
+
<circle cx="5" cy="19" r="1"></circle>
|
|
230
|
+
</svg>
|
|
231
|
+
</a>
|
|
232
|
+
) : null}
|
|
210
233
|
</div>
|
|
211
234
|
</aside>
|
|
212
235
|
<main class="main-content" data-pagefind-body={pagefindEnabled ? '' : null}>
|