@mgks/docmd 0.3.9 → 0.4.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.
Files changed (47) hide show
  1. package/README.md +15 -160
  2. package/bin/docmd.js +6 -69
  3. package/package.json +6 -79
  4. package/bin/postinstall.js +0 -14
  5. package/src/assets/css/docmd-highlight-dark.css +0 -86
  6. package/src/assets/css/docmd-highlight-light.css +0 -86
  7. package/src/assets/css/docmd-main.css +0 -1736
  8. package/src/assets/css/docmd-theme-retro.css +0 -867
  9. package/src/assets/css/docmd-theme-ruby.css +0 -629
  10. package/src/assets/css/docmd-theme-sky.css +0 -617
  11. package/src/assets/favicon.ico +0 -0
  12. package/src/assets/images/docmd-logo-dark.png +0 -0
  13. package/src/assets/images/docmd-logo-light.png +0 -0
  14. package/src/assets/js/docmd-image-lightbox.js +0 -74
  15. package/src/assets/js/docmd-main.js +0 -260
  16. package/src/assets/js/docmd-mermaid.js +0 -205
  17. package/src/assets/js/docmd-search.js +0 -218
  18. package/src/commands/build.js +0 -237
  19. package/src/commands/dev.js +0 -352
  20. package/src/commands/init.js +0 -277
  21. package/src/commands/live.js +0 -145
  22. package/src/core/asset-manager.js +0 -72
  23. package/src/core/config-loader.js +0 -58
  24. package/src/core/config-validator.js +0 -80
  25. package/src/core/file-processor.js +0 -103
  26. package/src/core/fs-utils.js +0 -40
  27. package/src/core/html-generator.js +0 -184
  28. package/src/core/icon-renderer.js +0 -106
  29. package/src/core/logger.js +0 -21
  30. package/src/core/markdown/containers.js +0 -94
  31. package/src/core/markdown/renderers.js +0 -90
  32. package/src/core/markdown/rules.js +0 -402
  33. package/src/core/markdown/setup.js +0 -113
  34. package/src/core/navigation-helper.js +0 -74
  35. package/src/index.js +0 -12
  36. package/src/live/core.js +0 -67
  37. package/src/live/index.html +0 -216
  38. package/src/live/live.css +0 -256
  39. package/src/live/shims.js +0 -1
  40. package/src/plugins/analytics.js +0 -48
  41. package/src/plugins/seo.js +0 -107
  42. package/src/plugins/sitemap.js +0 -127
  43. package/src/templates/layout.ejs +0 -187
  44. package/src/templates/navigation.ejs +0 -87
  45. package/src/templates/no-style.ejs +0 -166
  46. package/src/templates/partials/theme-init.js +0 -30
  47. package/src/templates/toc.ejs +0 -38
