methanol 0.0.1 → 0.0.2

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/src/pagefind.js CHANGED
@@ -20,7 +20,7 @@
20
20
 
21
21
  import { access } from 'fs/promises'
22
22
  import { constants } from 'fs'
23
- import { join } from 'path'
23
+ import { join, delimiter } from 'path'
24
24
  import { spawn } from 'child_process'
25
25
  import { state } from './state.js'
26
26
 
@@ -36,6 +36,14 @@ const resolvePagefindBin = async () => {
36
36
  return candidate
37
37
  } catch {}
38
38
  }
39
+ const pathEntries = (process.env.PATH || '').split(delimiter).filter(Boolean)
40
+ for (const entry of pathEntries) {
41
+ const candidate = join(entry, binName)
42
+ try {
43
+ await access(candidate, constants.X_OK)
44
+ return candidate
45
+ } catch {}
46
+ }
39
47
  return null
40
48
  }
41
49
 
@@ -88,7 +96,7 @@ export const runPagefind = async () => {
88
96
  return false
89
97
  }
90
98
  console.log('Running Pagefind search indexing...')
91
- const extraArgs = buildArgsFromOptions(state.PAGEFIND_BUILD_OPTIONS)
99
+ const extraArgs = buildArgsFromOptions(state.PAGEFIND_BUILD)
92
100
  const ok = await runCommand(bin, ['--site', state.DIST_DIR, ...extraArgs], {
93
101
  cwd: state.PROJECT_ROOT
94
102
  })
package/src/pages.js CHANGED
@@ -28,7 +28,7 @@ import { createStageLogger } from './stage-logger.js'
28
28
 
29
29
  const isPageFile = (name) => name.endsWith('.mdx') || name.endsWith('.md')
30
30
  const isInternalPage = (name) => name.startsWith('_') || name.startsWith('.')
31
- const isIgnoredEntry = (name) => name.startsWith('.')
31
+ const isIgnoredEntry = (name) => name.startsWith('.') || name.startsWith('_')
32
32
 
33
33
  const pageMetadataCache = new Map()
34
34
  const pageDerivedCache = new Map()
@@ -41,10 +41,18 @@ const collectLanguagesFromPages = (pages = []) => {
41
41
  if (label == null || label === '') continue
42
42
  const routePath = page.routePath || '/'
43
43
  const href = page.routeHref || routePath || '/'
44
+ const frontmatterCode = page?.frontmatter?.langCode
45
+ const code =
46
+ typeof frontmatterCode === 'string' && frontmatterCode.trim()
47
+ ? frontmatterCode.trim()
48
+ : routePath === '/'
49
+ ? null
50
+ : routePath.replace(/^\/+/, '')
44
51
  languages.set(routePath, {
45
52
  routePath,
46
53
  href,
47
- label: String(label)
54
+ label: String(label),
55
+ code: code || null
48
56
  })
49
57
  }
50
58
  return Array.from(languages.values()).sort((a, b) => a.href.localeCompare(b.href))
