methanol 0.0.9 → 0.0.10
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 +3 -0
- package/index.js +7 -2
- package/package.json +8 -3
- package/src/build-system.js +43 -7
- package/src/client/sw.js +751 -0
- package/src/{assets.js → client/virtual-module/assets.js} +7 -3
- package/src/{virtual-module → client/virtual-module}/inject.js +1 -0
- package/src/{virtual-module → client/virtual-module}/loader.js +5 -5
- package/src/{virtual-module → client/virtual-module}/pagefind-loader.js +1 -1
- package/src/client/virtual-module/pwa-inject.js +25 -0
- package/src/components.js +5 -5
- package/src/config.js +22 -3
- package/src/dev-server.js +62 -66
- package/src/logger.js +9 -5
- package/src/main.js +5 -1
- package/src/mdx.js +21 -37
- package/src/pages.js +61 -51
- package/src/public-assets.js +1 -1
- package/src/{rewind.js → reframe.js} +1 -1
- package/src/rehype-plugins/link-resolve.js +2 -2
- package/src/stage-logger.js +3 -2
- package/src/state.js +16 -3
- package/src/utils.js +23 -1
- package/src/vite-plugins.js +17 -4
- package/themes/default/components/ThemeSearchBox.client.jsx +120 -11
- package/themes/default/index.js +2 -2
- package/themes/default/pages/404.mdx +4 -0
- package/themes/default/pages/index.mdx +7 -9
- package/themes/default/pages/offline.mdx +11 -0
- package/themes/default/sources/style.css +59 -169
- package/themes/default/src/nav-tree.jsx +112 -0
- package/themes/default/{page.jsx → src/page.jsx} +10 -55
- /package/themes/default/{heading.jsx → src/heading.jsx} +0 -0
|
@@ -30,6 +30,7 @@ const loadPagefindModule = async () => {
|
|
|
30
30
|
|
|
31
31
|
let keybindReady = false
|
|
32
32
|
let cachedPagefind = null
|
|
33
|
+
const PAGE_SIZE = 10
|
|
33
34
|
|
|
34
35
|
const resolveShortcutLabel = () => {
|
|
35
36
|
if (typeof navigator === 'undefined') return 'Ctrl+K'
|
|
@@ -60,48 +61,107 @@ export default function ({ options } = {}) {
|
|
|
60
61
|
const query = signal('')
|
|
61
62
|
const results = signal([])
|
|
62
63
|
const isLoading = signal(false)
|
|
64
|
+
const isLoadingMore = signal(false)
|
|
65
|
+
const hasMore = signal(false)
|
|
63
66
|
const activeIndex = signal(-1)
|
|
67
|
+
const loadError = signal('')
|
|
64
68
|
|
|
65
69
|
const buttonRef = signal()
|
|
66
70
|
const inputRef = signal()
|
|
71
|
+
const resultsRef = signal()
|
|
72
|
+
const loadingMoreRef = signal()
|
|
67
73
|
const resultIdPrefix = `search-result-${Math.random().toString(36).slice(2)}`
|
|
68
74
|
const activeMatch = onCondition(activeIndex)
|
|
69
75
|
|
|
70
76
|
let debounceTimer = null
|
|
77
|
+
let resultHandles = []
|
|
78
|
+
let resultOffset = 0
|
|
79
|
+
let latestSearchId = 0
|
|
71
80
|
const shortcutLabel = resolveShortcutLabel()
|
|
72
81
|
const [Inlet, Outlet] = createPortal()
|
|
73
82
|
|
|
83
|
+
const resetSearchState = () => {
|
|
84
|
+
resultHandles = []
|
|
85
|
+
resultOffset = 0
|
|
86
|
+
hasMore.value = false
|
|
87
|
+
isLoadingMore.value = false
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const loadMore = async (initial = false) => {
|
|
91
|
+
const searchId = latestSearchId
|
|
92
|
+
if (!initial) {
|
|
93
|
+
if (isLoadingMore.value || !hasMore.value) return
|
|
94
|
+
isLoadingMore.value = true
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const slice = resultHandles.slice(resultOffset, resultOffset + PAGE_SIZE)
|
|
98
|
+
if (!slice.length) {
|
|
99
|
+
hasMore.value = false
|
|
100
|
+
if (!initial) isLoadingMore.value = false
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const data = await Promise.all(slice.map((r) => r.data()))
|
|
106
|
+
if (searchId !== latestSearchId) return
|
|
107
|
+
|
|
108
|
+
results.value = results.value.concat(data.map((value) => ({ value, el: signal() })))
|
|
109
|
+
resultOffset += slice.length
|
|
110
|
+
hasMore.value = resultOffset < resultHandles.length
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (searchId !== latestSearchId) return
|
|
113
|
+
loadError.value = 'Search is unavailable. Please refresh and try again.'
|
|
114
|
+
console.error('Search error:', err)
|
|
115
|
+
} finally {
|
|
116
|
+
if (!initial && searchId === latestSearchId) isLoadingMore.value = false
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
74
120
|
const search = async (q) => {
|
|
121
|
+
const searchId = ++latestSearchId
|
|
75
122
|
isLoading.value = true
|
|
76
123
|
results.value = []
|
|
77
124
|
activeIndex.value = -1
|
|
125
|
+
resetSearchState()
|
|
78
126
|
|
|
79
127
|
const pagefind = await ensurePagefind(options)
|
|
128
|
+
if (searchId !== latestSearchId) return
|
|
129
|
+
|
|
80
130
|
if (!pagefind) {
|
|
81
131
|
isLoading.value = false
|
|
132
|
+
loadError.value = 'Search is unavailable. Please refresh and try again.'
|
|
82
133
|
return
|
|
83
134
|
}
|
|
135
|
+
loadError.value = ''
|
|
84
136
|
|
|
85
137
|
try {
|
|
86
138
|
const searchResult = await pagefind.search(q)
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
139
|
+
if (searchId !== latestSearchId) return
|
|
140
|
+
|
|
141
|
+
resultHandles = searchResult?.results || []
|
|
142
|
+
resultOffset = 0
|
|
143
|
+
hasMore.value = resultHandles.length > 0
|
|
144
|
+
await loadMore(true)
|
|
91
145
|
} catch (err) {
|
|
146
|
+
if (searchId !== latestSearchId) return
|
|
147
|
+
loadError.value = 'Search is unavailable. Please refresh and try again.'
|
|
92
148
|
console.error('Search error:', err)
|
|
93
149
|
} finally {
|
|
94
|
-
isLoading.value = false
|
|
150
|
+
if (searchId === latestSearchId) isLoading.value = false
|
|
95
151
|
}
|
|
96
152
|
}
|
|
97
153
|
|
|
98
154
|
const onInput = (event) => {
|
|
99
155
|
const value = event.target.value
|
|
100
156
|
query.value = value
|
|
157
|
+
loadError.value = ''
|
|
101
158
|
if (debounceTimer) clearTimeout(debounceTimer)
|
|
102
159
|
|
|
103
160
|
if (!value.trim()) {
|
|
161
|
+
latestSearchId++
|
|
162
|
+
isLoading.value = false
|
|
104
163
|
results.value = []
|
|
164
|
+
resetSearchState()
|
|
105
165
|
activeIndex.value = -1
|
|
106
166
|
return
|
|
107
167
|
}
|
|
@@ -118,13 +178,18 @@ export default function ({ options } = {}) {
|
|
|
118
178
|
const open = async () => {
|
|
119
179
|
isOpen.value = true
|
|
120
180
|
setTimeout(focusInput, 50)
|
|
121
|
-
await ensurePagefind(options)
|
|
181
|
+
const pagefind = await ensurePagefind(options)
|
|
182
|
+
if (!pagefind) {
|
|
183
|
+
loadError.value = 'Search is unavailable. Please refresh and try again.'
|
|
184
|
+
}
|
|
122
185
|
}
|
|
123
186
|
|
|
124
187
|
const close = () => {
|
|
125
188
|
isOpen.value = false
|
|
126
189
|
query.value = ''
|
|
127
190
|
results.value = []
|
|
191
|
+
loadError.value = ''
|
|
192
|
+
resetSearchState()
|
|
128
193
|
activeIndex.value = -1
|
|
129
194
|
if (debounceTimer) clearTimeout(debounceTimer)
|
|
130
195
|
if (inputRef.value) inputRef.value.blur()
|
|
@@ -150,6 +215,13 @@ export default function ({ options } = {}) {
|
|
|
150
215
|
if (event.key === 'ArrowDown') {
|
|
151
216
|
event.preventDefault()
|
|
152
217
|
if (results.value.length > 0) {
|
|
218
|
+
if (hasMore.value && activeIndex.value === results.value.length - 1) {
|
|
219
|
+
loadMore(false)
|
|
220
|
+
setTimeout(() => {
|
|
221
|
+
loadingMoreRef.value?.scrollIntoView({ block: 'nearest' })
|
|
222
|
+
}, 10)
|
|
223
|
+
return
|
|
224
|
+
}
|
|
153
225
|
const nextIndex = activeIndex.value >= 0 ? (activeIndex.value + 1) % results.value.length : 0
|
|
154
226
|
activeIndex.value = nextIndex
|
|
155
227
|
scrollActiveIntoView()
|
|
@@ -181,6 +253,13 @@ export default function ({ options } = {}) {
|
|
|
181
253
|
}
|
|
182
254
|
if (event.key === 'ArrowDown') {
|
|
183
255
|
event.preventDefault()
|
|
256
|
+
if (hasMore.value && indexValue === results.value.length - 1) {
|
|
257
|
+
loadMore(false)
|
|
258
|
+
setTimeout(() => {
|
|
259
|
+
loadingMoreRef.value?.scrollIntoView({ block: 'nearest' })
|
|
260
|
+
}, 10)
|
|
261
|
+
return
|
|
262
|
+
}
|
|
184
263
|
const nextIndex = (indexValue + 1) % results.value.length
|
|
185
264
|
activeIndex.value = nextIndex
|
|
186
265
|
scrollActiveIntoView()
|
|
@@ -197,6 +276,13 @@ export default function ({ options } = {}) {
|
|
|
197
276
|
}
|
|
198
277
|
}
|
|
199
278
|
|
|
279
|
+
const onResultsScroll = (event) => {
|
|
280
|
+
const el = event.currentTarget
|
|
281
|
+
if (!el) return
|
|
282
|
+
const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 24
|
|
283
|
+
if (nearBottom) loadMore(false)
|
|
284
|
+
}
|
|
285
|
+
|
|
200
286
|
const showEmpty = $(() => !query.value)
|
|
201
287
|
const showNoResults = $(() => {
|
|
202
288
|
const _query = query.value
|
|
@@ -204,6 +290,9 @@ export default function ({ options } = {}) {
|
|
|
204
290
|
const _length = results.value.length
|
|
205
291
|
return _query && !_isLoading && _length === 0
|
|
206
292
|
})
|
|
293
|
+
const showError = $(() => loadError.value)
|
|
294
|
+
const showStatus = $(() => !loadError.value)
|
|
295
|
+
const showLoadingMore = $(() => isLoadingMore.value && !isLoading.value)
|
|
207
296
|
|
|
208
297
|
if (typeof window !== 'undefined') {
|
|
209
298
|
window.__methanolSearchOpen = open
|
|
@@ -280,12 +369,19 @@ export default function ({ options } = {}) {
|
|
|
280
369
|
$ref={inputRef}
|
|
281
370
|
/>
|
|
282
371
|
</div>
|
|
283
|
-
<div class="search-results">
|
|
284
|
-
<If condition={
|
|
285
|
-
<If condition={
|
|
286
|
-
{() =>
|
|
372
|
+
<div class="search-results" on:scroll={onResultsScroll} $ref={resultsRef}>
|
|
373
|
+
<If condition={showError}>{() => <div class="search-status">{loadError}</div>}</If>
|
|
374
|
+
<If condition={showStatus}>
|
|
375
|
+
{() => (
|
|
376
|
+
<>
|
|
377
|
+
<If condition={showEmpty}>{() => <div class="search-status">Type to start searching...</div>}</If>
|
|
378
|
+
<If condition={showNoResults}>
|
|
379
|
+
{() => <div class="search-status">No results found for "{query}"</div>}
|
|
380
|
+
</If>
|
|
381
|
+
<If condition={isLoading}>{() => <div class="search-status">Searching...</div>}</If>
|
|
382
|
+
</>
|
|
383
|
+
)}
|
|
287
384
|
</If>
|
|
288
|
-
<If condition={isLoading}>{() => <div class="search-status">Searching...</div>}</If>
|
|
289
385
|
<For entries={results} indexed>
|
|
290
386
|
{({ item: { value, el }, index }) => (
|
|
291
387
|
<a
|
|
@@ -321,6 +417,19 @@ export default function ({ options } = {}) {
|
|
|
321
417
|
</a>
|
|
322
418
|
)}
|
|
323
419
|
</For>
|
|
420
|
+
<If condition={showLoadingMore}>
|
|
421
|
+
{() => (
|
|
422
|
+
<div
|
|
423
|
+
class="search-status"
|
|
424
|
+
$ref={(el) => {
|
|
425
|
+
loadingMoreRef.value = el
|
|
426
|
+
el.scrollIntoView({ block: 'nearest' })
|
|
427
|
+
}}
|
|
428
|
+
>
|
|
429
|
+
Loading more results...
|
|
430
|
+
</div>
|
|
431
|
+
)}
|
|
432
|
+
</If>{' '}
|
|
324
433
|
</div>
|
|
325
434
|
</div>
|
|
326
435
|
</div>
|
package/themes/default/index.js
CHANGED
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
import { readFileSync } from 'fs'
|
|
22
22
|
import { fileURLToPath } from 'url'
|
|
23
23
|
import { dirname, resolve } from 'path'
|
|
24
|
-
import PAGE_TEMPLATE from './page.jsx'
|
|
25
|
-
import { createHeadings } from './heading.jsx'
|
|
24
|
+
import PAGE_TEMPLATE from './src/page.jsx'
|
|
25
|
+
import { createHeadings } from './src/heading.jsx'
|
|
26
26
|
|
|
27
27
|
const __filename = fileURLToPath(import.meta.url)
|
|
28
28
|
const __dirname = dirname(__filename)
|
|
@@ -6,19 +6,13 @@ title: Welcome
|
|
|
6
6
|
|
|
7
7
|
Methanol turns your content folder into a static site with rEFui and MDX, file-based routing, and a fast dev server.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npx methanol dev
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Open `http://localhost:5173`.
|
|
9
|
+
If you're seeing this page, that means you haven't created your `index.mdx` yet.
|
|
16
10
|
|
|
17
|
-
##
|
|
11
|
+
## Quick Start
|
|
18
12
|
|
|
19
13
|
Pages are currently read from the configured `pagesDir`:
|
|
20
14
|
|
|
21
|
-
<p><code>{ctx.site
|
|
15
|
+
<p><code>{ctx.site.pagesDir || 'pages/'}</code></p>
|
|
22
16
|
|
|
23
17
|
You can change this with `pagesDir` in your config or `--input` on the CLI. Create an `index.mdx` in that folder to replace this page.
|
|
24
18
|
|
|
@@ -29,3 +23,7 @@ components/ # JSX/TSX components for MDX
|
|
|
29
23
|
public/ # static assets copied as-is
|
|
30
24
|
dist/ # production output
|
|
31
25
|
```
|
|
26
|
+
|
|
27
|
+
## Further Reading
|
|
28
|
+
|
|
29
|
+
Visit [Methanol Docs](https://methanol.sudomaker.com) for details.
|
|
@@ -2,205 +2,88 @@
|
|
|
2
2
|
|
|
3
3
|
:root {
|
|
4
4
|
interpolate-size: allow-keywords;
|
|
5
|
-
color-scheme: dark;
|
|
6
|
-
--bg: #09090b;
|
|
7
|
-
--surface: #0c0c0e;
|
|
8
|
-
--surface-muted: #17171a;
|
|
9
|
-
--surface-elevated: #1c1c21;
|
|
10
|
-
--text: #fafafa;
|
|
11
|
-
--muted: #a1a1aa;
|
|
12
|
-
--border: #27272a;
|
|
13
|
-
--accent: #ffa000;
|
|
5
|
+
color-scheme: light dark;
|
|
6
|
+
--bg: light-dark(#ffffff, #09090b);
|
|
7
|
+
--surface: light-dark(#fafafa, #0c0c0e);
|
|
8
|
+
--surface-muted: light-dark(#f4f4f5, #17171a);
|
|
9
|
+
--surface-elevated: light-dark(#ffffff, #1c1c21);
|
|
10
|
+
--text: light-dark(#09090b, #fafafa);
|
|
11
|
+
--muted: light-dark(#71717a, #a1a1aa);
|
|
12
|
+
--border: light-dark(#e4e4e7, #27272a);
|
|
13
|
+
--accent: light-dark(#ff8f00, #ffa000);
|
|
14
14
|
--accent-foreground: #ffffff;
|
|
15
|
-
--accent-soft: rgba(255, 160, 0, 0.1);
|
|
15
|
+
--accent-soft: light-dark(rgba(255, 143, 0, 0.08), rgba(255, 160, 0, 0.1));
|
|
16
16
|
--radius: 8px;
|
|
17
17
|
--sidebar-width: 260px;
|
|
18
18
|
--toc-width: 240px;
|
|
19
19
|
--header-height: 0px;
|
|
20
|
-
--hover-bg: rgba(255, 255, 255, 0.05);
|
|
20
|
+
--hover-bg: light-dark(rgba(0, 0, 0, 0.04), rgba(255, 255, 255, 0.05));
|
|
21
|
+
|
|
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);
|
|
21
53
|
|
|
22
54
|
@media (prefers-color-scheme: light) {
|
|
23
|
-
|
|
55
|
+
&:not(.dark) {
|
|
24
56
|
color-scheme: light;
|
|
25
|
-
--bg: #ffffff;
|
|
26
|
-
--surface: #fafafa;
|
|
27
|
-
--surface-muted: #f4f4f5;
|
|
28
|
-
--surface-elevated: #ffffff;
|
|
29
|
-
--text: #09090b;
|
|
30
|
-
--muted: #71717a;
|
|
31
|
-
--border: #e4e4e7;
|
|
32
|
-
--accent: #ff8f00;
|
|
33
|
-
--accent-foreground: #ffffff;
|
|
34
|
-
--accent-soft: rgba(255, 143, 0, 0.08);
|
|
35
|
-
--hover-bg: rgba(0, 0, 0, 0.04);
|
|
36
57
|
}
|
|
37
58
|
}
|
|
38
59
|
|
|
39
60
|
&.light {
|
|
40
61
|
color-scheme: light;
|
|
41
|
-
--bg: #ffffff;
|
|
42
|
-
--surface: #fafafa;
|
|
43
|
-
--surface-muted: #f4f4f5;
|
|
44
|
-
--surface-elevated: #ffffff;
|
|
45
|
-
--text: #09090b;
|
|
46
|
-
--muted: #71717a;
|
|
47
|
-
--border: #e4e4e7;
|
|
48
|
-
--accent: #ff8f00;
|
|
49
|
-
--accent-foreground: #ffffff;
|
|
50
|
-
--accent-soft: rgba(255, 143, 0, 0.08);
|
|
51
|
-
--hover-bg: rgba(0, 0, 0, 0.04);
|
|
52
|
-
|
|
53
|
-
/* Starry Night Light Theme */
|
|
54
|
-
--color-prettylights-syntax-brackethighlighter-angle: #59636e;
|
|
55
|
-
--color-prettylights-syntax-brackethighlighter-unmatched: #82071e;
|
|
56
|
-
--color-prettylights-syntax-carriage-return-bg: #cf222e;
|
|
57
|
-
--color-prettylights-syntax-carriage-return-text: #f6f8fa;
|
|
58
|
-
--color-prettylights-syntax-comment: #59636e;
|
|
59
|
-
--color-prettylights-syntax-constant: #0550ae;
|
|
60
|
-
--color-prettylights-syntax-constant-other-reference-link: #0a3069;
|
|
61
|
-
--color-prettylights-syntax-entity: #6639ba;
|
|
62
|
-
--color-prettylights-syntax-entity-tag: #0550ae;
|
|
63
|
-
--color-prettylights-syntax-invalid-illegal-bg: #82071e;
|
|
64
|
-
--color-prettylights-syntax-invalid-illegal-text: #f6f8fa;
|
|
65
|
-
--color-prettylights-syntax-keyword: #cf222e;
|
|
66
|
-
--color-prettylights-syntax-markup-changed-bg: #ffd8b5;
|
|
67
|
-
--color-prettylights-syntax-markup-changed-text: #953800;
|
|
68
|
-
--color-prettylights-syntax-markup-deleted-bg: #ffebe9;
|
|
69
|
-
--color-prettylights-syntax-markup-deleted-text: #82071e;
|
|
70
|
-
--color-prettylights-syntax-markup-heading: #0550ae;
|
|
71
|
-
--color-prettylights-syntax-markup-ignored-bg: #0550ae;
|
|
72
|
-
--color-prettylights-syntax-markup-ignored-text: #d1d9e0;
|
|
73
|
-
--color-prettylights-syntax-markup-inserted-bg: #dafbe1;
|
|
74
|
-
--color-prettylights-syntax-markup-inserted-text: #116329;
|
|
75
|
-
--color-prettylights-syntax-markup-list: #3b2300;
|
|
76
|
-
--color-prettylights-syntax-meta-diff-range: #8250df;
|
|
77
|
-
--color-prettylights-syntax-string: #0a3069;
|
|
78
|
-
--color-prettylights-syntax-string-regexp: #116329;
|
|
79
|
-
--color-prettylights-syntax-sublimelinter-gutter-mark: #818b98;
|
|
80
|
-
--color-prettylights-syntax-variable: #953800;
|
|
81
|
-
--color-prettylights-syntax-markup-bold: #1f2328;
|
|
82
|
-
--color-prettylights-syntax-markup-italic: #1f2328;
|
|
83
|
-
--color-prettylights-syntax-storage-modifier-import: #1f2328;
|
|
84
62
|
}
|
|
85
63
|
|
|
86
64
|
&.dark {
|
|
87
65
|
color-scheme: dark;
|
|
88
|
-
--bg: #09090b;
|
|
89
|
-
--surface: #0c0c0e;
|
|
90
|
-
--surface-muted: #17171a;
|
|
91
|
-
--surface-elevated: #1c1c21;
|
|
92
|
-
--text: #fafafa;
|
|
93
|
-
--muted: #a1a1aa;
|
|
94
|
-
--border: #27272a;
|
|
95
|
-
--accent: #ffa000;
|
|
96
|
-
--accent-foreground: #ffffff;
|
|
97
|
-
--accent-soft: rgba(255, 160, 0, 0.1);
|
|
98
|
-
--radius: 8px;
|
|
99
|
-
--hover-bg: rgba(255, 255, 255, 0.05);
|
|
100
|
-
|
|
101
|
-
/* Starry Night Dark Theme */
|
|
102
|
-
--color-prettylights-syntax-brackethighlighter-angle: #9198a1;
|
|
103
|
-
--color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
|
|
104
|
-
--color-prettylights-syntax-carriage-return-bg: #b62324;
|
|
105
|
-
--color-prettylights-syntax-carriage-return-text: #f0f6fc;
|
|
106
|
-
--color-prettylights-syntax-comment: #9198a1;
|
|
107
|
-
--color-prettylights-syntax-constant: #79c0ff;
|
|
108
|
-
--color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
|
|
109
|
-
--color-prettylights-syntax-entity: #d2a8ff;
|
|
110
|
-
--color-prettylights-syntax-entity-tag: #7ee787;
|
|
111
|
-
--color-prettylights-syntax-invalid-illegal-bg: #8e1519;
|
|
112
|
-
--color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
|
|
113
|
-
--color-prettylights-syntax-keyword: #ff7b72;
|
|
114
|
-
--color-prettylights-syntax-markup-bold: #f0f6fc;
|
|
115
|
-
--color-prettylights-syntax-markup-changed-bg: #5a1e02;
|
|
116
|
-
--color-prettylights-syntax-markup-changed-text: #ffdfb6;
|
|
117
|
-
--color-prettylights-syntax-markup-deleted-bg: #67060c;
|
|
118
|
-
--color-prettylights-syntax-markup-deleted-text: #ffdcd7;
|
|
119
|
-
--color-prettylights-syntax-markup-heading: #1f6feb;
|
|
120
|
-
--color-prettylights-syntax-markup-ignored-bg: #1158c7;
|
|
121
|
-
--color-prettylights-syntax-markup-ignored-text: #f0f6fc;
|
|
122
|
-
--color-prettylights-syntax-markup-inserted-bg: #033a16;
|
|
123
|
-
--color-prettylights-syntax-markup-inserted-text: #aff5b4;
|
|
124
|
-
--color-prettylights-syntax-markup-italic: #f0f6fc;
|
|
125
|
-
--color-prettylights-syntax-markup-list: #f2cc60;
|
|
126
|
-
--color-prettylights-syntax-meta-diff-range: #d2a8ff;
|
|
127
|
-
--color-prettylights-syntax-storage-modifier-import: #f0f6fc;
|
|
128
|
-
--color-prettylights-syntax-string: #a5d6ff;
|
|
129
|
-
--color-prettylights-syntax-string-regexp: #7ee787;
|
|
130
|
-
--color-prettylights-syntax-sublimelinter-gutter-mark: #3d444d;
|
|
131
|
-
--color-prettylights-syntax-variable: #ffa657;
|
|
132
66
|
}
|
|
133
67
|
|
|
134
68
|
/* Accent Colors */
|
|
135
69
|
&.accent-rose {
|
|
136
|
-
--accent: #f43f5e;
|
|
137
|
-
--accent-soft: rgba(244, 63, 94, 0.15);
|
|
138
|
-
@media (prefers-color-scheme: light) {
|
|
139
|
-
--accent: #e11d48;
|
|
140
|
-
--accent-soft: rgba(225, 29, 72, 0.1);
|
|
141
|
-
}
|
|
142
|
-
&.light {
|
|
143
|
-
--accent: #e11d48;
|
|
144
|
-
--accent-soft: rgba(225, 29, 72, 0.1);
|
|
145
|
-
}
|
|
146
|
-
&.dark {
|
|
147
|
-
--accent: #f43f5e;
|
|
148
|
-
--accent-soft: rgba(244, 63, 94, 0.15);
|
|
149
|
-
}
|
|
70
|
+
--accent: light-dark(#e11d48, #f43f5e);
|
|
71
|
+
--accent-soft: light-dark(rgba(225, 29, 72, 0.1), rgba(244, 63, 94, 0.15));
|
|
150
72
|
}
|
|
151
73
|
|
|
152
74
|
&.accent-blue {
|
|
153
|
-
|
|
154
|
-
--accent:
|
|
155
|
-
--accent-soft: rgba(129, 140, 248, 0.15);
|
|
156
|
-
@media (prefers-color-scheme: light) {
|
|
157
|
-
--accent: #4f46e5;
|
|
158
|
-
--accent-soft: rgba(79, 70, 229, 0.1);
|
|
159
|
-
}
|
|
160
|
-
&.light {
|
|
161
|
-
--accent: #4f46e5;
|
|
162
|
-
--accent-soft: rgba(79, 70, 229, 0.1);
|
|
163
|
-
}
|
|
164
|
-
&.dark {
|
|
165
|
-
--accent: #818cf8;
|
|
166
|
-
--accent-soft: rgba(129, 140, 248, 0.15);
|
|
167
|
-
}
|
|
75
|
+
--accent: light-dark(#4f46e5, #818cf8);
|
|
76
|
+
--accent-soft: light-dark(rgba(79, 70, 229, 0.1), rgba(129, 140, 248, 0.15));
|
|
168
77
|
}
|
|
169
78
|
|
|
170
79
|
&.accent-green {
|
|
171
|
-
|
|
172
|
-
--accent:
|
|
173
|
-
--accent-soft: rgba(45, 212, 191, 0.15);
|
|
174
|
-
@media (prefers-color-scheme: light) {
|
|
175
|
-
--accent: #0d9488;
|
|
176
|
-
--accent-soft: rgba(13, 148, 136, 0.1);
|
|
177
|
-
}
|
|
178
|
-
&.light {
|
|
179
|
-
--accent: #0d9488;
|
|
180
|
-
--accent-soft: rgba(13, 148, 136, 0.1);
|
|
181
|
-
}
|
|
182
|
-
&.dark {
|
|
183
|
-
--accent: #2dd4bf;
|
|
184
|
-
--accent-soft: rgba(45, 212, 191, 0.15);
|
|
185
|
-
}
|
|
80
|
+
--accent: light-dark(#0d9488, #2dd4bf);
|
|
81
|
+
--accent-soft: light-dark(rgba(13, 148, 136, 0.1), rgba(45, 212, 191, 0.15));
|
|
186
82
|
}
|
|
187
83
|
|
|
188
84
|
&.accent-purple {
|
|
189
|
-
|
|
190
|
-
--accent:
|
|
191
|
-
--accent-soft: rgba(167, 139, 250, 0.15);
|
|
192
|
-
@media (prefers-color-scheme: light) {
|
|
193
|
-
--accent: #7c3aed;
|
|
194
|
-
--accent-soft: rgba(124, 58, 237, 0.1);
|
|
195
|
-
}
|
|
196
|
-
&.light {
|
|
197
|
-
--accent: #7c3aed;
|
|
198
|
-
--accent-soft: rgba(124, 58, 237, 0.1);
|
|
199
|
-
}
|
|
200
|
-
&.dark {
|
|
201
|
-
--accent: #a78bfa;
|
|
202
|
-
--accent-soft: rgba(167, 139, 250, 0.15);
|
|
203
|
-
}
|
|
85
|
+
--accent: light-dark(#7c3aed, #a78bfa);
|
|
86
|
+
--accent-soft: light-dark(rgba(124, 58, 237, 0.1), rgba(167, 139, 250, 0.15));
|
|
204
87
|
}
|
|
205
88
|
}
|
|
206
89
|
|
|
@@ -1190,6 +1073,13 @@ a {
|
|
|
1190
1073
|
margin-bottom: 1rem;
|
|
1191
1074
|
}
|
|
1192
1075
|
|
|
1076
|
+
img {
|
|
1077
|
+
max-width: 100%;
|
|
1078
|
+
height: auto;
|
|
1079
|
+
border-radius: var(--radius);
|
|
1080
|
+
margin: 1rem 0;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1193
1083
|
p {
|
|
1194
1084
|
color: var(--muted);
|
|
1195
1085
|
margin-bottom: 1.25rem;
|
|
@@ -0,0 +1,112 @@
|
|
|
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 { If, For, $, signal, onCondition, read, extract } from 'refui'
|
|
22
|
+
import NullProtoObj from 'null-prototype-object'
|
|
23
|
+
import { HTMLRenderer } from 'methanol'
|
|
24
|
+
|
|
25
|
+
const navEntryMap = new Map()
|
|
26
|
+
|
|
27
|
+
const currentPath = signal('')
|
|
28
|
+
const matchCurrentPath = onCondition(currentPath)
|
|
29
|
+
|
|
30
|
+
const toSignal = (i) => {
|
|
31
|
+
const clone = Object.assign(new NullProtoObj(), i)
|
|
32
|
+
|
|
33
|
+
let sig = navEntryMap.get(clone.path)
|
|
34
|
+
if (!sig) {
|
|
35
|
+
sig = signal(clone)
|
|
36
|
+
navEntryMap.set(clone.path, sig)
|
|
37
|
+
} else {
|
|
38
|
+
sig.value = clone
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (clone.type === 'directory') {
|
|
42
|
+
clone.children = clone.children.map(toSignal)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return sig
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const NavTree = ({ nodes, depth }) => (
|
|
49
|
+
<For entries={nodes}>
|
|
50
|
+
{({ item }) => {
|
|
51
|
+
const node = read(item)
|
|
52
|
+
const { routeHref: href, routePath, type, name, isRoot } = node
|
|
53
|
+
const { title } = extract(item, 'title')
|
|
54
|
+
|
|
55
|
+
const isActive = matchCurrentPath(routePath)
|
|
56
|
+
|
|
57
|
+
if (type === 'directory') {
|
|
58
|
+
const label = title.or(name)
|
|
59
|
+
const { children } = extract(item, 'children')
|
|
60
|
+
|
|
61
|
+
const isOpen = $(() => {
|
|
62
|
+
if (depth < 1) {
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
const _active = isActive.value
|
|
66
|
+
const _currentPath = currentPath.value
|
|
67
|
+
return _active || _currentPath.startsWith(routePath)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const header = href ? (
|
|
71
|
+
<a class={isActive.choose('nav-dir-link active', 'nav-dir-link')} href={href}>
|
|
72
|
+
{label}
|
|
73
|
+
</a>
|
|
74
|
+
) : (
|
|
75
|
+
<span class="nav-dir-label">{label}</span>
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<li class={isActive.choose('is-active', null)}>
|
|
80
|
+
<details class="sidebar-collapsible" open={isOpen.choose(true, null)}>
|
|
81
|
+
<summary class="sb-dir-header">{header}</summary>
|
|
82
|
+
<If condition={() => children.value.length}>{() => (
|
|
83
|
+
<ul data-depth={depth + 1}>
|
|
84
|
+
<NavTree nodes={children} depth={depth + 1} />
|
|
85
|
+
</ul>
|
|
86
|
+
)}
|
|
87
|
+
</If>
|
|
88
|
+
</details>
|
|
89
|
+
</li>
|
|
90
|
+
)
|
|
91
|
+
} else {
|
|
92
|
+
const label = title.or(node.isIndex ? 'Home' : name)
|
|
93
|
+
return (
|
|
94
|
+
<li>
|
|
95
|
+
<a class={isActive.choose('active', null)} href={href}>
|
|
96
|
+
{label}
|
|
97
|
+
</a>
|
|
98
|
+
</li>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
}}
|
|
102
|
+
</For>
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
const rootNodes = signal()
|
|
106
|
+
const rootTree = HTMLRenderer.createElement(NavTree, { nodes: rootNodes, depth: 0 })
|
|
107
|
+
|
|
108
|
+
export const renderNavTree = (nodes, path) => {
|
|
109
|
+
currentPath.value = path
|
|
110
|
+
rootNodes.value = nodes.map(toSignal)
|
|
111
|
+
return rootTree
|
|
112
|
+
}
|