@mgks/docmd 0.1.3 → 0.2.0
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/.github/workflows/deploy-docmd.yml +2 -2
- package/README.md +2 -4
- package/assets/css/welcome.css +62 -358
- package/assets/images/preview-dark-1.webp +0 -0
- package/assets/images/preview-dark-2.webp +0 -0
- package/assets/images/preview-dark-3.webp +0 -0
- package/assets/images/preview-light-1.webp +0 -0
- package/assets/images/preview-light-2.webp +0 -0
- package/assets/images/preview-light-3.webp +0 -0
- package/config.js +35 -4
- package/docs/cli-commands.md +1 -2
- package/docs/configuration.md +104 -23
- package/docs/content/containers/buttons.md +88 -0
- package/docs/content/containers/callouts.md +154 -0
- package/docs/content/containers/cards.md +93 -0
- package/docs/content/containers/index.md +35 -0
- package/docs/content/containers/nested-containers.md +329 -0
- package/docs/content/containers/steps.md +175 -0
- package/docs/content/containers/tabs.md +228 -0
- package/docs/content/frontmatter.md +4 -4
- package/docs/content/index.md +5 -4
- package/docs/content/markdown-syntax.md +4 -4
- package/docs/content/no-style-example.md +1 -1
- package/docs/contributing.md +7 -0
- package/docs/deployment.md +22 -31
- package/docs/getting-started/basic-usage.md +3 -2
- package/docs/getting-started/index.md +3 -3
- package/docs/getting-started/installation.md +1 -1
- package/docs/index.md +14 -20
- package/docs/plugins/seo.md +2 -0
- package/docs/plugins/sitemap.md +1 -1
- package/docs/theming/assets-management.md +1 -1
- package/docs/theming/available-themes.md +45 -52
- package/docs/theming/light-dark-mode.md +12 -3
- package/package.json +9 -3
- package/src/assets/css/docmd-main.css +935 -573
- package/src/assets/css/docmd-theme-retro.css +812 -0
- package/src/assets/css/docmd-theme-ruby.css +619 -0
- package/src/assets/css/docmd-theme-sky.css +606 -605
- package/src/assets/js/docmd-image-lightbox.js +1 -3
- package/src/assets/js/docmd-main.js +97 -0
- package/src/commands/build.js +1 -1
- package/src/commands/dev.js +5 -2
- package/src/commands/init.js +21 -1
- package/src/core/file-processor.js +626 -363
- package/src/core/html-generator.js +20 -30
- package/src/plugins/seo.js +4 -0
- package/src/templates/layout.ejs +34 -8
- package/assets/images/preview-dark-1.png +0 -0
- package/assets/images/preview-dark-2.png +0 -0
- package/assets/images/preview-dark-3.png +0 -0
- package/assets/images/preview-light-1.png +0 -0
- package/assets/images/preview-light-2.png +0 -0
- package/assets/images/preview-light-3.png +0 -0
- package/docs/content/custom-containers.md +0 -129
- package/src/assets/js/docmd-theme-toggle.js +0 -59
|
@@ -23,14 +23,10 @@ async function processPluginHooks(config, pageData, relativePathToRoot) {
|
|
|
23
23
|
|
|
24
24
|
// 2. Theme CSS (built-in handling for theme.name)
|
|
25
25
|
if (config.theme && config.theme.name && config.theme.name !== 'default') {
|
|
26
|
-
// Assumes theme CSS files are like 'docmd-theme-yourthemename.css' in assets/css
|
|
27
26
|
const themeCssPath = `assets/css/docmd-theme-${config.theme.name}.css`;
|
|
28
|
-
// Check if theme file exists before linking (optional, good practice)
|
|
29
|
-
// For now, assume it will exist if specified.
|
|
30
27
|
themeCssLinkHtml = ` <link rel="stylesheet" href="${relativePathToRoot}${themeCssPath}">\n`;
|
|
31
28
|
}
|
|
32
29
|
|
|
33
|
-
|
|
34
30
|
// 3. SEO Plugin (if configured)
|
|
35
31
|
if (config.plugins?.seo) {
|
|
36
32
|
metaTagsHtml += generateSeoMetaTags(config, pageData, relativePathToRoot);
|
|
@@ -43,9 +39,6 @@ async function processPluginHooks(config, pageData, relativePathToRoot) {
|
|
|
43
39
|
pluginBodyScriptsHtml += analyticsScripts.bodyScriptsHtml;
|
|
44
40
|
}
|
|
45
41
|
|
|
46
|
-
// Future: Loop through a more generic plugin array if you evolve the system
|
|
47
|
-
// for (const plugin of config.activePlugins) { /* plugin.runHook('meta', ...) */ }
|
|
48
|
-
|
|
49
42
|
return {
|
|
50
43
|
metaTagsHtml,
|
|
51
44
|
faviconLinkHtml,
|
|
@@ -58,11 +51,13 @@ async function processPluginHooks(config, pageData, relativePathToRoot) {
|
|
|
58
51
|
|
|
59
52
|
async function generateHtmlPage(templateData) {
|
|
60
53
|
const {
|
|
61
|
-
content,
|
|
54
|
+
content, siteTitle, navigationHtml,
|
|
62
55
|
relativePathToRoot, config, frontmatter, outputPath,
|
|
63
56
|
prevPage, nextPage, currentPagePath, headings
|
|
64
57
|
} = templateData;
|
|
65
58
|
|
|
59
|
+
const pageTitle = frontmatter.title; // Get title from frontmatter (already processed in file-processor)
|
|
60
|
+
|
|
66
61
|
// Process plugins to get their HTML contributions
|
|
67
62
|
const pluginOutputs = await processPluginHooks(
|
|
68
63
|
config,
|
|
@@ -75,16 +70,9 @@ async function generateHtmlPage(templateData) {
|
|
|
75
70
|
footerHtml = mdInstance.renderInline(config.footer);
|
|
76
71
|
}
|
|
77
72
|
|
|
78
|
-
// Determine which template to use based on frontmatter
|
|
79
73
|
let templateName = 'layout.ejs';
|
|
80
74
|
if (frontmatter.noStyle === true) {
|
|
81
75
|
templateName = 'no-style.ejs';
|
|
82
|
-
|
|
83
|
-
// For no-style pages, ensure we're passing the raw HTML content
|
|
84
|
-
// without any additional processing or escaping
|
|
85
|
-
if (content.includes('<') || content.includes('>')) {
|
|
86
|
-
console.warn(`⚠️ Warning: HTML content in no-style page appears to be escaped. This may cause rendering issues.`);
|
|
87
|
-
}
|
|
88
76
|
}
|
|
89
77
|
|
|
90
78
|
const layoutTemplatePath = path.join(__dirname, '..', 'templates', templateName);
|
|
@@ -93,42 +81,45 @@ async function generateHtmlPage(templateData) {
|
|
|
93
81
|
}
|
|
94
82
|
const layoutTemplate = await fs.readFile(layoutTemplatePath, 'utf8');
|
|
95
83
|
|
|
96
|
-
// Determine if this is an active page for TOC display
|
|
97
|
-
// The currentPagePath exists and has content
|
|
98
84
|
const isActivePage = currentPagePath && content && content.trim().length > 0;
|
|
99
85
|
|
|
100
86
|
const ejsData = {
|
|
101
87
|
content,
|
|
102
|
-
pageTitle
|
|
103
|
-
description: frontmatter.description,
|
|
88
|
+
pageTitle, // Pass the potentially undefined title
|
|
89
|
+
description: frontmatter.description,
|
|
104
90
|
siteTitle,
|
|
105
91
|
navigationHtml,
|
|
106
92
|
defaultMode: config.theme?.defaultMode || 'light',
|
|
107
93
|
relativePathToRoot,
|
|
108
94
|
logo: config.logo,
|
|
95
|
+
sidebarConfig: {
|
|
96
|
+
collapsible: config.sidebar?.collapsible ?? false,
|
|
97
|
+
defaultCollapsed: config.sidebar?.defaultCollapsed ?? false,
|
|
98
|
+
},
|
|
109
99
|
theme: config.theme,
|
|
110
100
|
customCssFiles: config.theme?.customCss || [],
|
|
111
101
|
customJsFiles: config.customJs || [],
|
|
102
|
+
sponsor: config.sponsor,
|
|
112
103
|
footer: config.footer,
|
|
113
104
|
footerHtml,
|
|
114
105
|
renderIcon,
|
|
115
106
|
prevPage,
|
|
116
107
|
nextPage,
|
|
117
|
-
currentPagePath,
|
|
118
|
-
headings: headings || [],
|
|
119
|
-
isActivePage,
|
|
120
|
-
frontmatter,
|
|
121
|
-
...pluginOutputs,
|
|
108
|
+
currentPagePath,
|
|
109
|
+
headings: headings || [],
|
|
110
|
+
isActivePage,
|
|
111
|
+
frontmatter,
|
|
112
|
+
...pluginOutputs,
|
|
122
113
|
};
|
|
123
114
|
|
|
124
115
|
try {
|
|
125
116
|
return ejs.render(layoutTemplate, ejsData, {
|
|
126
|
-
filename: layoutTemplatePath
|
|
117
|
+
filename: layoutTemplatePath
|
|
127
118
|
});
|
|
128
119
|
} catch (e) {
|
|
129
120
|
console.error(`❌ Error rendering EJS template for ${outputPath}: ${e.message}`);
|
|
130
|
-
console.error("EJS Data:", JSON.stringify(ejsData, null, 2).substring(0, 1000) + "...");
|
|
131
|
-
throw e;
|
|
121
|
+
console.error("EJS Data:", JSON.stringify(ejsData, null, 2).substring(0, 1000) + "...");
|
|
122
|
+
throw e;
|
|
132
123
|
}
|
|
133
124
|
}
|
|
134
125
|
|
|
@@ -139,17 +130,16 @@ async function generateNavigationHtml(navItems, currentPagePath, relativePathToR
|
|
|
139
130
|
}
|
|
140
131
|
const navTemplate = await fs.readFile(navTemplatePath, 'utf8');
|
|
141
132
|
|
|
142
|
-
// Make renderIcon available to the EJS template
|
|
143
133
|
const ejsHelpers = { renderIcon };
|
|
144
134
|
|
|
145
135
|
return ejs.render(navTemplate, {
|
|
146
136
|
navItems,
|
|
147
137
|
currentPagePath,
|
|
148
138
|
relativePathToRoot,
|
|
149
|
-
config,
|
|
139
|
+
config,
|
|
150
140
|
...ejsHelpers
|
|
151
141
|
}, {
|
|
152
|
-
filename: navTemplatePath
|
|
142
|
+
filename: navTemplatePath
|
|
153
143
|
});
|
|
154
144
|
}
|
|
155
145
|
|
package/src/plugins/seo.js
CHANGED
|
@@ -19,6 +19,10 @@ function generateSeoMetaTags(config, pageData, relativePathToRoot) {
|
|
|
19
19
|
|
|
20
20
|
metaTagsHtml += ` <meta name="description" content="${description}">\n`;
|
|
21
21
|
|
|
22
|
+
// Canonical URL - use permalink first, fall back to canonicalUrl, then default to pageUrl
|
|
23
|
+
const canonicalUrl = frontmatter.permalink || frontmatter.canonicalUrl || pageUrl;
|
|
24
|
+
metaTagsHtml += ` <link rel="canonical" href="${canonicalUrl}">\n`;
|
|
25
|
+
|
|
22
26
|
// Open Graph
|
|
23
27
|
metaTagsHtml += ` <meta property="og:title" content="${pageTitle} | ${siteTitle}">\n`;
|
|
24
28
|
metaTagsHtml += ` <meta property="og:description" content="${description}">\n`;
|
package/src/templates/layout.ejs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
<%- metaTagsHtml || '' %> <%# SEO Plugin Meta Tags %>
|
|
8
8
|
|
|
9
|
-
<title><%= pageTitle %>
|
|
9
|
+
<title><%= pageTitle %> : <%= siteTitle %></title>
|
|
10
10
|
<% if (description && !(metaTagsHtml && metaTagsHtml.includes('name="description"'))) { %>
|
|
11
11
|
<meta name="description" content="<%= description %>">
|
|
12
12
|
<% } %>
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
<%- pluginHeadScriptsHtml || '' %> <%# Plugin specific head scripts (e.g., Analytics) %>
|
|
29
29
|
</head>
|
|
30
|
-
<body data-theme="<%= defaultMode %>">
|
|
30
|
+
<body class="<%= sidebarConfig.collapsible ? 'sidebar-collapsible' : 'sidebar-not-collapsible' %>" data-theme="<%= defaultMode %>" data-default-collapsed="<%= sidebarConfig.defaultCollapsed %>">
|
|
31
31
|
<aside class="sidebar">
|
|
32
32
|
<div class="sidebar-header">
|
|
33
33
|
<% if (logo && logo.light && logo.dark) { %>
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
<% } %>
|
|
41
41
|
</div>
|
|
42
42
|
<%- navigationHtml %>
|
|
43
|
-
<% if (theme && theme.enableModeToggle) { %>
|
|
43
|
+
<% if (theme && theme.enableModeToggle && theme.positionMode !== 'top') { %>
|
|
44
44
|
<button id="theme-toggle-button" aria-label="Toggle theme" class="theme-toggle-button">
|
|
45
45
|
<%# renderIcon is available in the global EJS scope from html-generator %>
|
|
46
46
|
<%- renderIcon('sun', { class: 'icon-sun' }) %>
|
|
@@ -49,9 +49,25 @@
|
|
|
49
49
|
<% } %>
|
|
50
50
|
</aside>
|
|
51
51
|
<div class="main-content-wrapper">
|
|
52
|
-
<
|
|
53
|
-
<
|
|
54
|
-
|
|
52
|
+
<div class="page-header">
|
|
53
|
+
<div class="header-left">
|
|
54
|
+
<% if (sidebarConfig.collapsible) { %>
|
|
55
|
+
<button id="sidebar-toggle-button" class="sidebar-toggle-button" aria-label="Toggle Sidebar">
|
|
56
|
+
<%- renderIcon('panel-left-close') %>
|
|
57
|
+
</button>
|
|
58
|
+
<% } %>
|
|
59
|
+
<h1><%= pageTitle %></h1>
|
|
60
|
+
</div>
|
|
61
|
+
<% if (theme && theme.enableModeToggle && theme.positionMode === 'top') { %>
|
|
62
|
+
<div class="header-right">
|
|
63
|
+
<button id="theme-toggle-button" aria-label="Toggle theme" class="theme-toggle-button theme-toggle-header">
|
|
64
|
+
<%# renderIcon is available in the global EJS scope from html-generator %>
|
|
65
|
+
<%- renderIcon('sun', { class: 'icon-sun' }) %>
|
|
66
|
+
<%- renderIcon('moon', { class: 'icon-moon' }) %>
|
|
67
|
+
</button>
|
|
68
|
+
</div>
|
|
69
|
+
<% } %>
|
|
70
|
+
</div>
|
|
55
71
|
<main class="content-area">
|
|
56
72
|
<div class="content-layout">
|
|
57
73
|
<div class="main-content">
|
|
@@ -106,11 +122,21 @@
|
|
|
106
122
|
</footer>
|
|
107
123
|
</div>
|
|
108
124
|
|
|
109
|
-
<script src="<%= relativePathToRoot %>assets/js/docmd-
|
|
125
|
+
<script src="<%= relativePathToRoot %>assets/js/docmd-main.js"></script>
|
|
126
|
+
|
|
110
127
|
<% (customJsFiles || []).forEach(jsFile => { %>
|
|
111
128
|
<script src="<%= relativePathToRoot %><%- jsFile.startsWith('/') ? jsFile.substring(1) : jsFile %>"></script>
|
|
112
129
|
<% }); %>
|
|
113
130
|
|
|
114
|
-
<%- pluginBodyScriptsHtml || '' %>
|
|
131
|
+
<%- pluginBodyScriptsHtml || '' %>
|
|
132
|
+
|
|
133
|
+
<% if (sponsor && sponsor.enabled) { %>
|
|
134
|
+
<div class="sponsor-ribbon">
|
|
135
|
+
<a href="<%= sponsor.link %>" target="_blank" rel="noopener noreferrer" class="sponsor-link">
|
|
136
|
+
<%- renderIcon('heart', { class: 'sponsor-icon' }) %>
|
|
137
|
+
<span class="sponsor-text"><%= sponsor.title %></span>
|
|
138
|
+
</a>
|
|
139
|
+
</div>
|
|
140
|
+
<% } %>
|
|
115
141
|
</body>
|
|
116
142
|
</html>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Custom Containers"
|
|
3
|
-
description: "Enhance your documentation with special components like callouts, cards, and steps using docmd's custom container syntax."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Custom Containers
|
|
7
|
-
|
|
8
|
-
`docmd` provides a simple syntax for adding richer, pre-styled components to your Markdown content using "custom containers." These are powered by the `markdown-it-container` plugin.
|
|
9
|
-
|
|
10
|
-
The general syntax is:
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
::: containerName [optionalTitleOrType]
|
|
14
|
-
Content for the container goes here.
|
|
15
|
-
It can span **multiple lines** and include other *Markdown* elements.
|
|
16
|
-
:::
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Callouts
|
|
20
|
-
|
|
21
|
-
Callouts are useful for highlighting important information, warnings, tips, or notes.
|
|
22
|
-
|
|
23
|
-
**Syntax:**
|
|
24
|
-
```
|
|
25
|
-
::: callout type
|
|
26
|
-
Content of the callout.
|
|
27
|
-
:::
|
|
28
|
-
```
|
|
29
|
-
* `type`: Can be `info`, `warning`, `tip`, or `danger`.
|
|
30
|
-
|
|
31
|
-
**Examples:**
|
|
32
|
-
|
|
33
|
-
::: callout info
|
|
34
|
-
This is an informational message. It's good for general notes or neutral supplementary details.
|
|
35
|
-
:::
|
|
36
|
-
|
|
37
|
-
::: callout warning
|
|
38
|
-
**Watch out!** This indicates something that requires caution or might lead to unexpected results if ignored.
|
|
39
|
-
:::
|
|
40
|
-
|
|
41
|
-
::: callout tip
|
|
42
|
-
Here's a helpful tip or a best practice suggestion to improve a process or understanding.
|
|
43
|
-
:::
|
|
44
|
-
|
|
45
|
-
::: callout danger
|
|
46
|
-
**Critical!** This highlights a potential risk, a destructive action, or something that must be avoided.
|
|
47
|
-
:::
|
|
48
|
-
|
|
49
|
-
## Cards
|
|
50
|
-
|
|
51
|
-
Cards provide a visually distinct block for grouping related content, often with a title.
|
|
52
|
-
|
|
53
|
-
**Syntax:**
|
|
54
|
-
```
|
|
55
|
-
::: card Optional Card Title
|
|
56
|
-
The main body content of the card.
|
|
57
|
-
Supports **Markdown** formatting.
|
|
58
|
-
- List item 1
|
|
59
|
-
- List item 2
|
|
60
|
-
:::
|
|
61
|
-
```
|
|
62
|
-
* `Optional Card Title`: If provided after `card`, it becomes the title of the card.
|
|
63
|
-
|
|
64
|
-
**Example:**
|
|
65
|
-
|
|
66
|
-
::: card My Feature Overview
|
|
67
|
-
This card describes an amazing feature.
|
|
68
|
-
* It's easy to use.
|
|
69
|
-
* It solves a common problem.
|
|
70
|
-
|
|
71
|
-
Learn more by reading the full guide.
|
|
72
|
-
:::
|
|
73
|
-
|
|
74
|
-
::: card
|
|
75
|
-
This is a card without an explicit title. The content starts directly.
|
|
76
|
-
Ideal for small, self-contained snippets.
|
|
77
|
-
:::
|
|
78
|
-
|
|
79
|
-
## Steps
|
|
80
|
-
|
|
81
|
-
The "steps" container is designed for presenting a sequence of actions or instructions, often in a numbered or ordered fashion.
|
|
82
|
-
|
|
83
|
-
**Syntax:**
|
|
84
|
-
|
|
85
|
-
```
|
|
86
|
-
::: steps
|
|
87
|
-
> 1. **First Step Title:**
|
|
88
|
-
> Description of the first step.
|
|
89
|
-
|
|
90
|
-
> 2. **Second Step Title:**
|
|
91
|
-
> Description of the second step.
|
|
92
|
-
|
|
93
|
-
> *. **Using Asterisk:**
|
|
94
|
-
> Alternative numbering style.
|
|
95
|
-
|
|
96
|
-
> **No Number:**
|
|
97
|
-
> Without a number.
|
|
98
|
-
:::
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
**How it works:**
|
|
102
|
-
|
|
103
|
-
The steps container uses blockquotes (`>`) to define individual steps. Start each step with a number or asterisk followed by a period, then the step title in bold. The content after the title becomes the step content.
|
|
104
|
-
|
|
105
|
-
**Example:**
|
|
106
|
-
|
|
107
|
-
::: steps
|
|
108
|
-
> 1. **Install Dependencies:**
|
|
109
|
-
> First, make sure you have Node.js and npm installed. Then, run:
|
|
110
|
-
>
|
|
111
|
-
> ```bash
|
|
112
|
-
> npm install my-package
|
|
113
|
-
> ```
|
|
114
|
-
|
|
115
|
-
> 2. **Configure the Settings:**
|
|
116
|
-
> Open the `config.json` file and update the `apiKey` with your credentials.
|
|
117
|
-
|
|
118
|
-
> 3. **Run the Application:**
|
|
119
|
-
> Start the application using:
|
|
120
|
-
>
|
|
121
|
-
> ```bash
|
|
122
|
-
> npm start
|
|
123
|
-
> ```
|
|
124
|
-
>
|
|
125
|
-
> You should see "Application started successfully!" in your console.
|
|
126
|
-
:::
|
|
127
|
-
:::
|
|
128
|
-
|
|
129
|
-
These custom containers allow you to create more engaging and structured documentation without needing to write custom HTML or CSS for common patterns. Experiment with them to see how they can improve your content's clarity and visual appeal.
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// src/assets/js/theme-toggle.js
|
|
2
|
-
function applyTheme(theme, isInitialLoad = false) {
|
|
3
|
-
document.body.setAttribute('data-theme', theme);
|
|
4
|
-
if (!isInitialLoad) {
|
|
5
|
-
localStorage.setItem('docmd-theme', theme);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
// Change highlight.js theme dynamically (if separate themes are loaded)
|
|
9
|
-
const highlightThemeLink = document.getElementById('highlight-theme');
|
|
10
|
-
if (highlightThemeLink) {
|
|
11
|
-
const isDark = theme.includes('dark');
|
|
12
|
-
const isLight = theme.includes('light');
|
|
13
|
-
const currentHref = highlightThemeLink.href;
|
|
14
|
-
|
|
15
|
-
if (isDark && currentHref.includes('docmd-highlight-light.css')) {
|
|
16
|
-
highlightThemeLink.href = currentHref.replace('docmd-highlight-light.css', 'docmd-highlight-dark.css');
|
|
17
|
-
} else if (isLight && currentHref.includes('docmd-highlight-dark.css')) {
|
|
18
|
-
highlightThemeLink.href = currentHref.replace('docmd-highlight-dark.css', 'docmd-highlight-light.css');
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function getInitialTheme() {
|
|
24
|
-
const storedTheme = localStorage.getItem('docmd-theme');
|
|
25
|
-
if (storedTheme) {
|
|
26
|
-
return storedTheme;
|
|
27
|
-
}
|
|
28
|
-
// The server sets the initial data-theme based on config.theme.defaultMode.
|
|
29
|
-
// We respect localStorage first, then what the server provided.
|
|
30
|
-
// Optionally, could check prefers-color-scheme if neither is set, but defaultMode covers this.
|
|
31
|
-
return document.body.getAttribute('data-theme') || 'light';
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
35
|
-
// Apply initial theme (respecting localStorage over server-rendered if set)
|
|
36
|
-
const initialTheme = getInitialTheme();
|
|
37
|
-
applyTheme(initialTheme, true); // true indicates it's the initial load
|
|
38
|
-
|
|
39
|
-
const themeToggleButton = document.getElementById('theme-toggle-button');
|
|
40
|
-
if (themeToggleButton) {
|
|
41
|
-
themeToggleButton.addEventListener('click', () => {
|
|
42
|
-
let currentTheme = document.body.getAttribute('data-theme');
|
|
43
|
-
|
|
44
|
-
// Handle both regular themes and sky theme variants
|
|
45
|
-
let newTheme;
|
|
46
|
-
if (currentTheme === 'light') {
|
|
47
|
-
newTheme = 'dark';
|
|
48
|
-
} else if (currentTheme === 'dark') {
|
|
49
|
-
newTheme = 'light';
|
|
50
|
-
} else if (currentTheme === 'sky-light') {
|
|
51
|
-
newTheme = 'sky-dark';
|
|
52
|
-
} else if (currentTheme === 'sky-dark') {
|
|
53
|
-
newTheme = 'sky-light';
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
applyTheme(newTheme); // isInitialLoad is false here, so it saves to localStorage
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
});
|