@mgks/docmd 0.2.0 → 0.2.1

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) { %>
@@ -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
+ })();