@@ -1,216 +0,0 @@
1
- <!-- Source file from the docmd project — https://github.com/docmd-io/docmd -->
2
-
3
- <!DOCTYPE html>
4
- <html lang="en">
5
- <head>
6
- <meta charset="UTF-8">
7
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
- <title>Docmd Live Editor</title>
9
- <meta name="description" content="Real-time Markdown preview and editor powered by docmd.">
10
-
11
- <link rel="icon" href="favicon.ico" type="image/x-icon" sizes="any">
12
- <link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
13
-
14
- <link rel="stylesheet" href="live.css">
15
- <script src="docmd-live.js"></script>
16
-
17
- <script async src="https://www.googletagmanager.com/gtag/js?id=G-VCMQ0MCSHN"></script>
18
- <script>
19
- window.dataLayer = window.dataLayer || [];
20
- function gtag(){dataLayer.push(arguments);}
21
- gtag('js', new Date());
22
- gtag('config', 'G-VCMQ0MCSHN');
23
- </script>
24
- </head>
25
- <body class="mode-split mobile-tab-editor">
26
-
27
- <!-- Top Bar (Desktop) -->
28
- <div class="top-bar">
29
- <div class="logo">
30
- <a href="/" class="back-link" id="back-btn" title="Back to Home" aria-label="Back to Home">
31
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
32
- <path d="m12 19-7-7 7-7"/>
33
- <path d="M19 12H5"/>
34
- </svg>
35
- </a>
36
- docmd <span>Live</span>
37
- </div>
38
-
39
- <div class="view-controls desktop-only">
40
- <button class="view-btn active" onclick="setViewMode('split')" id="btn-split">Split View</button>
41
- <button class="view-btn" onclick="setViewMode('single')" id="btn-single">Single View</button>
42
- </div>
43
-
44
- <!-- Single View Tabs (Visible only when in Single Mode on Desktop) -->
45
- <div class="view-controls" id="single-view-tabs" style="display:none;">
46
- <button class="view-btn active" onclick="switchSingleTab('editor')" id="btn-tab-edit">Editor</button>
47
- <button class="view-btn" onclick="switchSingleTab('preview')" id="btn-tab-prev">Preview</button>
48
- </div>
49
- </div>
50
-
51
- <!-- Workspace -->
52
- <div class="workspace" id="workspace">
53
- <!-- Editor -->
54
- <div class="pane editor-pane" id="editorPane">
55
- <div class="pane-header">Markdown</div>
56
- <textarea id="input" spellcheck="false">---
57
- title: My Page
58
- description: Start editing to see changes instantly.
59
- ---
60
-
61
- # Hello World
62
-
63
- This is a **live** preview.
64
-
65
- ::: callout tip
66
- Try resizing the window or switching to mobile view!
67
- :::
68
-
69
- ## Features
70
- 1. Responsive Design
71
- 2. Split or Tabbed view
72
- 3. Instant Rendering
73
- </textarea>
74
- </div>
75
-
76
- <!-- Resizer Handle -->
77
- <div class="resizer" id="resizer"></div>
78
-
79
- <!-- Preview -->
80
- <div class="pane preview-pane" id="previewPane">
81
- <div class="pane-header">Preview</div>
82
- <iframe id="preview"></iframe>
83
- </div>
84
- </div>
85
-
86
- <!-- Mobile Bottom Tabs -->
87
- <div class="mobile-tabs">
88
- <button class="mobile-tab-btn active" onclick="setMobileTab('editor')" id="mob-edit">Editor</button>
89
- <button class="mobile-tab-btn" onclick="setMobileTab('preview')" id="mob-prev">Preview</button>
90
- </div>
91
-
92
- <script>
93
- // --- Core Logic ---
94
- const input = document.getElementById('input');
95
- const preview = document.getElementById('preview');
96
- const backBtn = document.getElementById('back-btn');
97
-
98
- function render() {
99
- try {
100
- let html = docmd.compile(input.value, {
101
- siteTitle: 'My Project', // User can change this in real config, but here we set a default
102
- search: false,
103
- theme: { name: 'sky', defaultMode: 'light' },
104
- sidebar: { collapsible: false }
105
- });
106
-
107
- // --- INJECTIONS ---
108
-
109
- // 1. Force links to open in new tab (Fixes infinite iframe recursion)
110
- html = html.replace('<head>', '<head><base target="_blank">');
111
-
112
- // 2. Hide Sidebar Header via CSS
113
- const customStyle = `
114
- <style>
115
- .sidebar-header { display: none !important; }
116
- .sidebar-nav { margin-top: 1rem; }
117
- </style>
118
- `;
119
- html = html.replace('</body>', `${customStyle}</body>`);
120
- // ------------------
121
-
122
- const doc = preview.contentWindow.document;
123
- doc.open();
124
- doc.write(html);
125
- doc.close();
126
- } catch (e) { console.error(e); }
127
- }
128
-
129
- // Debounce Render
130
- let timer;
131
- input.addEventListener('input', () => {
132
- clearTimeout(timer);
133
- timer = setTimeout(render, 300);
134
- });
135
-
136
- // Initial Render
137
- render();
138
-
139
-
140
- // --- Resizer Logic ---
141
- const resizer = document.getElementById('resizer');
142
- const editorPane = document.getElementById('editorPane');
143
- const workspace = document.getElementById('workspace');
144
- let isResizing = false;
145
-
146
- resizer.addEventListener('mousedown', (e) => {
147
- isResizing = true;
148
- resizer.classList.add('resizing');
149
- preview.style.pointerEvents = 'none';
150
- document.body.style.cursor = 'col-resize';
151
- });
152
-
153
- document.addEventListener('mousemove', (e) => {
154
- if (!isResizing) return;
155
- const containerWidth = workspace.offsetWidth;
156
- const newEditorWidth = (e.clientX / containerWidth) * 100;
157
- if (newEditorWidth > 15 && newEditorWidth < 85) {
158
- editorPane.style.width = newEditorWidth + '%';
159
- }
160
- });
161
-
162
- document.addEventListener('mouseup', () => {
163
- if (isResizing) {
164
- isResizing = false;
165
- resizer.classList.remove('resizing');
166
- preview.style.pointerEvents = 'auto';
167
- document.body.style.cursor = 'default';
168
- }
169
- });
170
-
171
-
172
- // --- View Mode Logic (Desktop) ---
173
- function setViewMode(mode) {
174
- document.body.classList.remove('mode-split', 'mode-single');
175
- document.body.classList.add('mode-' + mode);
176
-
177
- document.getElementById('btn-split').classList.toggle('active', mode === 'split');
178
- document.getElementById('btn-single').classList.toggle('active', mode === 'single');
179
-
180
- document.getElementById('single-view-tabs').style.display = mode === 'single' ? 'flex' : 'none';
181
-
182
- if (mode === 'single') {
183
- switchSingleTab('editor');
184
- }
185
- }
186
-
187
- function switchSingleTab(tab) {
188
- document.body.classList.remove('show-editor', 'show-preview');
189
- document.body.classList.add('show-' + tab);
190
-
191
- document.getElementById('btn-tab-edit').classList.toggle('active', tab === 'editor');
192
- document.getElementById('btn-tab-prev').classList.toggle('active', tab === 'preview');
193
- }
194
-
195
-
196
- // --- Mobile Logic ---
197
- function setMobileTab(tab) {
198
- document.body.classList.remove('mobile-tab-editor', 'mobile-tab-preview');
199
- document.body.classList.add('mobile-tab-' + tab);
200
-
201
- document.getElementById('mob-edit').classList.toggle('active', tab === 'editor');
202
- document.getElementById('mob-prev').classList.toggle('active', tab === 'preview');
203
- }
204
-
205
- // --- Back Button Logic ---
206
- if (window.history.length <= 1) {
207
- backBtn.style.display = 'none';
208
- } else {
209
- backBtn.addEventListener('click', (e) => {
210
- e.preventDefault();
211
- window.history.back();
212
- });
213
- }
214
- </script>
215
- </body>
216
- </html>
package/src/live/live.css DELETED
@@ -1,256 +0,0 @@
1
- /* Source file from the docmd project — https://github.com/docmd-io/docmd */
2
-
3
- :root {
4
- --header-height: 50px;
5
- --border-color: #e0e0e0;
6
- --bg-color: #f9fafb;
7
- --primary-color: #007bff;
8
- --resizer-width: 8px
9
- }
10
-
11
- body {
12
- margin: 0;
13
- height: 100vh;
14
- display: flex;
15
- flex-direction: column;
16
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
17
- overflow: hidden
18
- }
19
-
20
- .top-bar {
21
- height: var(--header-height);
22
- background: #fff;
23
- border-bottom: 1px solid var(--border-color);
24
- display: flex;
25
- align-items: center;
26
- justify-content: space-between;
27
- padding: 0 1rem;
28
- flex-shrink: 0
29
- }
30
-
31
- .logo {
32
- font-weight: 700;
33
- font-size: 1.1rem;
34
- display: flex;
35
- align-items: center;
36
- gap: 12px
37
- }
38
-
39
- .logo span {
40
- background: var(--primary-color);
41
- color: #fff;
42
- padding: 2px 6px;
43
- border-radius: 4px;
44
- font-size: .75rem;
45
- text-transform: uppercase
46
- }
47
-
48
- .back-link {
49
- display: flex;
50
- align-items: center;
51
- justify-content: center;
52
- color: #666;
53
- transition: color 0.2s, transform .2s;
54
- text-decoration: none;
55
- padding: 4px;
56
- border-radius: 4px
57
- }
58
-
59
- .back-link:hover {
60
- color: var(--primary-color);
61
- background: #f0f0f0;
62
- transform: translateX(-2px)
63
- }
64
-
65
- .view-controls {
66
- display: flex;
67
- gap: 8px;
68
- background: #f0f0f0;
69
- padding: 3px;
70
- border-radius: 6px
71
- }
72
-
73
- .view-btn {
74
- border: none;
75
- background: transparent;
76
- padding: 6px 10px;
77
- border-radius: 4px;
78
- cursor: pointer;
79
- font-size: .85rem;
80
- color: #666;
81
- font-weight: 500
82
- }
83
-
84
- .view-btn.active {
85
- background: #fff;
86
- color: #000;
87
- box-shadow: 0 1px 3px #0000001a
88
- }
89
-
90
- .workspace {
91
- flex: 1;
92
- display: flex;
93
- position: relative;
94
- overflow: hidden
95
- }
96
-
97
- .pane {
98
- height: 100%;
99
- display: flex;
100
- flex-direction: column;
101
- min-width: 300px;
102
- background: #fff
103
- }
104
-
105
- .pane-header {
106
- padding: 8px 16px;
107
- font-size: .75rem;
108
- font-weight: 600;
109
- text-transform: uppercase;
110
- color: #888;
111
- background: var(--bg-color);
112
- border-bottom: 1px solid var(--border-color);
113
- flex-shrink: 0
114
- }
115
-
116
- .editor-pane {
117
- width: 50%
118
- }
119
-
120
- textarea#input {
121
- flex: 1;
122
- border: none;
123
- resize: none;
124
- padding: 20px;
125
- font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
126
- font-size: 14px;
127
- line-height: 1.6;
128
- outline: none;
129
- background: var(--bg-color)
130
- }
131
-
132
- .preview-pane {
133
- flex: 1;
134
- background: #fff
135
- }
136
-
137
- iframe#preview {
138
- width: 100%;
139
- height: 100%;
140
- border: none;
141
- display: block
142
- }
143
-
144
- .resizer {
145
- width: var(--resizer-width);
146
- background: var(--bg-color);
147
- border-left: 1px solid var(--border-color);
148
- border-right: 1px solid var(--border-color);
149
- cursor: col-resize;
150
- display: flex;
151
- align-items: center;
152
- justify-content: center;
153
- transition: background .2s;
154
- z-index: 10
155
- }
156
-
157
- .resizer:hover,
158
- .resizer.resizing {
159
- background: #e0e0e0
160
- }
161
-
162
- .resizer::after {
163
- content: "||";
164
- color: #aaa;
165
- font-size: 10px;
166
- letter-spacing: 1px
167
- }
168
-
169
- body.mode-single .resizer {
170
- display: none
171
- }
172
-
173
- body.mode-single .pane {
174
- width: 100% !important;
175
- min-width: 0
176
- }
177
-
178
- body.mode-single .editor-pane {
179
- display: none
180
- }
181
-
182
- body.mode-single .preview-pane {
183
- display: none
184
- }
185
-
186
- body.mode-single.show-editor .editor-pane {
187
- display: flex
188
- }
189
-
190
- body.mode-single.show-preview .preview-pane {
191
- display: flex
192
- }
193
-
194
- @media (max-width: 768px) {
195
- .desktop-only {
196
- display: none !important
197
- }
198
-
199
- .resizer {
200
- display: none !important
201
- }
202
-
203
- .pane {
204
- width: 100% !important
205
- }
206
-
207
- .editor-pane {
208
- display: none
209
- }
210
-
211
- .preview-pane {
212
- display: none
213
- }
214
-
215
- body.mobile-tab-editor .editor-pane {
216
- display: flex
217
- }
218
-
219
- body.mobile-tab-preview .preview-pane {
220
- display: flex
221
- }
222
-
223
- .mobile-tabs {
224
- display: flex !important;
225
- position: fixed;
226
- bottom: 0;
227
- left: 0;
228
- right: 0;
229
- height: 50px;
230
- background: #fff;
231
- border-top: 1px solid var(--border-color);
232
- z-index: 100
233
- }
234
-
235
- .mobile-tab-btn {
236
- flex: 1;
237
- border: none;
238
- background: transparent;
239
- font-weight: 600;
240
- color: #888;
241
- cursor: pointer
242
- }
243
-
244
- .mobile-tab-btn.active {
245
- color: var(--primary-color);
246
- border-top: 2px solid var(--primary-color)
247
- }
248
-
249
- .workspace {
250
- padding-bottom: 50px
251
- }
252
- }
253
-
254
- .mobile-tabs {
255
- display: none
256
- }
package/src/live/shims.js DELETED
@@ -1 +0,0 @@
1
- import { Buffer } from 'buffer'; globalThis.Buffer = Buffer;
@@ -1,48 +0,0 @@
1
- // Source file from the docmd project — https://github.com/docmd-io/docmd
2
-
3
- /*
4
- * Generate analytics scripts for a page
5
- */
6
-
7
- function generateAnalyticsScripts(config, pageData) {
8
- let headScriptsHtml = '';
9
- let bodyScriptsHtml = ''; // For scripts that need to be at the end of body
10
-
11
- const analyticsConfig = config.plugins?.analytics || {}; // Assuming analytics is under plugins.analytics
12
-
13
- // Google Analytics 4 (GA4)
14
- if (analyticsConfig.googleV4?.measurementId) {
15
- const id = analyticsConfig.googleV4.measurementId;
16
- headScriptsHtml += `
17
- <!-- Google Analytics GA4 -->
18
- <script async src="https://www.googletagmanager.com/gtag/js?id=${id}"></script>
19
- <script>
20
- window.dataLayer = window.dataLayer || [];
21
- function gtag(){dataLayer.push(arguments);}
22
- gtag('js', new Date());
23
- gtag('config', '${id}');
24
- </script>\n`;
25
- }
26
-
27
- // Google Analytics Universal Analytics (UA) - Legacy
28
- if (analyticsConfig.googleUA?.trackingId) {
29
- const id = analyticsConfig.googleUA.trackingId;
30
- headScriptsHtml += `
31
- <!-- Google Universal Analytics (Legacy) -->
32
- <script async src="https://www.google-analytics.com/analytics.js"></script>
33
- <script>
34
- window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
35
- ga('create', '${id}', 'auto');
36
- ga('send', 'pageview');
37
- </script>\n`;
38
- }
39
-
40
- // Example for a hypothetical future plugin requiring body script
41
- // if (config.plugins?.someOtherAnalytics?.apiKey) {
42
- // bodyScriptsHtml += `<script src="..."></script>\n`;
43
- // }
44
-
45
- return { headScriptsHtml, bodyScriptsHtml };
46
- }
47
-
48
- module.exports = { generateAnalyticsScripts };
@@ -1,107 +0,0 @@
1
- // Source file from the docmd project — https://github.com/docmd-io/docmd
2
-
3
- /*
4
- * Generate SEO meta tags for a page
5
- */
6
-
7
- function generateSeoMetaTags(config, pageData, relativePathToRoot) {
8
- let metaTagsHtml = '';
9
- const { frontmatter, outputPath } = pageData;
10
- const seoFrontmatter = frontmatter.seo || {};
11
-
12
- if (frontmatter.noindex || seoFrontmatter.noindex) {
13
- metaTagsHtml += '<meta name="robots" content="noindex">\n';
14
- return metaTagsHtml;
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: { '@type': 'WebPage', '@id': canonicalUrl },
75
- headline: pageTitle,
76
- description: description,
77
- url: canonicalUrl,
78
- };
79
-
80
- if (config.siteTitle) {
81
- baseLdJson.publisher = { '@type': 'Organization', name: config.siteTitle };
82
- if (config.logo?.light) {
83
- baseLdJson.publisher.logo = {
84
- '@type': 'ImageObject',
85
- url: `${siteUrl}${config.logo.light.startsWith('/') ? config.logo.light : '/' + config.logo.light}`
86
- };
87
- }
88
- }
89
-
90
- if (ogImage) {
91
- baseLdJson.image = `${siteUrl}${ogImage.startsWith('/') ? ogImage : '/' + ogImage}`;
92
- }
93
-
94
- const finalLdJson = typeof ldJsonConfig === 'object' ? { ...baseLdJson, ...ldJsonConfig } : baseLdJson;
95
-
96
- metaTagsHtml += `<script type="application/ld+json">\n`;
97
- metaTagsHtml += `${JSON.stringify(finalLdJson, null, 2)}\n`;
98
- metaTagsHtml += `</script>\n`;
99
- } catch (e) {
100
- console.error(`❌ Error generating LD+JSON: ${e.message}`);
101
- }
102
- }
103
-
104
- return metaTagsHtml;
105
- }
106
-
107
- module.exports = { generateSeoMetaTags };