@mgks/docmd 0.2.0 → 0.2.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.
@@ -1,69 +1,117 @@
1
- // src/plugins/seo.js
1
+ // Source file from the docmd project — https://github.com/mgks/docmd
2
+
3
+ /*
4
+ * Generate SEO meta tags for a page
5
+ */
6
+
2
7
  function generateSeoMetaTags(config, pageData, relativePathToRoot) {
3
- let metaTagsHtml = '';
4
- const { frontmatter, outputPath } = pageData;
5
-
6
- if (frontmatter.noindex) {
7
- metaTagsHtml += ' <meta name="robots" content="noindex">\n';
8
- return metaTagsHtml; // No other SEO tags if noindex
9
- }
10
-
11
- const siteTitle = config.siteTitle;
12
- const pageTitle = frontmatter.title || 'Untitled'; // Ensure pageTitle is always defined
13
- const description = frontmatter.description || config.plugins?.seo?.defaultDescription || '';
14
-
15
- // Construct pageUrl - ensure siteUrl in config has no trailing slash
16
- const siteUrl = config.siteUrl ? config.siteUrl.replace(/\/$/, '') : '';
17
- const pageSegment = outputPath.replace(/index\.html$/, '').replace(/\.html$/, '');
18
- const pageUrl = `${siteUrl}${pageSegment.startsWith('/') ? pageSegment : '/' + pageSegment}`;
19
-
20
- metaTagsHtml += ` <meta name="description" content="${description}">\n`;
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
-
26
- // Open Graph
27
- metaTagsHtml += ` <meta property="og:title" content="${pageTitle} | ${siteTitle}">\n`;
28
- metaTagsHtml += ` <meta property="og:description" content="${description}">\n`;
29
- metaTagsHtml += ` <meta property="og:url" content="${pageUrl}">\n`;
30
- metaTagsHtml += ` <meta property="og:site_name" content="${config.plugins?.seo?.openGraph?.siteName || siteTitle}">\n`;
31
-
32
- const ogImage = frontmatter.image || frontmatter.ogImage || config.plugins?.seo?.openGraph?.defaultImage;
33
- if (ogImage) {
34
- const ogImageUrl = ogImage.startsWith('http') ? ogImage : `${siteUrl}${ogImage.startsWith('/') ? ogImage : '/' + ogImage}`;
35
- metaTagsHtml += ` <meta property="og:image" content="${ogImageUrl}">\n`;
36
- }
37
- metaTagsHtml += ` <meta property="og:type" content="${frontmatter.ogType || 'website'}">\n`;
38
-
39
- // Twitter Card
40
- const twitterCardType = frontmatter.twitterCard || config.plugins?.seo?.twitter?.cardType || 'summary';
41
- metaTagsHtml += ` <meta name="twitter:card" content="${twitterCardType}">\n`;
42
- if (config.plugins?.seo?.twitter?.siteUsername) {
43
- metaTagsHtml += ` <meta name="twitter:site" content="${config.plugins.seo.twitter.siteUsername}">\n`;
44
- }
45
- const twitterCreator = frontmatter.twitterCreator || config.plugins?.seo?.twitter?.creatorUsername;
46
- if (twitterCreator) {
47
- metaTagsHtml += ` <meta name="twitter:creator" content="${twitterCreator}">\n`;
48
- }
49
- // Twitter title, description, image often fallback to OG tags if not explicitly set by Twitter.
50
- // For explicitness:
51
- metaTagsHtml += ` <meta name="twitter:title" content="${pageTitle} | ${siteTitle}">\n`;
52
- metaTagsHtml += ` <meta name="twitter:description" content="${description}">\n`;
53
- if (ogImage) { // Re-use ogImage for twitter:image if not specified differently
54
- const twitterImageUrl = ogImage.startsWith('http') ? ogImage : `${siteUrl}${ogImage.startsWith('/') ? ogImage : '/' + ogImage}`;
55
- metaTagsHtml += ` <meta name="twitter:image" content="${twitterImageUrl}">\n`;
56
- }
57
-
58
-
59
- // Keywords (optional, less impact nowadays)
60
- if (frontmatter.keywords) {
61
- const keywordsString = Array.isArray(frontmatter.keywords) ? frontmatter.keywords.join(', ') : frontmatter.keywords;
62
- metaTagsHtml += ` <meta name="keywords" content="${keywordsString}">\n`;
8
+ let metaTagsHtml = '';
9
+ const { frontmatter, outputPath } = pageData;
10
+ const seoFrontmatter = frontmatter.seo || {}; // Use nested seo object, fallback to empty
11
+
12
+ if (frontmatter.noindex || seoFrontmatter.noindex) {
13
+ metaTagsHtml += ' <meta name="robots" content="noindex">\n';
14
+ return metaTagsHtml; // No other SEO tags if noindex
15
+ }
16
+
17
+ const siteTitle = config.siteTitle;
18
+ const pageTitle = frontmatter.title || 'Untitled';
19
+ const description = seoFrontmatter.description || frontmatter.description || config.plugins?.seo?.defaultDescription || '';
20
+
21
+ const siteUrl = config.siteUrl ? config.siteUrl.replace(/\/$/, '') : '';
22
+ const pageSegment = outputPath.replace(/index\.html$/, '').replace(/\.html$/, '');
23
+ const pageUrl = `${siteUrl}${pageSegment.startsWith('/') ? pageSegment : '/' + pageSegment}`;
24
+
25
+ metaTagsHtml += ` <meta name="description" content="${description}">\n`;
26
+
27
+ const canonicalUrl = seoFrontmatter.permalink || frontmatter.permalink || seoFrontmatter.canonicalUrl || frontmatter.canonicalUrl || pageUrl;
28
+ metaTagsHtml += ` <link rel="canonical" href="${canonicalUrl}">\n`;
29
+
30
+ // Open Graph
31
+ metaTagsHtml += ` <meta property="og:title" content="${pageTitle} | ${siteTitle}">\n`;
32
+ metaTagsHtml += ` <meta property="og:description" content="${description}">\n`;
33
+ metaTagsHtml += ` <meta property="og:url" content="${pageUrl}">\n`;
34
+ metaTagsHtml += ` <meta property="og:site_name" content="${config.plugins?.seo?.openGraph?.siteName || siteTitle}">\n`;
35
+
36
+ const ogImage = seoFrontmatter.image || frontmatter.image || seoFrontmatter.ogImage || frontmatter.ogImage || config.plugins?.seo?.openGraph?.defaultImage;
37
+ if (ogImage) {
38
+ const ogImageUrl = ogImage.startsWith('http') ? ogImage : `${siteUrl}${ogImage.startsWith('/') ? ogImage : '/' + ogImage}`;
39
+ metaTagsHtml += ` <meta property="og:image" content="${ogImageUrl}">\n`;
40
+ }
41
+ metaTagsHtml += ` <meta property="og:type" content="${seoFrontmatter.ogType || frontmatter.ogType || 'website'}">\n`;
42
+
43
+ // Twitter Card
44
+ const twitterCardType = seoFrontmatter.twitterCard || frontmatter.twitterCard || config.plugins?.seo?.twitter?.cardType || 'summary';
45
+ metaTagsHtml += ` <meta name="twitter:card" content="${twitterCardType}">\n`;
46
+ if (config.plugins?.seo?.twitter?.siteUsername) {
47
+ metaTagsHtml += ` <meta name="twitter:site" content="${config.plugins.seo.twitter.siteUsername}">\n`;
48
+ }
49
+ const twitterCreator = seoFrontmatter.twitterCreator || frontmatter.twitterCreator || config.plugins?.seo?.twitter?.creatorUsername;
50
+ if (twitterCreator) {
51
+ metaTagsHtml += ` <meta name="twitter:creator" content="${twitterCreator}">\n`;
52
+ }
53
+ metaTagsHtml += ` <meta name="twitter:title" content="${pageTitle} | ${siteTitle}">\n`;
54
+ metaTagsHtml += ` <meta name="twitter:description" content="${description}">\n`;
55
+ if (ogImage) {
56
+ const twitterImageUrl = ogImage.startsWith('http') ? ogImage : `${siteUrl}${ogImage.startsWith('/') ? ogImage : '/' + ogImage}`;
57
+ metaTagsHtml += ` <meta name="twitter:image" content="${twitterImageUrl}">\n`;
58
+ }
59
+
60
+ // Keywords
61
+ const keywords = seoFrontmatter.keywords || frontmatter.keywords;
62
+ if (keywords) {
63
+ const keywordsString = Array.isArray(keywords) ? keywords.join(', ') : keywords;
64
+ metaTagsHtml += ` <meta name="keywords" content="${keywordsString}">\n`;
65
+ }
66
+
67
+ // LD+JSON Structured Data
68
+ const ldJsonConfig = seoFrontmatter.ldJson || frontmatter.ldJson;
69
+ if (ldJsonConfig) {
70
+ try {
71
+ const baseLdJson = {
72
+ '@context': 'https://schema.org',
73
+ '@type': 'Article',
74
+ mainEntityOfPage: {
75
+ '@type': 'WebPage',
76
+ '@id': canonicalUrl,
77
+ },
78
+ headline: pageTitle,
79
+ description: description,
80
+ url: canonicalUrl,
81
+ };
82
+
83
+ if (config.siteTitle) {
84
+ baseLdJson.publisher = {
85
+ '@type': 'Organization',
86
+ name: config.siteTitle,
87
+ };
88
+ if (config.logo?.light) {
89
+ baseLdJson.publisher.logo = {
90
+ '@type': 'ImageObject',
91
+ url: `${siteUrl}${config.logo.light.startsWith('/') ? config.logo.light : '/' + config.logo.light}`
92
+ };
93
+ }
94
+ }
95
+
96
+ if (ogImage) {
97
+ baseLdJson.image = `${siteUrl}${ogImage.startsWith('/') ? ogImage : '/' + ogImage}`;
98
+ }
99
+
100
+ const finalLdJson = typeof ldJsonConfig === 'object'
101
+ ? { ...baseLdJson, ...ldJsonConfig }
102
+ : baseLdJson;
103
+
104
+ metaTagsHtml += ` <script type="application/ld+json">\n`;
105
+ metaTagsHtml += ` ${JSON.stringify(finalLdJson, null, 2)}\n`;
106
+ metaTagsHtml += ` </script>\n`;
107
+ } catch (e) {
108
+ console.error(`❌ Error generating LD+JSON for page: ${outputPath}`);
109
+ console.error(` Could not stringify the ldJson object. Please check its structure in the frontmatter.`);
110
+ console.error(` ${e.message}`);
63
111
  }
64
-
65
-
66
- return metaTagsHtml;
67
112
  }
68
-
69
- module.exports = { generateSeoMetaTags };
113
+
114
+ return metaTagsHtml;
115
+ }
116
+
117
+ module.exports = { generateSeoMetaTags };
@@ -1,3 +1,9 @@
1
+ // Source file from the docmd project — https://github.com/mgks/docmd
2
+
3
+ /*
4
+ * Generate sitemap.xml in the output directory root
5
+ */
6
+
1
7
  const fs = require('fs-extra');
2
8
  const path = require('path');
3
9
 
@@ -13,9 +13,13 @@
13
13
 
14
14
  <%- faviconLinkHtml || '' %> <%# Favicon %>
15
15
 
16
+ <%- themeInitScript %>
17
+
16
18
  <link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-main.css">
17
19
 
18
- <link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-highlight-<%= defaultMode === 'dark' ? 'dark' : 'light' %>.css" id="highlight-theme">
20
+ <% if (config.theme?.codeHighlight !== false) { %>
21
+ <link rel="stylesheet" id="highlight-theme" href="<%= relativePathToRoot %>assets/css/docmd-highlight-<%= defaultMode === 'dark' ? 'dark' : 'light' %>.css" data-base-href="<%= relativePathToRoot %>assets/css/">
22
+ <% } %>
19
23
 
20
24
  <%- themeCssLinkHtml || '' %> <%# For theme.name specific CSS %>
21
25
 
@@ -27,7 +31,9 @@
27
31
 
28
32
  <%- pluginHeadScriptsHtml || '' %> <%# Plugin specific head scripts (e.g., Analytics) %>
29
33
  </head>
30
- <body class="<%= sidebarConfig.collapsible ? 'sidebar-collapsible' : 'sidebar-not-collapsible' %>" data-theme="<%= defaultMode %>" data-default-collapsed="<%= sidebarConfig.defaultCollapsed %>">
34
+ <body class="<%= sidebarConfig.collapsible ? 'sidebar-collapsible' : 'sidebar-not-collapsible' %>"
35
+ data-theme="<%= defaultMode %>" data-default-collapsed="<%= sidebarConfig.defaultCollapsed %>"
36
+ data-copy-code-enabled="<%= config.copyCode === true %>">
31
37
  <aside class="sidebar">
32
38
  <div class="sidebar-header">
33
39
  <% if (logo && logo.light && logo.dark) { %>
@@ -1,107 +1,78 @@
1
- <%# src/templates/navigation.ejs %>
2
-
3
- <%# renderIcon is passed from html-generator.js %>
4
-
5
- <%
6
- // Debug function - uncomment to troubleshoot paths
7
- function debugNavPaths(item, itemPath, currentPath, isActive, isParentActive) {
8
- console.log('\nDEBUG NAV ITEM:');
9
- console.log(`Title: ${item.title}`);
10
- console.log(`Path: ${item.path}`);
11
- console.log(`Computed item path: ${itemPath}`);
12
- console.log(`Current page path: ${currentPath}`);
13
- console.log(`Is directly active: ${isActive}`);
14
- console.log(`Is parent active: ${isParentActive}`);
15
- }
16
-
17
- function renderNavItems(items, currentLevelPagePath, rootPath) { %>
1
+ <%# navigation.ejs - Renders the sidebar navigation %>
2
+ <nav class="sidebar-nav" aria-label="Main navigation">
18
3
  <ul>
19
- <% items.forEach(item => { %>
20
- <%
21
- let itemHref = '#'; // Default for non-linking parents or error
22
- let isCurrentPageActive = false;
23
- let isActivePage = false; // For the direct page itself
24
- const isExternal = item.external === true;
25
- const targetBlank = isExternal ? 'target="_blank" rel="noopener noreferrer"' : '';
26
- let iconHtml = '';
27
- let externalLinkIconHtml = '';
28
-
29
- if (item.icon) {
30
- // Use custom class for nav icons for specific styling
31
- iconHtml = renderIcon(item.icon, { class: 'nav-item-icon' });
4
+ <%
5
+ // A robust, centralized function to normalize navigation paths
6
+ function normalizePath(p) {
7
+ if (!p) return '#';
8
+ let path = p.replace(/\\/g, '/');
9
+
10
+ if (path.endsWith('index.md')) {
11
+ path = path.slice(0, -'index.md'.length);
12
+ } else {
13
+ path = path.replace(/\.md$/, '');
32
14
  }
33
15
 
34
- if (isExternal) {
35
- itemHref = item.path; // Full URL for external links
36
- // Use a Lucide icon like 'arrow-up-right' or 'external-link'
37
- externalLinkIconHtml = renderIcon('arrow-up-right', { class: 'nav-external-icon', width: '0.8em', height: '0.8em' });
38
- } else {
39
- // Path normalization for internal links
40
- let cleanPath = item.path.startsWith('/') ? item.path : '/' + item.path; // Ensure leading slash
16
+ if (!path.startsWith('/')) path = '/' + path;
41
17
 
42
- // Handle paths with pretty URLs
43
- if (cleanPath === '/') {
44
- // Root path goes to index.html
45
- itemHref = rootPath + 'index.html';
46
- } else {
47
- // Remove any trailing slash for consistency
48
- const pathWithoutTrailingSlash = cleanPath.endsWith('/') ? cleanPath.slice(0, -1) : cleanPath;
49
-
50
- // For all other paths, link to the directory (pretty URL)
51
- // Remove leading slash and ensure clean path
52
- const cleanedPath = pathWithoutTrailingSlash.substring(1);
53
- itemHref = rootPath + cleanedPath + '/';
54
- }
18
+ if (path.length > 1 && !path.endsWith('/')) {
19
+ path += '/';
20
+ }
21
+
22
+ // Handle root case where path might be "" after stripping index.md
23
+ if (path === '') path = '/';
24
+
25
+ return path;
26
+ }
27
+
28
+ // Helper to check for active children recursively
29
+ function hasActiveChild(item, currentPagePath) {
30
+ if (!item.children || !Array.isArray(item.children)) return false;
31
+ return item.children.some(child => {
32
+ if (!child.path || child.external) return false;
33
+ const childPath = normalizePath(child.path);
34
+ if (currentPagePath === childPath) return true;
35
+ return hasActiveChild(child, currentPagePath);
36
+ });
37
+ }
55
38
 
56
- // SIMPLIFIED ACTIVE STATE LOGIC - START
57
- // 1. Prepare the paths for comparison
58
- // The item path in the format used in the navigation config
59
- const normalizedItemPath = cleanPath.substring(1) || '';
39
+ // Recursive function to render navigation items
40
+ function renderNav(items) {
41
+ if (!items || !Array.isArray(items)) return;
42
+ items.forEach(item => {
43
+ const isExternal = item.external || false;
44
+ let itemPath = item.path || '#';
60
45
 
61
- // Special handling for root path
62
- if (cleanPath === '/') {
63
- isActivePage = currentLevelPagePath === 'index.html';
64
- isCurrentPageActive = isActivePage;
65
- }
66
- // Parent folder with index.md
67
- else if (cleanPath.endsWith('/')) {
68
- // Direct match for folder/index.html pages
69
- const folderPath = cleanPath.substring(1, cleanPath.length - 1); // Remove leading / and trailing /
70
- isActivePage = currentLevelPagePath === folderPath + '/';
71
- isCurrentPageActive = isActivePage;
72
-
73
- // Check if any children are active
74
- // If current path starts with this item's path, it's a parent of the active page
75
- if (!isActivePage && currentLevelPagePath.startsWith(normalizedItemPath)) {
76
- isCurrentPageActive = true;
77
- }
78
- }
79
- // Regular page
80
- else {
81
- const pagePath = cleanPath.substring(1) + '/';
82
- isActivePage = currentLevelPagePath === pagePath;
83
- isCurrentPageActive = isActivePage;
84
- }
85
- // SIMPLIFIED ACTIVE STATE LOGIC - END
86
- }
87
- %>
88
- <li class="<%= isCurrentPageActive ? 'active-parent' : '' %>">
89
- <a href="<%= itemHref %>"
90
- class="<%= isActivePage ? 'active' : '' %>"
91
- <%- targetBlank %>>
92
- <% if (iconHtml) { %><%- iconHtml %><% } %>
93
- <span><%- item.title %></span> <%# Wrap title in span for styling if needed %>
94
- <% if (externalLinkIconHtml) { %><%- externalLinkIconHtml %><% } %>
95
- </a>
46
+ const normalizedItemPath = isExternal ? itemPath : normalizePath(itemPath);
96
47
 
97
- <% if (item.children && item.children.length > 0) { %>
98
- <%- renderNavItems(item.children, currentLevelPagePath, rootPath) %>
99
- <% } %>
100
- </li>
101
- <% }); %>
102
- </ul>
103
- <% } %>
48
+ const isActive = !isExternal && currentPagePath === normalizedItemPath;
49
+ const isParentOfActive = !isActive && hasActiveChild(item, currentPagePath);
50
+ const isCollapsible = item.children && item.collapsible;
104
51
 
105
- <nav class="sidebar-nav">
106
- <%- renderNavItems(navItems, currentPagePath, relativePathToRoot) %>
52
+ const liClasses = [];
53
+ if (isActive) liClasses.push('active');
54
+ if (isParentOfActive) liClasses.push('active-parent');
55
+ if (isCollapsible) liClasses.push('collapsible');
56
+
57
+ const finalHref = isExternal ? item.path : (itemPath === '#' ? '#' : (relativePathToRoot + (item.path.startsWith('/') ? item.path.substring(1) : item.path)));
58
+ %>
59
+ <li class="<%= liClasses.join(' ') %>" <%- isCollapsible ? `data-nav-id="${item.path}"` : '' %>>
60
+ <a href="<%- finalHref %>" class="<%- isActive ? 'active' : '' %>" <%- isExternal ? 'target="_blank" rel="noopener"' : '' %>>
61
+ <% if (item.icon) { %> <%- renderIcon(item.icon) %> <% } %>
62
+ <span class="nav-item-title"><%= item.title %></span>
63
+ <% if (isCollapsible) { %> <%- renderIcon('chevron-right', { class: 'collapse-icon' }) %> <% } %>
64
+ <% if (isExternal) { %> <%- renderIcon('external-link', { class: 'nav-external-icon' }) %> <% } %>
65
+ </a>
66
+ <% if (item.children) { %>
67
+ <ul class="submenu">
68
+ <% renderNav(item.children); %>
69
+ </ul>
70
+ <% } %>
71
+ </li>
72
+ <%
73
+ });
74
+ }
75
+ renderNav(navItems);
76
+ %>
77
+ </ul>
107
78
  </nav>
@@ -16,6 +16,10 @@
16
16
  <%- faviconLinkHtml || '' %>
17
17
  <% } %>
18
18
 
19
+ <% if (frontmatter.components?.themeMode !== false) { %>
20
+ <%- themeInitScript %>
21
+ <% } %>
22
+
19
23
  <% if (frontmatter.components?.css !== false) { %>
20
24
  <link rel="stylesheet" href="<%= relativePathToRoot %>assets/css/docmd-main.css">
21
25
  <% if (frontmatter.components?.highlight !== false) { %>
@@ -45,7 +49,16 @@
45
49
  <%- frontmatter.customHead %>
46
50
  <% } %>
47
51
  </head>
48
- <body<% if (frontmatter.components?.theme !== false) { %> data-theme="<%= defaultMode %>"<% } %><% if (frontmatter.bodyClass) { %> class="<%= frontmatter.bodyClass %>"<% } %>>
52
+ <body
53
+ <%
54
+ if (frontmatter.components?.theme !== false) {
55
+ %> data-theme="<%= defaultMode %>"<%
56
+ }
57
+ %><%
58
+ if (frontmatter.bodyClass) {
59
+ %> class="<%= frontmatter.bodyClass %>"<%
60
+ }
61
+ %> data-copy-code-enabled="<%= config.copyCode === true %>">
49
62
  <% if (frontmatter.components?.layout === true || frontmatter.components?.layout === 'full') { %>
50
63
  <div class="main-content-wrapper">
51
64
  <% if (frontmatter.components?.header !== false) { %>
@@ -136,18 +149,22 @@
136
149
  <%- content %>
137
150
  <% } %>
138
151
 
139
- <% if (frontmatter.components?.scripts !== false) { %>
140
- <% if (frontmatter.components?.themeToggle !== false) { %>
141
- <script src="<%= relativePathToRoot %>assets/js/docmd-theme-toggle.js"></script>
152
+ <% if (frontmatter.components?.scripts === true) { %>
153
+ <% if (frontmatter.components?.mainScripts === true) { %>
154
+ <script src="<%= relativePathToRoot %>assets/js/docmd-main.js"></script>
155
+ <% } %>
156
+
157
+ <% if (frontmatter.components?.lightbox === true && frontmatter.components?.mainScripts === true) { %>
158
+ <script src="<%= relativePathToRoot %>assets/js/docmd-image-lightbox.js"></script>
142
159
  <% } %>
143
160
 
144
- <% if (frontmatter.components?.customJs !== false && customJsFiles && customJsFiles.length > 0) { %>
161
+ <% if (frontmatter.components?.customJs === true && customJsFiles && customJsFiles.length > 0) { %>
145
162
  <% customJsFiles.forEach(jsFile => { %>
146
163
  <script src="<%= relativePathToRoot %><%- jsFile.startsWith('/') ? jsFile.substring(1) : jsFile %>"></script>
147
164
  <% }); %>
148
165
  <% } %>
149
166
 
150
- <% if (frontmatter.components?.pluginBodyScripts !== false) { %>
167
+ <% if (frontmatter.components?.pluginBodyScripts === true) { %>
151
168
  <%- pluginBodyScriptsHtml || '' %>
152
169
  <% } %>
153
170
  <% } %>
@@ -0,0 +1,26 @@
1
+ // Source file from the docmd project — https://github.com/mgks/docmd
2
+
3
+ /*
4
+ * Initialize the theme from localStorage
5
+ */
6
+
7
+ (function() {
8
+ try {
9
+ const storedTheme = localStorage.getItem('docmd-theme');
10
+ if (storedTheme) {
11
+ document.documentElement.setAttribute('data-theme', storedTheme);
12
+
13
+ // Also update highlight CSS link to match the stored theme
14
+ const highlightThemeLink = document.getElementById('highlight-theme');
15
+ if (highlightThemeLink) {
16
+ const baseHref = highlightThemeLink.getAttribute('data-base-href');
17
+ if (baseHref) {
18
+ const newHref = baseHref + `docmd-highlight-${storedTheme}.css`;
19
+ highlightThemeLink.setAttribute('href', newHref);
20
+ }
21
+ }
22
+ }
23
+ } catch (e) {
24
+ console.error('Error applying theme from localStorage', e);
25
+ }
26
+ })();