@mgks/docmd 0.1.4 → 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/README.md +2 -4
- package/assets/css/welcome.css +62 -374
- 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 +31 -2
- package/docs/configuration.md +72 -3
- 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 +2 -2
- package/docs/index.md +6 -9
- package/docs/plugins/seo.md +2 -0
- package/docs/theming/available-themes.md +17 -2
- package/docs/theming/light-dark-mode.md +12 -3
- package/package.json +9 -3
- package/src/assets/css/docmd-main.css +934 -573
- package/src/assets/css/docmd-theme-retro.css +812 -0
- package/src/assets/css/docmd-theme-ruby.css +26 -13
- 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/init.js +19 -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 +33 -7
- 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
|
@@ -5,457 +5,720 @@ const matter = require('gray-matter');
|
|
|
5
5
|
const hljs = require('highlight.js');
|
|
6
6
|
const container = require('markdown-it-container');
|
|
7
7
|
const attrs = require('markdown-it-attrs');
|
|
8
|
-
const path = require('path');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const markdown_it_footnote = require('markdown-it-footnote');
|
|
10
|
+
const markdown_it_task_lists = require('markdown-it-task-lists');
|
|
11
|
+
const markdown_it_abbr = require('markdown-it-abbr');
|
|
12
|
+
const markdown_it_deflist = require('markdown-it-deflist');
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Formats an absolute path to be relative to the current working directory for cleaner logging.
|
|
16
|
+
*/
|
|
11
17
|
function formatPathForDisplay(absolutePath) {
|
|
12
18
|
const CWD = process.cwd();
|
|
13
19
|
const relativePath = path.relative(CWD, absolutePath);
|
|
14
|
-
|
|
15
|
-
// If it's not a subdirectory, prefix with ./ for clarity
|
|
16
20
|
if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
|
|
17
21
|
return `./${relativePath}`;
|
|
18
22
|
}
|
|
19
|
-
|
|
20
|
-
// Return the relative path
|
|
21
23
|
return relativePath;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
// Initialize MarkdownIt with plugins and options.
|
|
24
27
|
const md = new MarkdownIt({
|
|
25
28
|
html: true,
|
|
26
29
|
linkify: true,
|
|
27
30
|
typographer: true,
|
|
31
|
+
breaks: true,
|
|
28
32
|
highlight: function (str, lang) {
|
|
29
33
|
if (lang && hljs.getLanguage(lang)) {
|
|
30
34
|
try {
|
|
31
35
|
return '<pre class="hljs"><code>' +
|
|
32
36
|
hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
|
|
33
37
|
'</code></pre>';
|
|
34
|
-
} catch (e) {
|
|
35
|
-
console.error(`Error highlighting language ${lang}:`, e);
|
|
36
|
-
}
|
|
38
|
+
} catch (e) { console.error(`Error highlighting language ${lang}:`, e); }
|
|
37
39
|
}
|
|
38
|
-
|
|
40
|
+
// For non-language code blocks, preserve the original content without processing
|
|
41
|
+
return '<pre class="hljs"><code>' + str + '</code></pre>';
|
|
39
42
|
}
|
|
40
43
|
});
|
|
41
44
|
|
|
42
|
-
// Add markdown-it-attrs for image styling and other element attributes
|
|
43
|
-
// This allows for {.class} syntax after elements
|
|
44
|
-
md.use(attrs, {
|
|
45
|
-
// Allow attributes on images and other elements
|
|
46
|
-
leftDelimiter: '{',
|
|
47
|
-
rightDelimiter: '}',
|
|
48
|
-
allowedAttributes: ['class', 'id', 'width', 'height', 'style']
|
|
49
|
-
});
|
|
50
45
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
md.
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
|
|
47
|
+
// Use standard markdown-it plugins for extended syntax support.
|
|
48
|
+
md.use(attrs, { leftDelimiter: '{', rightDelimiter: '}' });
|
|
49
|
+
md.use(markdown_it_footnote);
|
|
50
|
+
md.use(markdown_it_task_lists);
|
|
51
|
+
md.use(markdown_it_abbr);
|
|
52
|
+
md.use(markdown_it_deflist);
|
|
53
|
+
|
|
54
|
+
// Override the default fence renderer to preserve original content for all code blocks
|
|
55
|
+
const defaultFenceRenderer = md.renderer.rules.fence;
|
|
56
|
+
md.renderer.rules.fence = function(tokens, idx, options, env, self) {
|
|
57
|
+
const token = tokens[idx];
|
|
56
58
|
|
|
57
|
-
//
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
// Escape HTML entities to prevent rendering
|
|
60
|
+
const escapedContent = token.content
|
|
61
|
+
.replace(/&/g, '&')
|
|
62
|
+
.replace(/</g, '<')
|
|
63
|
+
.replace(/>/g, '>')
|
|
64
|
+
.replace(/"/g, '"')
|
|
65
|
+
.replace(/'/g, ''');
|
|
66
|
+
|
|
67
|
+
// If no language is specified, preserve the original content without processing
|
|
68
|
+
if (!token.info || token.info.trim() === '') {
|
|
69
|
+
return '<pre class="hljs"><code>' + escapedContent + '</code></pre>';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// For all language blocks, preserve the original content to avoid processing HTML or other content
|
|
73
|
+
// This ensures code blocks are treated as literal text only
|
|
74
|
+
const language = token.info.trim();
|
|
75
|
+
return '<pre class="hljs"><code class="language-' + language + '">' + escapedContent + '</code></pre>';
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
// ===================================================================
|
|
81
|
+
// --- ADVANCED NESTED CONTAINER SYSTEM ---
|
|
82
|
+
// ===================================================================
|
|
83
|
+
|
|
84
|
+
// Container definitions
|
|
85
|
+
// To add a new container type:
|
|
86
|
+
// 1. Add it to this containers object
|
|
87
|
+
// 2. Define the render function for opening (nesting === 1) and closing (nesting === -1)
|
|
88
|
+
// 3. The system will automatically register it and support nesting
|
|
89
|
+
const containers = {
|
|
90
|
+
card: {
|
|
91
|
+
name: 'card',
|
|
92
|
+
render: (tokens, idx) => {
|
|
93
|
+
if (tokens[idx].nesting === 1) {
|
|
94
|
+
const title = tokens[idx].info ? tokens[idx].info.trim() : '';
|
|
95
|
+
return `<div class="docmd-container card">${title ? `<div class="card-title">${title}</div>` : ''}<div class="card-content">`;
|
|
96
|
+
}
|
|
97
|
+
return '</div></div>';
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
callout: {
|
|
101
|
+
name: 'callout',
|
|
102
|
+
render: (tokens, idx) => {
|
|
103
|
+
if (tokens[idx].nesting === 1) {
|
|
104
|
+
const [type, ...titleParts] = tokens[idx].info.split(' ');
|
|
105
|
+
const title = titleParts.join(' ');
|
|
106
|
+
return `<div class="docmd-container callout callout-${type}">${title ? `<div class="callout-title">${title}</div>` : ''}<div class="callout-content">`;
|
|
107
|
+
}
|
|
108
|
+
return '</div></div>';
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
button: {
|
|
112
|
+
name: 'button',
|
|
113
|
+
selfClosing: true, // Mark as self-closing
|
|
114
|
+
render: (tokens, idx) => {
|
|
115
|
+
if (tokens[idx].nesting === 1) {
|
|
116
|
+
const parts = tokens[idx].info.split(' ');
|
|
117
|
+
const text = parts[0];
|
|
118
|
+
const url = parts[1];
|
|
119
|
+
const color = parts[2];
|
|
120
|
+
const colorStyle = color && color.startsWith('color:') ? ` style="background-color: ${color.split(':')[1]}"` : '';
|
|
121
|
+
|
|
122
|
+
// Check if URL starts with 'external:' for new tab behavior
|
|
123
|
+
let finalUrl = url;
|
|
124
|
+
let targetAttr = '';
|
|
125
|
+
if (url && url.startsWith('external:')) {
|
|
126
|
+
finalUrl = url.substring(9); // Remove 'external:' prefix
|
|
127
|
+
targetAttr = ' target="_blank" rel="noopener noreferrer"';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return `<a href="${finalUrl}" class="docmd-button"${colorStyle}${targetAttr}>${text.replace(/_/g, ' ')}</a>`;
|
|
131
|
+
}
|
|
132
|
+
return '';
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
steps: {
|
|
136
|
+
name: 'steps',
|
|
137
|
+
render: (tokens, idx) => {
|
|
138
|
+
if (tokens[idx].nesting === 1) {
|
|
139
|
+
// Add a unique class for steps containers to enable CSS-based numbering reset
|
|
140
|
+
// The steps-numbering class will style only direct ol > li children as numbered steps
|
|
141
|
+
return '<div class="docmd-container steps steps-reset steps-numbering">';
|
|
142
|
+
}
|
|
143
|
+
return '</div>';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Future containers can be added here:
|
|
147
|
+
// timeline: {
|
|
148
|
+
// name: 'timeline',
|
|
149
|
+
// render: (tokens, idx) => {
|
|
150
|
+
// if (tokens[idx].nesting === 1) {
|
|
151
|
+
// return '<div class="docmd-container timeline">';
|
|
152
|
+
// }
|
|
153
|
+
// return '</div>';
|
|
154
|
+
// }
|
|
155
|
+
// },
|
|
156
|
+
// changelog: {
|
|
157
|
+
// name: 'changelog',
|
|
158
|
+
// render: (tokens, idx) => {
|
|
159
|
+
// if (tokens[idx].nesting === 1) {
|
|
160
|
+
// return '<div class="docmd-container changelog">';
|
|
161
|
+
// }
|
|
162
|
+
// return '</div>';
|
|
163
|
+
// }
|
|
164
|
+
// }
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
// Advanced container rule with proper nesting support
|
|
170
|
+
function advancedContainerRule(state, startLine, endLine, silent) {
|
|
171
|
+
const start = state.bMarks[startLine] + state.tShift[startLine];
|
|
172
|
+
const max = state.eMarks[startLine];
|
|
173
|
+
const lineContent = state.src.slice(start, max).trim();
|
|
174
|
+
|
|
175
|
+
// Check if this is a container opening
|
|
176
|
+
const containerMatch = lineContent.match(/^:::\s*(\w+)(?:\s+(.+))?$/);
|
|
177
|
+
if (!containerMatch) return false;
|
|
178
|
+
|
|
179
|
+
const [, containerName, params] = containerMatch;
|
|
180
|
+
const container = containers[containerName];
|
|
181
|
+
|
|
182
|
+
if (!container) return false;
|
|
183
|
+
|
|
184
|
+
if (silent) return true;
|
|
185
|
+
|
|
186
|
+
// Handle self-closing containers (like buttons)
|
|
187
|
+
if (container.selfClosing) {
|
|
188
|
+
const openToken = state.push(`container_${containerName}_open`, 'div', 1);
|
|
189
|
+
openToken.info = params || '';
|
|
190
|
+
const closeToken = state.push(`container_${containerName}_close`, 'div', -1);
|
|
191
|
+
state.line = startLine + 1;
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Find the closing tag with proper nesting handling
|
|
196
|
+
let nextLine = startLine;
|
|
197
|
+
let found = false;
|
|
198
|
+
let depth = 1;
|
|
199
|
+
|
|
200
|
+
while (nextLine < endLine) {
|
|
201
|
+
nextLine++;
|
|
202
|
+
const nextStart = state.bMarks[nextLine] + state.tShift[nextLine];
|
|
203
|
+
const nextMax = state.eMarks[nextLine];
|
|
204
|
+
const nextContent = state.src.slice(nextStart, nextMax).trim();
|
|
62
205
|
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
206
|
+
// Check for opening tags (any container)
|
|
207
|
+
if (nextContent.startsWith(':::')) {
|
|
208
|
+
const containerMatch = nextContent.match(/^:::\s*(\w+)/);
|
|
209
|
+
if (containerMatch && containerMatch[1] !== containerName) {
|
|
210
|
+
// Only increment depth for non-self-closing containers
|
|
211
|
+
const innerContainer = containers[containerMatch[1]];
|
|
212
|
+
if (innerContainer && innerContainer.render && !innerContainer.selfClosing) {
|
|
213
|
+
depth++;
|
|
214
|
+
}
|
|
215
|
+
continue;
|
|
71
216
|
}
|
|
72
|
-
}
|
|
217
|
+
}
|
|
73
218
|
|
|
74
|
-
//
|
|
75
|
-
|
|
219
|
+
// Check for closing tags
|
|
220
|
+
if (nextContent === ':::') {
|
|
221
|
+
depth--;
|
|
222
|
+
if (depth === 0) {
|
|
223
|
+
found = true;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
76
227
|
}
|
|
77
228
|
|
|
78
|
-
return
|
|
79
|
-
|
|
229
|
+
if (!found) return false;
|
|
230
|
+
|
|
231
|
+
// Create tokens
|
|
232
|
+
const openToken = state.push(`container_${containerName}_open`, 'div', 1);
|
|
233
|
+
openToken.info = params || '';
|
|
234
|
+
|
|
235
|
+
// Process content recursively
|
|
236
|
+
const oldParentType = state.parentType;
|
|
237
|
+
const oldLineMax = state.lineMax;
|
|
238
|
+
|
|
239
|
+
state.parentType = 'container';
|
|
240
|
+
state.lineMax = nextLine;
|
|
241
|
+
|
|
242
|
+
// Process the content inside the container
|
|
243
|
+
state.md.block.tokenize(state, startLine + 1, nextLine);
|
|
244
|
+
|
|
245
|
+
const closeToken = state.push(`container_${containerName}_close`, 'div', -1);
|
|
246
|
+
|
|
247
|
+
state.parentType = oldParentType;
|
|
248
|
+
state.lineMax = oldLineMax;
|
|
249
|
+
state.line = nextLine + 1;
|
|
250
|
+
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
80
253
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
254
|
+
// --- Simple Steps Container Rule ---
|
|
255
|
+
function stepsContainerRule(state, startLine, endLine, silent) {
|
|
256
|
+
const start = state.bMarks[startLine] + state.tShift[startLine];
|
|
257
|
+
const max = state.eMarks[startLine];
|
|
258
|
+
const lineContent = state.src.slice(start, max).trim();
|
|
259
|
+
if (lineContent !== '::: steps') return false;
|
|
260
|
+
if (silent) return true;
|
|
87
261
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
262
|
+
// Find the closing ':::' for the steps container
|
|
263
|
+
let nextLine = startLine;
|
|
264
|
+
let found = false;
|
|
265
|
+
let depth = 1;
|
|
266
|
+
|
|
267
|
+
while (nextLine < endLine) {
|
|
268
|
+
nextLine++;
|
|
269
|
+
const nextStart = state.bMarks[nextLine] + state.tShift[nextLine];
|
|
270
|
+
const nextMax = state.eMarks[nextLine];
|
|
271
|
+
const nextContent = state.src.slice(nextStart, nextMax).trim();
|
|
92
272
|
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
token.attrSet('id', id);
|
|
273
|
+
// Skip tab markers as they don't affect container depth
|
|
274
|
+
if (nextContent.startsWith('== tab')) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Check for opening tags (any container)
|
|
279
|
+
if (nextContent.startsWith(':::')) {
|
|
280
|
+
const containerMatch = nextContent.match(/^:::\s*(\w+)/);
|
|
281
|
+
if (containerMatch) {
|
|
282
|
+
const containerName = containerMatch[1];
|
|
283
|
+
// Only count non-self-closing containers for depth
|
|
284
|
+
const innerContainer = containers[containerName];
|
|
285
|
+
if (innerContainer && !innerContainer.selfClosing) {
|
|
286
|
+
depth++;
|
|
287
|
+
}
|
|
288
|
+
continue;
|
|
110
289
|
}
|
|
111
290
|
}
|
|
112
291
|
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
md.use(container, 'callout', {
|
|
120
|
-
validate: function(params) {
|
|
121
|
-
// Allows optional title for callout: ::: callout type [Optional Title Text]
|
|
122
|
-
return params.trim().match(/^callout\s+(info|warning|tip|danger|success)(\s+.*)?$/);
|
|
123
|
-
},
|
|
124
|
-
render: function (tokens, idx) {
|
|
125
|
-
const token = tokens[idx];
|
|
126
|
-
const match = token.info.trim().match(/^callout\s+(info|warning|tip|danger|success)(\s+(.*))?$/);
|
|
127
|
-
|
|
128
|
-
if (token.nesting === 1) {
|
|
129
|
-
const type = match[1];
|
|
130
|
-
const title = match[3] ? md.renderInline(match[3]) : ''; // Render title as markdown
|
|
131
|
-
let titleHtml = '';
|
|
132
|
-
if (title) {
|
|
133
|
-
titleHtml = `<div class="callout-title">${title}</div>`;
|
|
292
|
+
// Check for closing tags
|
|
293
|
+
if (nextContent === ':::') {
|
|
294
|
+
depth--;
|
|
295
|
+
if (depth === 0) {
|
|
296
|
+
found = true;
|
|
297
|
+
break;
|
|
134
298
|
}
|
|
135
|
-
return `<div class="docmd-container callout callout-${type}">\n${titleHtml}<div class="callout-content">\n`;
|
|
136
|
-
} else {
|
|
137
|
-
return '</div></div>\n';
|
|
138
299
|
}
|
|
139
300
|
}
|
|
140
|
-
|
|
301
|
+
|
|
302
|
+
if (!found) return false;
|
|
141
303
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
304
|
+
// Create tokens for steps container
|
|
305
|
+
const openToken = state.push('container_steps_open', 'div', 1);
|
|
306
|
+
openToken.info = '';
|
|
307
|
+
|
|
308
|
+
// Process content normally but disable automatic list processing
|
|
309
|
+
const oldParentType = state.parentType;
|
|
310
|
+
const oldLineMax = state.lineMax;
|
|
311
|
+
|
|
312
|
+
state.parentType = 'container';
|
|
313
|
+
state.lineMax = nextLine;
|
|
314
|
+
|
|
315
|
+
// Process the content inside the container
|
|
316
|
+
state.md.block.tokenize(state, startLine + 1, nextLine);
|
|
317
|
+
|
|
318
|
+
const closeToken = state.push('container_steps_close', 'div', -1);
|
|
319
|
+
|
|
320
|
+
state.parentType = oldParentType;
|
|
321
|
+
state.lineMax = oldLineMax;
|
|
322
|
+
state.line = nextLine + 1;
|
|
323
|
+
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// --- Enhanced tabs rule with nested content support ---
|
|
328
|
+
function enhancedTabsRule(state, startLine, endLine, silent) {
|
|
329
|
+
const start = state.bMarks[startLine] + state.tShift[startLine];
|
|
330
|
+
const max = state.eMarks[startLine];
|
|
331
|
+
const lineContent = state.src.slice(start, max).trim();
|
|
150
332
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
333
|
+
if (lineContent !== '::: tabs') return false;
|
|
334
|
+
if (silent) return true;
|
|
335
|
+
|
|
336
|
+
// Find the closing tag with proper nesting handling
|
|
337
|
+
let nextLine = startLine;
|
|
338
|
+
let found = false;
|
|
339
|
+
let depth = 1;
|
|
340
|
+
while (nextLine < endLine) {
|
|
341
|
+
nextLine++;
|
|
342
|
+
const nextStart = state.bMarks[nextLine] + state.tShift[nextLine];
|
|
343
|
+
const nextMax = state.eMarks[nextLine];
|
|
344
|
+
const nextContent = state.src.slice(nextStart, nextMax).trim();
|
|
345
|
+
|
|
346
|
+
// Check for opening tags (any container)
|
|
347
|
+
if (nextContent.startsWith(':::')) {
|
|
348
|
+
const containerMatch = nextContent.match(/^:::\s*(\w+)/);
|
|
349
|
+
if (containerMatch && containerMatch[1] !== 'tabs') {
|
|
350
|
+
// Don't increment depth for steps - they have their own depth counting
|
|
351
|
+
if (containerMatch[1] === 'steps') {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
// Only increment depth for non-self-closing containers
|
|
355
|
+
const innerContainer = containers[containerMatch[1]];
|
|
356
|
+
if (innerContainer && !innerContainer.selfClosing) {
|
|
357
|
+
depth++;
|
|
358
|
+
}
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Check for closing tags
|
|
364
|
+
if (nextContent === ':::') {
|
|
365
|
+
depth--;
|
|
366
|
+
if (depth === 0) {
|
|
367
|
+
found = true;
|
|
368
|
+
break;
|
|
155
369
|
}
|
|
156
|
-
return `<div class="docmd-container card">\n${titleHtml}<div class="card-content">\n`;
|
|
157
|
-
} else {
|
|
158
|
-
return '</div></div>\n';
|
|
159
370
|
}
|
|
160
371
|
}
|
|
161
|
-
|
|
372
|
+
if (!found) return false;
|
|
373
|
+
|
|
374
|
+
// Get the raw content by manually extracting lines
|
|
375
|
+
let content = '';
|
|
376
|
+
for (let i = startLine + 1; i < nextLine; i++) {
|
|
377
|
+
const lineStart = state.bMarks[i] + state.tShift[i];
|
|
378
|
+
const lineEnd = state.eMarks[i];
|
|
379
|
+
content += state.src.slice(lineStart, lineEnd) + '\n';
|
|
380
|
+
}
|
|
162
381
|
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
382
|
+
// Parse tabs manually
|
|
383
|
+
const lines = content.split('\n');
|
|
384
|
+
const tabs = [];
|
|
385
|
+
let currentTab = null;
|
|
386
|
+
let currentContent = [];
|
|
387
|
+
|
|
388
|
+
for (let i = 0; i < lines.length; i++) {
|
|
389
|
+
const line = lines[i].trim();
|
|
390
|
+
const tabMatch = line.match(/^==\s*tab\s+(?:"([^"]+)"|(\S+))$/);
|
|
391
|
+
|
|
392
|
+
if (tabMatch) {
|
|
393
|
+
// Save previous tab if exists
|
|
394
|
+
if (currentTab) {
|
|
395
|
+
currentTab.content = currentContent.join('\n').trim();
|
|
396
|
+
tabs.push(currentTab);
|
|
397
|
+
}
|
|
398
|
+
// Start new tab
|
|
399
|
+
const title = tabMatch[1] || tabMatch[2];
|
|
400
|
+
currentTab = { title: title, content: '' };
|
|
401
|
+
currentContent = [];
|
|
402
|
+
} else if (currentTab) {
|
|
403
|
+
// Add line to current tab content (only if not empty and not a tab marker)
|
|
404
|
+
if (lines[i].trim() && !lines[i].trim().startsWith('==')) {
|
|
405
|
+
currentContent.push(lines[i]);
|
|
406
|
+
}
|
|
179
407
|
}
|
|
180
408
|
}
|
|
181
|
-
|
|
409
|
+
|
|
410
|
+
// Save the last tab
|
|
411
|
+
if (currentTab) {
|
|
412
|
+
currentTab.content = currentContent.join('\n').trim();
|
|
413
|
+
tabs.push(currentTab);
|
|
414
|
+
}
|
|
182
415
|
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
// Handle any remaining blockquotes as generic steps
|
|
222
|
-
.replace(
|
|
223
|
-
/<blockquote>([\s\S]*?)<\/blockquote>/g,
|
|
224
|
-
function(blockquote, content) {
|
|
225
|
-
return `<div class="step">${content}</div>`;
|
|
416
|
+
// Create tabs structure
|
|
417
|
+
const openToken = state.push('tabs_open', 'div', 1);
|
|
418
|
+
openToken.attrs = [['class', 'docmd-tabs']];
|
|
419
|
+
|
|
420
|
+
// Create navigation
|
|
421
|
+
const navToken = state.push('tabs_nav_open', 'div', 1);
|
|
422
|
+
navToken.attrs = [['class', 'docmd-tabs-nav']];
|
|
423
|
+
tabs.forEach((tab, index) => {
|
|
424
|
+
const navItemToken = state.push('tabs_nav_item', 'div', 0);
|
|
425
|
+
navItemToken.attrs = [['class', `docmd-tabs-nav-item ${index === 0 ? 'active' : ''}`]];
|
|
426
|
+
navItemToken.content = tab.title;
|
|
427
|
+
});
|
|
428
|
+
state.push('tabs_nav_close', 'div', -1);
|
|
429
|
+
|
|
430
|
+
// Create content
|
|
431
|
+
const contentToken = state.push('tabs_content_open', 'div', 1);
|
|
432
|
+
contentToken.attrs = [['class', 'docmd-tabs-content']];
|
|
433
|
+
tabs.forEach((tab, index) => {
|
|
434
|
+
const paneToken = state.push('tab_pane_open', 'div', 1);
|
|
435
|
+
paneToken.attrs = [['class', `docmd-tab-pane ${index === 0 ? 'active' : ''}`]];
|
|
436
|
+
|
|
437
|
+
// Process tab content with the main markdown-it instance
|
|
438
|
+
if (tab.content.trim()) {
|
|
439
|
+
const tabContent = tab.content.trim();
|
|
440
|
+
|
|
441
|
+
// Create a separate markdown-it instance for tab content to avoid double processing
|
|
442
|
+
const tabMd = new MarkdownIt({
|
|
443
|
+
html: true,
|
|
444
|
+
linkify: true,
|
|
445
|
+
typographer: true,
|
|
446
|
+
breaks: true,
|
|
447
|
+
highlight: function (str, lang) {
|
|
448
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
449
|
+
try {
|
|
450
|
+
return '<pre class="hljs"><code>' +
|
|
451
|
+
hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
|
|
452
|
+
'</code></pre>';
|
|
453
|
+
} catch (e) { console.error(`Error highlighting language ${lang}:`, e); }
|
|
226
454
|
}
|
|
227
|
-
|
|
455
|
+
return '<pre class="hljs"><code>' + str + '</code></pre>';
|
|
456
|
+
}
|
|
457
|
+
});
|
|
228
458
|
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
459
|
+
// Register the same plugins for the tab markdown instance
|
|
460
|
+
tabMd.use(attrs, { leftDelimiter: '{', rightDelimiter: '}' });
|
|
461
|
+
tabMd.use(markdown_it_footnote);
|
|
462
|
+
tabMd.use(markdown_it_task_lists);
|
|
463
|
+
tabMd.use(markdown_it_abbr);
|
|
464
|
+
tabMd.use(markdown_it_deflist);
|
|
233
465
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return `### STEP_MARKER ${stepNumber}${stepContent}`;
|
|
252
|
-
}
|
|
253
|
-
);
|
|
466
|
+
// Register container renderers for the tab markdown instance
|
|
467
|
+
Object.keys(containers).forEach(containerName => {
|
|
468
|
+
const container = containers[containerName];
|
|
469
|
+
tabMd.renderer.rules[`container_${containerName}_open`] = container.render;
|
|
470
|
+
tabMd.renderer.rules[`container_${containerName}_close`] = container.render;
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// Register the enhanced rules for the tab markdown instance
|
|
474
|
+
tabMd.block.ruler.before('fence', 'enhanced_tabs', enhancedTabsRule, {
|
|
475
|
+
alt: ['paragraph', 'reference', 'blockquote', 'list']
|
|
476
|
+
});
|
|
477
|
+
tabMd.block.ruler.before('paragraph', 'steps_container', stepsContainerRule, {
|
|
478
|
+
alt: ['paragraph', 'reference', 'blockquote', 'list']
|
|
479
|
+
});
|
|
480
|
+
tabMd.block.ruler.before('paragraph', 'advanced_container', advancedContainerRule, {
|
|
481
|
+
alt: ['paragraph', 'reference', 'blockquote', 'list']
|
|
482
|
+
});
|
|
254
483
|
|
|
255
|
-
|
|
484
|
+
// Render the tab content
|
|
485
|
+
const renderedContent = tabMd.render(tabContent);
|
|
486
|
+
const htmlToken = state.push('html_block', '', 0);
|
|
487
|
+
htmlToken.content = renderedContent;
|
|
256
488
|
}
|
|
257
|
-
|
|
489
|
+
|
|
490
|
+
state.push('tab_pane_close', 'div', -1);
|
|
491
|
+
});
|
|
492
|
+
state.push('tabs_content_close', 'div', -1);
|
|
493
|
+
state.push('tabs_close', 'div', -1);
|
|
494
|
+
state.line = nextLine + 1;
|
|
495
|
+
return true;
|
|
258
496
|
}
|
|
259
497
|
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
498
|
+
// Register the enhanced rules
|
|
499
|
+
md.block.ruler.before('fence', 'steps_container', stepsContainerRule, {
|
|
500
|
+
alt: ['paragraph', 'reference', 'blockquote', 'list']
|
|
501
|
+
});
|
|
502
|
+
md.block.ruler.before('fence', 'enhanced_tabs', enhancedTabsRule, {
|
|
503
|
+
alt: ['paragraph', 'reference', 'blockquote', 'list']
|
|
504
|
+
});
|
|
505
|
+
md.block.ruler.before('paragraph', 'advanced_container', advancedContainerRule, {
|
|
506
|
+
alt: ['paragraph', 'reference', 'blockquote', 'list']
|
|
507
|
+
});
|
|
269
508
|
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
509
|
+
// Add a rule to handle standalone closing tags
|
|
510
|
+
md.block.ruler.before('paragraph', 'standalone_closing', (state, startLine, endLine, silent) => {
|
|
511
|
+
const start = state.bMarks[startLine] + state.tShift[startLine];
|
|
512
|
+
const max = state.eMarks[startLine];
|
|
513
|
+
const lineContent = state.src.slice(start, max).trim();
|
|
514
|
+
|
|
515
|
+
if (lineContent === ':::') {
|
|
516
|
+
if (silent) return true;
|
|
517
|
+
// Skip this line by not creating any tokens
|
|
518
|
+
state.line = startLine + 1;
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return false;
|
|
523
|
+
}, {
|
|
524
|
+
alt: ['paragraph', 'reference', 'blockquote', 'list']
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// Register renderers for all containers
|
|
528
|
+
Object.keys(containers).forEach(containerName => {
|
|
529
|
+
const container = containers[containerName];
|
|
530
|
+
md.renderer.rules[`container_${containerName}_open`] = container.render;
|
|
531
|
+
md.renderer.rules[`container_${containerName}_close`] = container.render;
|
|
532
|
+
});
|
|
291
533
|
|
|
292
|
-
//
|
|
293
|
-
function
|
|
294
|
-
|
|
295
|
-
|
|
534
|
+
// Custom renderer for ordered lists in steps containers
|
|
535
|
+
md.renderer.rules.ordered_list_open = function(tokens, idx, options, env, self) {
|
|
536
|
+
const token = tokens[idx];
|
|
537
|
+
// Check if we're inside a steps container by looking at the context
|
|
538
|
+
let isInSteps = false;
|
|
296
539
|
|
|
297
|
-
//
|
|
298
|
-
|
|
540
|
+
// Look back through tokens to see if we're in a steps container
|
|
541
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
542
|
+
if (tokens[i].type === 'container_steps_open') {
|
|
543
|
+
isInSteps = true;
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
if (tokens[i].type === 'container_steps_close') {
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
299
550
|
|
|
300
|
-
|
|
301
|
-
|
|
551
|
+
if (isInSteps) {
|
|
552
|
+
const start = token.attrGet('start');
|
|
553
|
+
return start ?
|
|
554
|
+
`<ol class="steps-list" start="${start}">` :
|
|
555
|
+
'<ol class="steps-list">';
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Default behavior for non-steps ordered lists
|
|
559
|
+
const start = token.attrGet('start');
|
|
560
|
+
return start ? `<ol start="${start}">` : '<ol>';
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// Custom renderer for list items in steps containers
|
|
564
|
+
md.renderer.rules.list_item_open = function(tokens, idx, options, env, self) {
|
|
565
|
+
const token = tokens[idx];
|
|
566
|
+
// Check if we're inside a steps container and this is a direct child
|
|
567
|
+
let isInStepsList = false;
|
|
302
568
|
|
|
303
|
-
//
|
|
304
|
-
|
|
569
|
+
// Look back through tokens to see if we're in a steps list
|
|
570
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
571
|
+
if (tokens[i].type === 'ordered_list_open' &&
|
|
572
|
+
tokens[i].markup &&
|
|
573
|
+
tokens[i].level < token.level) {
|
|
574
|
+
// Check if this ordered list has steps-list class (meaning it's in steps container)
|
|
575
|
+
let j = i - 1;
|
|
576
|
+
while (j >= 0) {
|
|
577
|
+
if (tokens[j].type === 'container_steps_open') {
|
|
578
|
+
isInStepsList = true;
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
if (tokens[j].type === 'container_steps_close') {
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
j--;
|
|
585
|
+
}
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
305
589
|
|
|
306
|
-
|
|
307
|
-
|
|
590
|
+
if (isInStepsList) {
|
|
591
|
+
return '<li class="step-item">';
|
|
592
|
+
}
|
|
308
593
|
|
|
309
|
-
|
|
310
|
-
|
|
594
|
+
// Default behavior for non-step list items
|
|
595
|
+
return '<li>';
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
// Enhanced tabs renderers
|
|
599
|
+
md.renderer.rules.tabs_open = (tokens, idx) => {
|
|
600
|
+
const token = tokens[idx];
|
|
601
|
+
return `<div class="${token.attrs.map(attr => attr[1]).join(' ')}">`;
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
md.renderer.rules.tabs_nav_open = () => '<div class="docmd-tabs-nav">';
|
|
605
|
+
md.renderer.rules.tabs_nav_close = () => '</div>';
|
|
606
|
+
|
|
607
|
+
md.renderer.rules.tabs_nav_item = (tokens, idx) => {
|
|
608
|
+
const token = tokens[idx];
|
|
609
|
+
return `<div class="${token.attrs[0][1]}">${token.content}</div>`;
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
md.renderer.rules.tabs_content_open = () => '<div class="docmd-tabs-content">';
|
|
613
|
+
md.renderer.rules.tabs_content_close = () => '</div>';
|
|
614
|
+
|
|
615
|
+
md.renderer.rules.tab_pane_open = (tokens, idx) => {
|
|
616
|
+
const token = tokens[idx];
|
|
617
|
+
return `<div class="${token.attrs[0][1]}">`;
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
md.renderer.rules.tab_pane_close = () => '</div>';
|
|
621
|
+
|
|
622
|
+
md.renderer.rules.tabs_close = () => '</div>';
|
|
623
|
+
|
|
624
|
+
// Override the default image renderer to properly handle attributes like {.class}.
|
|
625
|
+
const defaultImageRenderer = md.renderer.rules.image;
|
|
626
|
+
md.renderer.rules.image = function(tokens, idx, options, env, self) {
|
|
627
|
+
const renderedImage = defaultImageRenderer(tokens, idx, options, env, self);
|
|
628
|
+
const nextToken = tokens[idx + 1];
|
|
629
|
+
if (nextToken && nextToken.type === 'attrs_block') {
|
|
630
|
+
const attrs = nextToken.attrs || [];
|
|
631
|
+
const attrsStr = attrs.map(([name, value]) => `${name}="${value}"`).join(' ');
|
|
632
|
+
return renderedImage.replace('<img ', `<img ${attrsStr} `);
|
|
633
|
+
}
|
|
634
|
+
return renderedImage;
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
// Add IDs to headings for anchor links, used by the Table of Contents.
|
|
638
|
+
md.use((md) => {
|
|
639
|
+
const defaultRender = md.renderer.rules.heading_open || function(tokens, idx, options, env, self) {
|
|
640
|
+
return self.renderToken(tokens, idx, options);
|
|
641
|
+
};
|
|
642
|
+
md.renderer.rules.heading_open = function(tokens, idx, options, env, self) {
|
|
643
|
+
const token = tokens[idx];
|
|
644
|
+
const contentToken = tokens[idx + 1];
|
|
645
|
+
if (contentToken && contentToken.type === 'inline') {
|
|
646
|
+
const headingText = contentToken.content;
|
|
647
|
+
const id = headingText.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, '').replace(/--+/g, '-').replace(/^-+|-+$/g, '');
|
|
648
|
+
if (id) { token.attrSet('id', id); }
|
|
649
|
+
}
|
|
650
|
+
return defaultRender(tokens, idx, options, env, self);
|
|
651
|
+
};
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
// ===================================================================
|
|
656
|
+
// --- SAFE CONTAINER WRAPPER (FOR SIMPLE CONTAINERS) ---
|
|
657
|
+
// The safeContainer function has been replaced by the advanced nested container system
|
|
658
|
+
// which provides better nesting support and more robust parsing.
|
|
659
|
+
|
|
660
|
+
// ===================================================================
|
|
661
|
+
// --- ADVANCED NESTED CONTAINER SYSTEM IMPLEMENTATION ---
|
|
662
|
+
// ===================================================================
|
|
663
|
+
|
|
664
|
+
// The advanced nested container system is now implemented above
|
|
665
|
+
// All containers (card, callout, button, steps, tabs) are handled by the new system
|
|
666
|
+
// which supports seamless nesting of any container within any other container.
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
// --- UTILITY AND PROCESSING FUNCTIONS ---
|
|
311
670
|
|
|
312
|
-
/**
|
|
313
|
-
* Decodes HTML entities in a string
|
|
314
|
-
* @param {string} html - The HTML string to decode
|
|
315
|
-
* @returns {string} - Decoded string
|
|
316
|
-
*/
|
|
317
671
|
function decodeHtmlEntities(html) {
|
|
318
|
-
return html
|
|
319
|
-
.replace(/&/g, '&')
|
|
320
|
-
.replace(/</g, '<')
|
|
321
|
-
.replace(/>/g, '>')
|
|
322
|
-
.replace(/"/g, '"')
|
|
323
|
-
.replace(/'/g, "'")
|
|
324
|
-
.replace(/ /g, ' ');
|
|
672
|
+
return html.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, ' ');
|
|
325
673
|
}
|
|
326
674
|
|
|
327
|
-
/**
|
|
328
|
-
* Extracts headings from HTML content for table of contents generation
|
|
329
|
-
* @param {string} htmlContent - The rendered HTML content
|
|
330
|
-
* @returns {Array} - Array of heading objects with id, level, and text
|
|
331
|
-
*/
|
|
332
675
|
function extractHeadingsFromHtml(htmlContent) {
|
|
333
676
|
const headings = [];
|
|
334
|
-
|
|
335
|
-
// Regular expression to find heading tags (h1-h6) with their content and id attributes
|
|
336
677
|
const headingRegex = /<h([1-6])[^>]*?id="([^"]*)"[^>]*?>([\s\S]*?)<\/h\1>/g;
|
|
337
|
-
|
|
338
678
|
let match;
|
|
339
679
|
while ((match = headingRegex.exec(htmlContent)) !== null) {
|
|
340
680
|
const level = parseInt(match[1], 10);
|
|
341
681
|
const id = match[2];
|
|
342
|
-
|
|
343
|
-
const textWithTags = match[3].replace(/<\/?[^>]+(>|$)/g, '');
|
|
344
|
-
// Decode any HTML entities in the text
|
|
345
|
-
const text = decodeHtmlEntities(textWithTags);
|
|
346
|
-
|
|
682
|
+
const text = decodeHtmlEntities(match[3].replace(/<\/?[^>]+(>|$)/g, ''));
|
|
347
683
|
headings.push({ id, level, text });
|
|
348
684
|
}
|
|
349
|
-
|
|
350
685
|
return headings;
|
|
351
686
|
}
|
|
352
687
|
|
|
353
|
-
async function processMarkdownFile(filePath, options = { isDev: false }) {
|
|
688
|
+
async function processMarkdownFile(filePath, options = { isDev: false }, config) {
|
|
354
689
|
const rawContent = await fs.readFile(filePath, 'utf8');
|
|
355
|
-
let frontmatter, markdownContent;
|
|
356
|
-
|
|
357
|
-
try {
|
|
358
|
-
const parsed = matter(rawContent);
|
|
359
|
-
frontmatter = parsed.data;
|
|
360
|
-
markdownContent = parsed.content;
|
|
361
|
-
} catch (e) {
|
|
362
|
-
if (e.name === 'YAMLException') {
|
|
363
|
-
// Provide more specific error for YAML parsing issues
|
|
364
|
-
const errorMessage = `Error parsing YAML frontmatter in ${formatPathForDisplay(filePath)}: ${e.reason || e.message}${e.mark ? ` at line ${e.mark.line + 1}, column ${e.mark.column + 1}` : ''}. Please check the syntax.`;
|
|
365
|
-
console.error(`❌ ${errorMessage}`);
|
|
366
|
-
throw new Error(errorMessage); // Propagate error to stop build/dev
|
|
367
|
-
}
|
|
368
|
-
// For other errors from gray-matter or unknown errors
|
|
369
|
-
console.error(`❌ Error processing frontmatter in ${formatPathForDisplay(filePath)}: ${e.message}`);
|
|
370
|
-
throw e;
|
|
371
|
-
}
|
|
690
|
+
let { data: frontmatter, content: markdownContent } = matter(rawContent);
|
|
372
691
|
|
|
692
|
+
// Handle autoTitleFromH1
|
|
373
693
|
if (!frontmatter.title) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if (!options.isDev) {
|
|
383
|
-
console.log(`📄 Processing no-style page: ${formatPathForDisplay(filePath)} - Using raw HTML content`);
|
|
694
|
+
if (config.autoTitleFromH1 !== false) { // Default to true
|
|
695
|
+
const h1Match = markdownContent.match(/^#\s+(.*)/m);
|
|
696
|
+
if (h1Match && h1Match[1]) {
|
|
697
|
+
frontmatter.title = h1Match[1].trim();
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
if (!frontmatter.title) {
|
|
701
|
+
console.warn(`⚠️ Warning: Markdown file ${formatPathForDisplay(filePath)} has no title in frontmatter and no H1 fallback. The page header will be hidden.`);
|
|
384
702
|
}
|
|
385
|
-
|
|
386
|
-
// For no-style pages, we'll use the raw content directly
|
|
387
|
-
// No markdown processing, no HTML escaping
|
|
388
|
-
const htmlContent = markdownContent;
|
|
389
|
-
|
|
390
|
-
// Extract headings for table of contents (if needed)
|
|
391
|
-
const headings = extractHeadingsFromHtml(htmlContent);
|
|
392
|
-
|
|
393
|
-
return {
|
|
394
|
-
frontmatter,
|
|
395
|
-
htmlContent,
|
|
396
|
-
headings,
|
|
397
|
-
};
|
|
398
703
|
}
|
|
399
704
|
|
|
400
|
-
//
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if (isContainerDocumentation) {
|
|
409
|
-
markdownContent = escapeContainerSyntax(markdownContent);
|
|
705
|
+
// For no-style pages, skip markdown processing and treat content as raw HTML
|
|
706
|
+
let htmlContent, headings;
|
|
707
|
+
if (frontmatter.noStyle === true) {
|
|
708
|
+
htmlContent = markdownContent; // Use raw content as HTML
|
|
709
|
+
headings = []; // No headings extraction for no-style pages
|
|
710
|
+
} else {
|
|
711
|
+
htmlContent = md.render(markdownContent);
|
|
712
|
+
headings = extractHeadingsFromHtml(htmlContent);
|
|
410
713
|
}
|
|
411
|
-
|
|
412
|
-
// Normalize container syntax
|
|
413
|
-
const normalizedContent = normalizeContainerSyntax(markdownContent);
|
|
414
|
-
|
|
415
|
-
// Render to HTML
|
|
416
|
-
let htmlContent = md.render(normalizedContent);
|
|
417
|
-
|
|
418
|
-
// Apply steps formatting
|
|
419
|
-
htmlContent = processStepsContent(htmlContent);
|
|
420
|
-
|
|
421
|
-
// Fix any specific issues
|
|
422
|
-
// 1. Fix the issue with "These custom containers" paragraph in custom-containers.md
|
|
423
|
-
htmlContent = htmlContent.replace(
|
|
424
|
-
/<p>You should see "Application started successfully!" in your console.\s*<\/p>\s*<p>::: These custom containers/,
|
|
425
|
-
'<p>You should see "Application started successfully!" in your console.</p></div><p>These custom containers'
|
|
426
|
-
);
|
|
427
|
-
|
|
428
|
-
// 2. Fix any remaining ::: markers at the start of paragraphs
|
|
429
|
-
htmlContent = htmlContent.replace(/<p>:::\s+(.*?)<\/p>/g, '<p>$1</p>');
|
|
430
|
-
|
|
431
|
-
// 3. Fix any broken Asterisk steps
|
|
432
|
-
htmlContent = htmlContent.replace(
|
|
433
|
-
/<div class="step"><h4>\*\. <strong><\/strong><\/h4>(.+?)<\/strong>/,
|
|
434
|
-
'<div class="step"><h4>*. <strong>$1</strong>'
|
|
435
|
-
);
|
|
436
|
-
|
|
437
|
-
// 4. Replace our special escape marker with nothing (to fix backslash issue in rendered HTML)
|
|
438
|
-
htmlContent = htmlContent.replace(/___DOCMD_CONTAINER_ESCAPED___/g, '');
|
|
439
|
-
|
|
440
|
-
// Extract headings for table of contents
|
|
441
|
-
const headings = extractHeadingsFromHtml(htmlContent);
|
|
442
714
|
|
|
443
715
|
return {
|
|
444
|
-
frontmatter
|
|
445
|
-
title: "Untitled Page", // Default if not provided and no fallback
|
|
446
|
-
...frontmatter
|
|
447
|
-
},
|
|
716
|
+
frontmatter,
|
|
448
717
|
htmlContent,
|
|
449
|
-
headings,
|
|
718
|
+
headings,
|
|
450
719
|
};
|
|
451
720
|
}
|
|
452
721
|
|
|
453
|
-
// Add findMarkdownFiles function
|
|
454
|
-
/**
|
|
455
|
-
* Recursively finds all Markdown files in a directory and its subdirectories
|
|
456
|
-
* @param {string} dir - Directory to search in
|
|
457
|
-
* @returns {Promise<string[]>} - Array of file paths
|
|
458
|
-
*/
|
|
459
722
|
async function findMarkdownFiles(dir) {
|
|
460
723
|
let files = [];
|
|
461
724
|
const items = await fs.readdir(dir, { withFileTypes: true });
|
|
@@ -470,9 +733,9 @@ async function findMarkdownFiles(dir) {
|
|
|
470
733
|
return files;
|
|
471
734
|
}
|
|
472
735
|
|
|
473
|
-
module.exports = {
|
|
474
|
-
processMarkdownFile,
|
|
475
|
-
mdInstance: md,
|
|
736
|
+
module.exports = {
|
|
737
|
+
processMarkdownFile,
|
|
738
|
+
mdInstance: md,
|
|
476
739
|
extractHeadingsFromHtml,
|
|
477
|
-
findMarkdownFiles
|
|
740
|
+
findMarkdownFiles
|
|
478
741
|
};
|