@@ -150,7 +158,19 @@ const buildPagesTree = (pages, options = {}) => {
150
158
  const rootPath = normalizeRoutePath(options.rootPath || '/')
151
159
  const rootDir = rootPath === '/' ? '' : rootPath.replace(/^\/+/, '')
152
160
  const includeHiddenRoot = Boolean(options.includeHiddenRoot)
161
+ const currentRoutePath = normalizeRoutePath(options.currentRoutePath || '/')
153
162
  const rootSegments = rootDir ? rootDir.split('/') : []
163
+ const resolveRouteWithinRoot = (routePath) => {
164
+ if (!routePath) return '/'
165
+ if (!rootDir) return routePath
166
+ if (routePath === rootPath) return '/'
167
+ if (routePath.startsWith(`${rootPath}/`)) {
168
+ const stripped = routePath.slice(rootPath.length)
169
+ return stripped.startsWith('/') ? stripped : `/${stripped}`
170
+ }
171
+ return routePath
172
+ }
173
+ const currentRouteWithinRoot = resolveRouteWithinRoot(currentRoutePath)
154
174
  const isUnderRoot = (page) => {
155
175
  if (!rootDir) return true
156
176
  return page.routePath === rootPath || page.routePath.startsWith(`${rootPath}/`)
@@ -179,6 +199,18 @@ const buildPagesTree = (pages, options = {}) => {
179
199
  .filter((page) => page.isIndex && page.dir && page.hidden && !(includeHiddenRoot && page.routePath === rootPath))
180
200
  .map((page) => page.dir)
181
201
  )
202
+ const exposedHiddenDirs = new Set()
203
+ if (currentRoutePath && currentRoutePath !== '/' && hiddenDirs.size) {
204
+ for (const hiddenDir of hiddenDirs) {
205
+ const hiddenRoute = `/${hiddenDir}`
206
+ if (
207
+ currentRouteWithinRoot === hiddenRoute ||
208
+ currentRouteWithinRoot.startsWith(`${hiddenRoute}/`)
209
+ ) {
210
+ exposedHiddenDirs.add(hiddenDir)
211
+ }
212
+ }
213
+ }
182
214
  if (includeHiddenRoot && rootDir) {
183
215
  for (const hiddenDir of Array.from(hiddenDirs)) {
184
216
  if (rootDir === hiddenDir || rootDir.startsWith(`${hiddenDir}/`)) {
@@ -186,6 +218,11 @@ const buildPagesTree = (pages, options = {}) => {
186
218
  }
187
219
  }
188
220
  }
221
+ if (exposedHiddenDirs.size) {
222
+ for (const hiddenDir of exposedHiddenDirs) {
223
+ hiddenDirs.delete(hiddenDir)
224
+ }
225
+ }
189
226
  const isUnderHiddenDir = (dir) => {
190
227
  if (!dir) return false
191
228
  const parts = dir.split('/')
@@ -215,9 +252,25 @@ const buildPagesTree = (pages, options = {}) => {
215
252
  dirs.set(path, dir)
216
253
  return dir
217
254
  }
255
+ const isUnderExposedHiddenDir = (dir) => {
256
+ if (!dir || !exposedHiddenDirs.size) return false
257
+ for (const hiddenDir of exposedHiddenDirs) {
258
+ if (dir === hiddenDir || dir.startsWith(`${hiddenDir}/`)) {
259
+ return true
260
+ }
261
+ }
262
+ return false
263
+ }
218
264
  for (const page of treePages) {
219
265
  if (page.hidden && !(includeHiddenRoot && page.routePath === rootPath)) {
220
- continue
266
+ const isHidden404 = page.routePath === '/404'
267
+ const shouldExposeHidden =
268
+ !isHidden404 &&
269
+ page.hiddenByFrontmatter === true &&
270
+ (page.routePath === currentRoutePath || isUnderExposedHiddenDir(page.dir))
271
+ if (!shouldExposeHidden) {
272
+ continue
273
+ }
221
274
  }
222
275
  if (isUnderHiddenDir(page.dir)) {
223
276
  continue
@@ -352,6 +405,7 @@ export const buildPageEntry = async ({ filePath, pagesDir, source }) => {
352
405
  const derived = pageDerivedCache.get(filePath)
353
406
  const exclude = Boolean(metadata.frontmatter?.exclude)
354
407
  const frontmatterHidden = metadata.frontmatter?.hidden
408
+ const hiddenByFrontmatter = frontmatterHidden === true
355
409
  const isNotFoundPage = routePath === '/404'
356
410
  const hidden = frontmatterHidden === false
357
411
  ? false
@@ -375,6 +429,7 @@ export const buildPageEntry = async ({ filePath, pagesDir, source }) => {
375
429
  date: parseDate(metadata.frontmatter?.date) || parseDate(stats.mtime),
376
430
  isRoot: Boolean(metadata.frontmatter?.isRoot),
377
431
  hidden,
432
+ hiddenByFrontmatter,
378
433
  exclude,
379
434
  content: metadata.content,
380
435
  frontmatter: metadata.frontmatter,
@@ -479,6 +534,38 @@ const resolveRootPath = (routePath, pagesByRoute, pagesByRouteIndex = null) => {
479
534
  return '/'
480
535
  }
481
536
 
537
+ const buildNavSequence = (nodes, pagesByRoute) => {
538
+ const result = []
539
+ const seen = new Set()
540
+ const addEntry = (entry) => {
541
+ if (!entry?.routePath) return
542
+ const key = entry.filePath || entry.routePath
543
+ if (seen.has(key)) return
544
+ seen.add(key)
545
+ result.push(entry)
546
+ }
547
+ const walk = (items = []) => {
548
+ for (const node of items) {
549
+ if (node.type === 'directory') {
550
+ if (node.routePath) {
551
+ const page = pagesByRoute.get(node.routePath) || node.page || null
552
+ if (page) addEntry(page)
553
+ }
554
+ if (node.children?.length) {
555
+ walk(node.children)
556
+ }
557
+ continue
558
+ }
559
+ if (node.type === 'page') {
560
+ const page = pagesByRoute.get(node.routePath) || node
561
+ addEntry(page)
562
+ }
563
+ }
564
+ }
565
+ walk(nodes)
566
+ return result
567
+ }
568
+
482
569
  export const buildPagesContext = async ({ compileAll = true } = {}) => {
483
570
  const logEnabled = state.CURRENT_MODE === 'production' && cli.command === 'build'
484
571
  const stageLogger = createStageLogger(logEnabled)
@@ -555,18 +642,34 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
555
642
  return pagesByRoute.get(routePath) || pagesByRouteIndex.get(routePath) || null
556
643
  }
557
644
  const pagesTreeCache = new Map()
645
+ const navSequenceCache = new Map()
558
646
  const getPagesTree = (routePath) => {
559
647
  const rootPath = resolveRootPath(routePath, pagesByRoute, pagesByRouteIndex)
560
- if (pagesTreeCache.has(rootPath)) {
561
- return pagesTreeCache.get(rootPath)
648
+ const normalizedRoute = normalizeRoutePath(routePath || '/')
649
+ const cacheKey = `${rootPath}::${normalizedRoute}`
650
+ if (pagesTreeCache.has(cacheKey)) {
651
+ return pagesTreeCache.get(cacheKey)
562
652
  }
563
653
  const tree = buildPagesTree(pages, {
564
654
  rootPath,
565
- includeHiddenRoot: rootPath !== '/'
655
+ includeHiddenRoot: rootPath !== '/',
656
+ currentRoutePath: normalizedRoute
566
657
  })
567
- pagesTreeCache.set(rootPath, tree)
658
+ pagesTreeCache.set(cacheKey, tree)
568
659
  return tree
569
660
  }
661
+ const getNavSequence = (routePath) => {
662
+ const rootPath = resolveRootPath(routePath, pagesByRoute, pagesByRouteIndex)
663
+ const normalizedRoute = normalizeRoutePath(routePath || '/')
664
+ const cacheKey = `${rootPath}::${normalizedRoute}`
665
+ if (navSequenceCache.has(cacheKey)) {
666
+ return navSequenceCache.get(cacheKey)
667
+ }
668
+ const tree = getPagesTree(routePath)
669
+ const sequence = buildNavSequence(tree, pagesByRoute)
670
+ navSequenceCache.set(cacheKey, sequence)
671
+ return sequence
672
+ }
570
673
  let pagesTree = getPagesTree('/')
571
674
  const notFound = pagesByRoute.get('/404') || null
572
675
  const languages = collectLanguagesFromPages(pages)
@@ -581,7 +684,7 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
581
684
  pagefind: {
582
685
  enabled: state.PAGEFIND_ENABLED,
583
686
  options: state.PAGEFIND_OPTIONS || null,
584
- buildOptions: state.PAGEFIND_BUILD_OPTIONS || null
687
+ build: state.PAGEFIND_BUILD || null
585
688
  },
586
689
  generatedAt: new Date().toISOString()
587
690
  }
@@ -604,8 +707,35 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
604
707
  },
605
708
  refreshPagesTree: () => {
606
709
  pagesTreeCache.clear()
710
+ navSequenceCache.clear()
607
711
  pagesContext.pagesTree = getPagesTree('/')
608
712
  },
713
+ getSiblings: (routePath, filePath = null) => {
714
+ if (!routePath) return { prev: null, next: null }
715
+ const sequence = getNavSequence(routePath)
716
+ if (!sequence.length) return { prev: null, next: null }
717
+ let index = -1
718
+ if (filePath) {
719
+ index = sequence.findIndex((entry) => entry.filePath === filePath)
720
+ }
721
+ if (index < 0) {
722
+ index = sequence.findIndex((entry) => entry.routePath === routePath)
723
+ }
724
+ if (index < 0) return { prev: null, next: null }
725
+ const toNavEntry = (entry) => {
726
+ if (!entry) return null
727
+ return {
728
+ routePath: entry.routePath,
729
+ routeHref: entry.routeHref || entry.routePath,
730
+ title: entry.title || entry.name || entry.routePath,
731
+ filePath: entry.filePath || null
732
+ }
733
+ }
734
+ return {
735
+ prev: toNavEntry(sequence[index - 1] || null),
736
+ next: toNavEntry(sequence[index + 1] || null)
737
+ }
738
+ },
609
739
  refreshLanguages: () => {
610
740
  pagesContext.languages = collectLanguagesFromPages(pages)
611
741
  pagesContext.getLanguageForRoute = (routePath) =>
@@ -627,7 +757,10 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
627
757
  if (logEnabled) {
628
758
  stageLogger.update(compileToken, `Compiling MDX [${i + 1}/${totalPages}] ${page.filePath}`)
629
759
  }
630
- await compilePageMdx(page, pagesContext)
760
+ await compilePageMdx(page, pagesContext, {
761
+ lazyPagesTree: true,
762
+ refreshPagesTree: false
763
+ })
631
764
  }
632
765
  stageLogger.end(compileToken)
633
766
  pagesTreeCache.clear()
@@ -19,9 +19,10 @@
19
19
  */
20
20
 
21
21
  import { existsSync } from 'fs'
22
- import { dirname, resolve } from 'path'
22
+ import { dirname, resolve, relative, isAbsolute } from 'path'
23
23
  import { isElement } from 'hast-util-is-element'
24
24
  import { visit } from 'unist-util-visit'
25
+ import { state } from '../state.js'
25
26
 
26
27
  const extensionRegex = /\.(mdx?|html)$/i
27
28
 
@@ -44,14 +45,40 @@ const splitHref = (href) => {
44
45
 
45
46
  const resolveCandidate = (baseDir, targetPath) => resolve(baseDir, targetPath)
46
47
 
47
- const hasExistingSource = (baseDir, pathWithoutSuffix, extension) => {
48
- const targetPath = `${pathWithoutSuffix}${extension}`
49
- return existsSync(resolveCandidate(baseDir, targetPath))
48
+ const isWithinRoot = (root, targetPath) => {
49
+ if (!root) return false
50
+ const relPath = relative(root, targetPath)
51
+ if (relPath === '') return true
52
+ if (relPath.startsWith('..') || relPath.startsWith('..\\')) return false
53
+ if (isAbsolute(relPath)) return false
54
+ return true
55
+ }
56
+
57
+ const hasExistingSource = (baseDir, pathWithoutSuffix, extension, root) => {
58
+ const targetPath = resolveCandidate(baseDir, `${pathWithoutSuffix}${extension}`)
59
+ if (root && !isWithinRoot(root, targetPath)) {
60
+ return false
61
+ }
62
+ return existsSync(targetPath)
63
+ }
64
+
65
+ const resolvePagesRoot = (filePath) => {
66
+ const roots = [state.PAGES_DIR, state.THEME_PAGES_DIR].filter(Boolean).map((dir) => resolve(dir))
67
+ if (!roots.length) return null
68
+ if (!filePath) return roots[0]
69
+ const resolvedFile = resolve(filePath)
70
+ for (const root of roots) {
71
+ if (isWithinRoot(root, resolvedFile)) {
72
+ return root
73
+ }
74
+ }
75
+ return roots[0]
50
76
  }
51
77
 
52
78
  export const linkResolve = () => {
53
79
  return (tree, file) => {
54
80
  const baseDir = file?.path ? dirname(file.path) : file?.cwd || process.cwd()
81
+ const pagesRoot = resolvePagesRoot(file?.path || null)
55
82
  visit(tree, (node) => {
56
83
  if (!isElement(node) || node.tagName !== 'a') {
57
84
  return
@@ -71,12 +98,12 @@ export const linkResolve = () => {
71
98
 
72
99
  let shouldStrip = false
73
100
  if (/\.mdx?$/i.test(extension)) {
74
- shouldStrip = hasExistingSource(baseDir, withoutExtension, extension)
101
+ shouldStrip = hasExistingSource(baseDir, withoutExtension, extension, pagesRoot)
75
102
  } else if (/\.html$/i.test(extension)) {
76
103
  shouldStrip =
77
- hasExistingSource(baseDir, withoutExtension, extension) ||
78
- hasExistingSource(baseDir, withoutExtension, '.md') ||
79
- hasExistingSource(baseDir, withoutExtension, '.mdx')
104
+ hasExistingSource(baseDir, withoutExtension, extension, pagesRoot) ||
105
+ hasExistingSource(baseDir, withoutExtension, '.md', pagesRoot) ||
106
+ hasExistingSource(baseDir, withoutExtension, '.mdx', pagesRoot)
80
107
  }
81
108
 
82
109
  if (!shouldStrip) {
package/src/state.js CHANGED
@@ -101,6 +101,19 @@ const withCommonOptions = (y) =>
101
101
  type: 'boolean',
102
102
  default: false
103
103
  })
104
+ .option('code-highlighting', {
105
+ describe: 'Enable or disable code highlighting',
106
+ type: 'string',
107
+ coerce: (value) => {
108
+ if (value == null) return null
109
+ if (value === true || value === '') return true
110
+ if (typeof value === 'boolean') return value
111
+ const normalized = String(value).trim().toLowerCase()
112
+ if (normalized === 'true') return true
113
+ if (normalized === 'false') return false
114
+ return null
115
+ }
116
+ })
104
117
 
105
118
  const parser = yargs(hideBin(process.argv))
106
119
  .scriptName('methanol')
@@ -127,7 +140,8 @@ export const cli = {
127
140
  CLI_ASSETS_DIR: argv.assets || null,
128
141
  CLI_OUTPUT_DIR: argv.output || null,
129
142
  CLI_CONFIG_PATH: argv.config || null,
130
- CLI_SITE_NAME: argv['site-name'] || null
143
+ CLI_SITE_NAME: argv['site-name'] || null,
144
+ CLI_CODE_HIGHLIGHTING: typeof argv['code-highlighting'] === 'boolean' ? argv['code-highlighting'] : null
131
145
  }
132
146
 
133
147
  export const state = {
@@ -149,10 +163,16 @@ export const state = {
149
163
  USER_VITE_CONFIG: null,
150
164
  USER_MDX_CONFIG: null,
151
165
  USER_PUBLIC_OVERRIDE: false,
152
- RESOURCES: [],
166
+ SOURCES: [],
153
167
  PAGEFIND_ENABLED: false,
154
168
  PAGEFIND_OPTIONS: null,
155
- PAGEFIND_BUILD_OPTIONS: null,
169
+ PAGEFIND_BUILD: null,
170
+ USER_PRE_BUILD_HOOKS: [],
171
+ USER_POST_BUILD_HOOKS: [],
172
+ THEME_PRE_BUILD_HOOKS: [],
173
+ THEME_POST_BUILD_HOOKS: [],
174
+ STARRY_NIGHT_ENABLED: false,
175
+ STARRY_NIGHT_OPTIONS: null,
156
176
  CURRENT_MODE: 'production',
157
177
  RESOLVED_MDX_CONFIG: undefined,
158
178
  RESOLVED_VITE_CONFIG: undefined
@@ -146,9 +146,9 @@ export const methanolResolverPlugin = () => {
146
146
  }
147
147
  }
148
148
 
149
- if (state.RESOURCES.length) {
149
+ if (state.SOURCES.length) {
150
150
  const { pathname, search } = new URL(id, 'http://methanol')
151
- for (const entry of state.RESOURCES) {
151
+ for (const entry of state.SOURCES) {
152
152
  const { find, replacement } = entry
153
153
  if (!find || !replacement) continue
154
154
  if (typeof find === 'string') {
@@ -0,0 +1,95 @@
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 { signal, $ } from 'refui'
22
+
23
+ const ACCENTS = [
24
+ { id: 'default', label: 'Amber', color: '#ffa000' },
25
+ { id: 'rose', label: 'Rose', color: '#f43f5e' },
26
+ { id: 'blue', label: 'Indigo', color: '#818cf8' },
27
+ { id: 'green', label: 'Teal', color: '#2dd4bf' },
28
+ { id: 'purple', label: 'Violet', color: '#a78bfa' }
29
+ ]
30
+
31
+ export default function () {
32
+ const currentAccent = signal('default')
33
+ const isOpen = signal(false)
34
+
35
+ // Initialize theme from localStorage
36
+ if (typeof window !== 'undefined') {
37
+ const saved = localStorage.getItem('methanol-accent')
38
+ if (saved && ACCENTS.some((a) => a.id === saved)) {
39
+ currentAccent.value = saved
40
+ if (saved !== 'default') {
41
+ document.documentElement.classList.add(`accent-${saved}`)
42
+ }
43
+ }
44
+
45
+ // Close popup when clicking outside
46
+ document.addEventListener('click', (e) => {
47
+ if (!e.target.closest('.theme-switch-wrapper')) {
48
+ isOpen.value = false
49
+ }
50
+ })
51
+ }
52
+
53
+ const setAccent = (id) => {
54
+ const oldId = currentAccent.value
55
+
56
+ // Remove old
57
+ if (oldId !== 'default') {
58
+ document.documentElement.classList.remove(`accent-${oldId}`)
59
+ }
60
+
61
+ // Add new
62
+ if (id !== 'default') {
63
+ document.documentElement.classList.add(`accent-${id}`)
64
+ }
65
+
66
+ currentAccent.value = id
67
+ localStorage.setItem('methanol-accent', id)
68
+ isOpen.value = false
69
+ }
70
+
71
+ const togglePopup = () => {
72
+ isOpen.value = !isOpen.value
73
+ }
74
+
75
+ return (
76
+ <div class="theme-switch-container">
77
+ <div class="theme-switch-wrapper">
78
+ <div class={$(() => `accent-popup ${isOpen.value ? 'open' : ''}`)}>
79
+ {ACCENTS.map((accent) => (
80
+ <button
81
+ class={$(() => `accent-option ${currentAccent.value === accent.id ? 'active' : ''}`)}
82
+ on:click={() => setAccent(accent.id)}
83
+ >
84
+ <span class="option-circle" style={`background-color: ${accent.color}`}></span>
85
+ {accent.label}
86
+ </button>
87
+ ))}
88
+ </div>
89
+ <button class="theme-switch-btn" on:click={togglePopup} attr:aria-label="Select accent color">
90
+ <div class="accent-circle"></div>
91
+ </button>
92
+ </div>
93
+ </div>
94
+ )
95
+ }
@@ -0,0 +1,23 @@
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 () {
22
+ // render nothing on server side
23
+ }
@@ -87,7 +87,7 @@ export default function () {
87
87
 
88
88
  return (
89
89
  <div class="theme-switch-container">
90
- <button class="theme-switch-btn" on:click={toggle} aria-label="Toggle theme">
90
+ <button class="theme-switch-btn" on:click={toggle} attr:aria-label="Toggle theme">
91
91
  <CurrentIcon />
92
92
  </button>
93
93
  </div>