@mgks/docmd 0.2.7 → 0.2.9

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.
@@ -100,6 +100,27 @@ async function generateHtmlPage(templateData) {
100
100
 
101
101
  const isActivePage = currentPagePath && content && content.trim().length > 0;
102
102
 
103
+ // Calculate Edit Link
104
+ let editUrl = null;
105
+ let editLinkText = 'Edit this page';
106
+
107
+ if (config.editLink && config.editLink.enabled && config.editLink.baseUrl) {
108
+ // Normalize URL (remove trailing slash)
109
+ const baseUrl = config.editLink.baseUrl.replace(/\/$/, '');
110
+
111
+ // Get the source file path relative to srcDir
112
+ let relativeSourcePath = outputPath
113
+ .replace(/\/index\.html$/, '.md') // folder/index.html -> folder.md
114
+ .replace(/\\/g, '/'); // fix windows slashes
115
+
116
+ // Special case: The root index.html comes from index.md
117
+ if (relativeSourcePath === 'index.html') relativeSourcePath = 'index.md';
118
+
119
+ // Let's assume a standard 1:1 mapping for v0.2.x
120
+ editUrl = `${baseUrl}/${relativeSourcePath}`;
121
+ editLinkText = config.editLink.text || editLinkText;
122
+ }
123
+
103
124
  const ejsData = {
104
125
  content,
105
126
  pageTitle,
@@ -107,6 +128,8 @@ async function generateHtmlPage(templateData) {
107
128
  description: frontmatter.description,
108
129
  siteTitle,
109
130
  navigationHtml,
131
+ editUrl,
132
+ editLinkText,
110
133
  defaultMode: config.theme?.defaultMode || 'light',
111
134
  relativePathToRoot,
112
135
  logo: config.logo,
@@ -0,0 +1,94 @@
1
+ // Source file from the docmd project — https://github.com/mgks/docmd
2
+
3
+ const containers = {
4
+ card: {
5
+ name: 'card',
6
+ render: (tokens, idx) => {
7
+ if (tokens[idx].nesting === 1) {
8
+ const title = tokens[idx].info ? tokens[idx].info.trim() : '';
9
+ return `<div class="docmd-container card">${title ? `<div class="card-title">${title}</div>` : ''}<div class="card-content">`;
10
+ }
11
+ return '</div></div>';
12
+ }
13
+ },
14
+ callout: {
15
+ name: 'callout',
16
+ render: (tokens, idx) => {
17
+ if (tokens[idx].nesting === 1) {
18
+ const [type, ...titleParts] = tokens[idx].info.split(' ');
19
+ const title = titleParts.join(' ');
20
+ return `<div class="docmd-container callout callout-${type}">${title ? `<div class="callout-title">${title}</div>` : ''}<div class="callout-content">`;
21
+ }
22
+ return '</div></div>';
23
+ }
24
+ },
25
+ button: {
26
+ name: 'button',
27
+ selfClosing: true,
28
+ render: (tokens, idx) => {
29
+ if (tokens[idx].nesting === 1) {
30
+ const parts = tokens[idx].info.split(' ');
31
+ const text = parts[0];
32
+ const url = parts[1];
33
+ const color = parts[2];
34
+ const colorStyle = color && color.startsWith('color:') ? ` style="background-color: ${color.split(':')[1]}"` : '';
35
+
36
+ let finalUrl = url;
37
+ let targetAttr = '';
38
+ if (url && url.startsWith('external:')) {
39
+ finalUrl = url.substring(9);
40
+ targetAttr = ' target="_blank" rel="noopener noreferrer"';
41
+ }
42
+
43
+ return `<a href="${finalUrl}" class="docmd-button"${colorStyle}${targetAttr}>${text.replace(/_/g, ' ')}</a>`;
44
+ }
45
+ return '';
46
+ }
47
+ },
48
+ steps: {
49
+ name: 'steps',
50
+ render: (tokens, idx) => {
51
+ if (tokens[idx].nesting === 1) {
52
+ return '<div class="docmd-container steps steps-reset steps-numbering">';
53
+ }
54
+ return '</div>';
55
+ }
56
+ },
57
+ collapsible: {
58
+ name: 'collapsible',
59
+ render: (tokens, idx) => {
60
+ if (tokens[idx].nesting === 1) {
61
+ const info = tokens[idx].info.trim();
62
+ let isOpen = false;
63
+ let displayTitle = info;
64
+
65
+ if (info.startsWith('open ')) {
66
+ isOpen = true;
67
+ displayTitle = info.substring(5);
68
+ }
69
+ if (!displayTitle) displayTitle = 'Click to expand';
70
+
71
+ return `<details class="docmd-container collapsible" ${isOpen ? 'open' : ''}>
72
+ <summary class="collapsible-summary">
73
+ <span class="collapsible-title">${displayTitle}</span>
74
+ <span class="collapsible-arrow">
75
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>
76
+ </span>
77
+ </summary>
78
+ <div class="collapsible-content">`;
79
+ }
80
+ return '</div></details>\n';
81
+ }
82
+ },
83
+ changelog: {
84
+ name: 'changelog',
85
+ render: (tokens, idx) => {
86
+ if (tokens[idx].nesting === 1) {
87
+ return '<div class="docmd-container changelog-timeline">';
88
+ }
89
+ return '</div>';
90
+ }
91
+ }
92
+ };
93
+
94
+ module.exports = { containers };
@@ -0,0 +1,90 @@
1
+ // Source file from the docmd project — https://github.com/mgks/docmd
2
+
3
+ const customOrderedListOpenRenderer = function(tokens, idx, options, env, self) {
4
+ const token = tokens[idx];
5
+ let isInSteps = false;
6
+ for (let i = idx - 1; i >= 0; i--) {
7
+ if (tokens[i].type === 'container_steps_open') {
8
+ isInSteps = true;
9
+ break;
10
+ }
11
+ if (tokens[i].type === 'container_steps_close') {
12
+ break;
13
+ }
14
+ }
15
+ if (isInSteps) {
16
+ const start = token.attrGet('start');
17
+ return start ? `<ol class="steps-list" start="${start}">` : '<ol class="steps-list">';
18
+ }
19
+ const start = token.attrGet('start');
20
+ return start ? `<ol start="${start}">` : '<ol>';
21
+ };
22
+
23
+ const customListItemOpenRenderer = function(tokens, idx, options, env, self) {
24
+ const token = tokens[idx];
25
+ let isInStepsList = false;
26
+ for (let i = idx - 1; i >= 0; i--) {
27
+ if (tokens[i].type === 'ordered_list_open' && tokens[i].markup && tokens[i].level < token.level) {
28
+ let j = i - 1;
29
+ while (j >= 0) {
30
+ if (tokens[j].type === 'container_steps_open') {
31
+ isInStepsList = true;
32
+ break;
33
+ }
34
+ if (tokens[j].type === 'container_steps_close') {
35
+ break;
36
+ }
37
+ j--;
38
+ }
39
+ break;
40
+ }
41
+ }
42
+ if (isInStepsList) {
43
+ return '<li class="step-item">';
44
+ }
45
+ return '<li>';
46
+ };
47
+
48
+ const customImageRenderer = function(tokens, idx, options, env, self) {
49
+ const defaultImageRenderer = function(tokens, idx, options, env, self) { return self.renderToken(tokens, idx, options); };
50
+ const renderedImage = defaultImageRenderer(tokens, idx, options, env, self);
51
+ const nextToken = tokens[idx + 1];
52
+ if (nextToken && nextToken.type === 'attrs_block') {
53
+ const attrs = nextToken.attrs || [];
54
+ const attrsStr = attrs.map(([name, value]) => `${name}="${value}"`).join(' ');
55
+ return renderedImage.replace('<img ', `<img ${attrsStr} `);
56
+ }
57
+ return renderedImage;
58
+ };
59
+
60
+ // Table Wrapper for horizontal scrolling
61
+ const tableOpenRenderer = (tokens, idx, options, env, self) => '<div class="table-wrapper">' + self.renderToken(tokens, idx, options);
62
+ const tableCloseRenderer = (tokens, idx, options, env, self) => self.renderToken(tokens, idx, options) + '</div>';
63
+
64
+ // Tabs Renderers
65
+ const tabsOpenRenderer = (tokens, idx) => `<div class="${tokens[idx].attrs.map(attr => attr[1]).join(' ')}">`;
66
+ const tabsNavOpenRenderer = () => '<div class="docmd-tabs-nav">';
67
+ const tabsNavCloseRenderer = () => '</div>';
68
+ const tabsNavItemRenderer = (tokens, idx) => `<div class="${tokens[idx].attrs[0][1]}">${tokens[idx].content}</div>`;
69
+ const tabsContentOpenRenderer = () => '<div class="docmd-tabs-content">';
70
+ const tabsContentCloseRenderer = () => '</div>';
71
+ const tabPaneOpenRenderer = (tokens, idx) => `<div class="${tokens[idx].attrs[0][1]}">`;
72
+ const tabPaneCloseRenderer = () => '</div>';
73
+ const tabsCloseRenderer = () => '</div>';
74
+
75
+ module.exports = {
76
+ customOrderedListOpenRenderer,
77
+ customListItemOpenRenderer,
78
+ customImageRenderer,
79
+ tableOpenRenderer,
80
+ tableCloseRenderer,
81
+ tabsOpenRenderer,
82
+ tabsNavOpenRenderer,
83
+ tabsNavCloseRenderer,
84
+ tabsNavItemRenderer,
85
+ tabsContentOpenRenderer,
86
+ tabsContentCloseRenderer,
87
+ tabPaneOpenRenderer,
88
+ tabPaneCloseRenderer,
89
+ tabsCloseRenderer
90
+ };
@@ -0,0 +1,355 @@
1
+ // Source file from the docmd project — https://github.com/mgks/docmd
2
+
3
+ const MarkdownIt = require('markdown-it'); // Required for inner rendering fallback logic
4
+ const { containers } = require('./containers');
5
+
6
+ // --- Helper: Create isolated parser for tabs (prevents recursion stack issues) ---
7
+ // Note: We pass a callback to get the main config logic from setup.js later if needed,
8
+ // but for now we reconstruct a basic one for tab/changelog internals to ensure plugins run.
9
+ // Ideally, we prefer reusing state.md.render() where possible.
10
+
11
+ // --- 1. Advanced Container Rule (Nesting Logic) ---
12
+ function advancedContainerRule(state, startLine, endLine, silent) {
13
+ const start = state.bMarks[startLine] + state.tShift[startLine];
14
+ const max = state.eMarks[startLine];
15
+ const lineContent = state.src.slice(start, max).trim();
16
+
17
+ const containerMatch = lineContent.match(/^:::\s*(\w+)(?:\s+(.+))?$/);
18
+ if (!containerMatch) return false;
19
+
20
+ const [, containerName, params] = containerMatch;
21
+ const container = containers[containerName];
22
+ if (!container) return false;
23
+ if (silent) return true;
24
+
25
+ if (container.selfClosing) {
26
+ const openToken = state.push(`container_${containerName}_open`, 'div', 1);
27
+ openToken.info = params || '';
28
+ const closeToken = state.push(`container_${containerName}_close`, 'div', -1);
29
+ state.line = startLine + 1;
30
+ return true;
31
+ }
32
+
33
+ let nextLine = startLine;
34
+ let found = false;
35
+ let depth = 1;
36
+
37
+ while (nextLine < endLine) {
38
+ nextLine++;
39
+ const nextStart = state.bMarks[nextLine] + state.tShift[nextLine];
40
+ const nextMax = state.eMarks[nextLine];
41
+ const nextContent = state.src.slice(nextStart, nextMax).trim();
42
+
43
+ if (nextContent.startsWith(':::')) {
44
+ const containerMatch = nextContent.match(/^:::\s*(\w+)/);
45
+ if (containerMatch && containerMatch[1] !== containerName) {
46
+ const innerContainer = containers[containerMatch[1]];
47
+ if (innerContainer && innerContainer.render && !innerContainer.selfClosing) {
48
+ depth++;
49
+ }
50
+ continue;
51
+ }
52
+ }
53
+
54
+ if (nextContent === ':::') {
55
+ depth--;
56
+ if (depth === 0) {
57
+ found = true;
58
+ break;
59
+ }
60
+ }
61
+ }
62
+
63
+ if (!found) return false;
64
+
65
+ const openToken = state.push(`container_${containerName}_open`, 'div', 1);
66
+ openToken.info = params || '';
67
+
68
+ const oldParentType = state.parentType;
69
+ const oldLineMax = state.lineMax;
70
+ state.parentType = 'container';
71
+ state.lineMax = nextLine;
72
+
73
+ state.md.block.tokenize(state, startLine + 1, nextLine);
74
+
75
+ const closeToken = state.push(`container_${containerName}_close`, 'div', -1);
76
+
77
+ state.parentType = oldParentType;
78
+ state.lineMax = oldLineMax;
79
+ state.line = nextLine + 1;
80
+
81
+ return true;
82
+ }
83
+
84
+ // --- 2. Changelog Timeline Rule (FIXED FOR NESTING) ---
85
+ function changelogTimelineRule(state, startLine, endLine, silent) {
86
+ const start = state.bMarks[startLine] + state.tShift[startLine];
87
+ const max = state.eMarks[startLine];
88
+ const lineContent = state.src.slice(start, max).trim();
89
+
90
+ if (lineContent !== '::: changelog') return false;
91
+ if (silent) return true;
92
+
93
+ let nextLine = startLine;
94
+ let found = false;
95
+ let depth = 1;
96
+
97
+ while (nextLine < endLine) {
98
+ nextLine++;
99
+ const nextStart = state.bMarks[nextLine] + state.tShift[nextLine];
100
+ const nextMax = state.eMarks[nextLine];
101
+ const nextContent = state.src.slice(nextStart, nextMax).trim();
102
+
103
+ if (nextContent.startsWith(':::')) {
104
+ const match = nextContent.match(/^:::\s*(\w+)/);
105
+ if (match) {
106
+ const containerName = match[1];
107
+ // Don't count self-closing like buttons for depth
108
+ const containerDef = containers[containerName];
109
+ if (!containerDef || !containerDef.selfClosing) {
110
+ depth++;
111
+ }
112
+ }
113
+ }
114
+
115
+ if (nextContent === ':::') {
116
+ depth--;
117
+ if (depth === 0) { found = true; break; }
118
+ }
119
+ }
120
+
121
+ if (!found) return false;
122
+
123
+ let content = '';
124
+ for (let i = startLine + 1; i < nextLine; i++) {
125
+ const lineStart = state.bMarks[i] + state.tShift[i];
126
+ const lineEnd = state.eMarks[i];
127
+ content += state.src.slice(lineStart, lineEnd) + '\n';
128
+ }
129
+
130
+ const lines = content.split('\n');
131
+ const entries = [];
132
+ let currentEntry = null;
133
+ let currentContent = [];
134
+
135
+ for (let i = 0; i < lines.length; i++) {
136
+ const line = lines[i].trim();
137
+ const markerMatch = line.match(/^==\s+(.+)$/);
138
+
139
+ if (markerMatch) {
140
+ if (currentEntry) {
141
+ currentEntry.content = currentContent.join('\n');
142
+ entries.push(currentEntry);
143
+ }
144
+ currentEntry = { meta: markerMatch[1], content: '' };
145
+ currentContent = [];
146
+ } else if (currentEntry) {
147
+ currentContent.push(lines[i]);
148
+ }
149
+ }
150
+ if (currentEntry) {
151
+ currentEntry.content = currentContent.join('\n');
152
+ entries.push(currentEntry);
153
+ }
154
+
155
+ state.push('container_changelog_open', 'div', 1);
156
+
157
+ entries.forEach(entry => {
158
+ const entryOpen = state.push('html_block', '', 0);
159
+ entryOpen.content = `<div class="changelog-entry">
160
+ <div class="changelog-meta"><span class="changelog-date">${entry.meta}</span></div>
161
+ <div class="changelog-body">`;
162
+
163
+ // --- FIX: Use parent parser to render inner content ---
164
+ // This ensures callouts/cards inside changelogs are parsed
165
+ entryOpen.content += state.md.render(entry.content, state.env);
166
+
167
+ const entryClose = state.push('html_block', '', 0);
168
+ entryClose.content = `</div></div>`;
169
+ });
170
+
171
+ state.push('container_changelog_close', 'div', -1);
172
+ state.line = nextLine + 1;
173
+ return true;
174
+ }
175
+
176
+ // --- 3. Steps Container Rule ---
177
+ function stepsContainerRule(state, startLine, endLine, silent) {
178
+ const start = state.bMarks[startLine] + state.tShift[startLine];
179
+ const max = state.eMarks[startLine];
180
+ const lineContent = state.src.slice(start, max).trim();
181
+ if (lineContent !== '::: steps') return false;
182
+ if (silent) return true;
183
+
184
+ let nextLine = startLine;
185
+ let found = false;
186
+ let depth = 1;
187
+
188
+ while (nextLine < endLine) {
189
+ nextLine++;
190
+ const nextStart = state.bMarks[nextLine] + state.tShift[nextLine];
191
+ const nextMax = state.eMarks[nextLine];
192
+ const nextContent = state.src.slice(nextStart, nextMax).trim();
193
+
194
+ if (nextContent.startsWith('== tab')) { continue; }
195
+
196
+ if (nextContent.startsWith(':::')) {
197
+ const containerMatch = nextContent.match(/^:::\s*(\w+)/);
198
+ if (containerMatch) {
199
+ const containerName = containerMatch[1];
200
+ const innerContainer = containers[containerName];
201
+ if (innerContainer && !innerContainer.selfClosing) {
202
+ depth++;
203
+ }
204
+ continue;
205
+ }
206
+ }
207
+
208
+ if (nextContent === ':::') {
209
+ depth--;
210
+ if (depth === 0) { found = true; break; }
211
+ }
212
+ }
213
+
214
+ if (!found) return false;
215
+
216
+ const openToken = state.push('container_steps_open', 'div', 1);
217
+ openToken.info = '';
218
+
219
+ const oldParentType = state.parentType;
220
+ const oldLineMax = state.lineMax;
221
+ state.parentType = 'container';
222
+ state.lineMax = nextLine;
223
+ state.md.block.tokenize(state, startLine + 1, nextLine);
224
+ const closeToken = state.push('container_steps_close', 'div', -1);
225
+ state.parentType = oldParentType;
226
+ state.lineMax = oldLineMax;
227
+ state.line = nextLine + 1;
228
+ return true;
229
+ }
230
+
231
+ // --- 4. Enhanced Tabs Rule ---
232
+ function enhancedTabsRule(state, startLine, endLine, silent) {
233
+ const start = state.bMarks[startLine] + state.tShift[startLine];
234
+ const max = state.eMarks[startLine];
235
+ const lineContent = state.src.slice(start, max).trim();
236
+
237
+ if (lineContent !== '::: tabs') return false;
238
+ if (silent) return true;
239
+
240
+ let nextLine = startLine;
241
+ let found = false;
242
+ let depth = 1;
243
+ while (nextLine < endLine) {
244
+ nextLine++;
245
+ const nextStart = state.bMarks[nextLine] + state.tShift[nextLine];
246
+ const nextMax = state.eMarks[nextLine];
247
+ const nextContent = state.src.slice(nextStart, nextMax).trim();
248
+
249
+ if (nextContent.startsWith(':::')) {
250
+ const containerMatch = nextContent.match(/^:::\s*(\w+)/);
251
+ if (containerMatch && containerMatch[1] !== 'tabs') {
252
+ if (containerMatch[1] === 'steps') continue;
253
+ const innerContainer = containers[containerMatch[1]];
254
+ if (innerContainer && !innerContainer.selfClosing) depth++;
255
+ continue;
256
+ }
257
+ }
258
+
259
+ if (nextContent === ':::') {
260
+ depth--;
261
+ if (depth === 0) { found = true; break; }
262
+ }
263
+ }
264
+ if (!found) return false;
265
+
266
+ let content = '';
267
+ for (let i = startLine + 1; i < nextLine; i++) {
268
+ const lineStart = state.bMarks[i] + state.tShift[i];
269
+ const lineEnd = state.eMarks[i];
270
+ content += state.src.slice(lineStart, lineEnd) + '\n';
271
+ }
272
+
273
+ const lines = content.split('\n');
274
+ const tabs = [];
275
+ let currentTab = null;
276
+ let currentContent = [];
277
+
278
+ for (let i = 0; i < lines.length; i++) {
279
+ const line = lines[i].trim();
280
+ const tabMatch = line.match(/^==\s*tab\s+(?:"([^"]+)"|(\S+))$/);
281
+
282
+ if (tabMatch) {
283
+ if (currentTab) {
284
+ currentTab.content = currentContent.join('\n').trim();
285
+ tabs.push(currentTab);
286
+ }
287
+ const title = tabMatch[1] || tabMatch[2];
288
+ currentTab = { title: title, content: '' };
289
+ currentContent = [];
290
+ } else if (currentTab) {
291
+ if (lines[i].trim() && !lines[i].trim().startsWith('==')) {
292
+ currentContent.push(lines[i]);
293
+ }
294
+ }
295
+ }
296
+ if (currentTab) {
297
+ currentTab.content = currentContent.join('\n').trim();
298
+ tabs.push(currentTab);
299
+ }
300
+
301
+ const openToken = state.push('tabs_open', 'div', 1);
302
+ openToken.attrs = [['class', 'docmd-tabs']];
303
+
304
+ const navToken = state.push('tabs_nav_open', 'div', 1);
305
+ navToken.attrs = [['class', 'docmd-tabs-nav']];
306
+ tabs.forEach((tab, index) => {
307
+ const navItemToken = state.push('tabs_nav_item', 'div', 0);
308
+ navItemToken.attrs = [['class', `docmd-tabs-nav-item ${index === 0 ? 'active' : ''}`]];
309
+ navItemToken.content = tab.title;
310
+ });
311
+ state.push('tabs_nav_close', 'div', -1);
312
+
313
+ const contentToken = state.push('tabs_content_open', 'div', 1);
314
+ contentToken.attrs = [['class', 'docmd-tabs-content']];
315
+ tabs.forEach((tab, index) => {
316
+ const paneToken = state.push('tab_pane_open', 'div', 1);
317
+ paneToken.attrs = [['class', `docmd-tab-pane ${index === 0 ? 'active' : ''}`]];
318
+
319
+ if (tab.content.trim()) {
320
+ // Use recursion here if possible, or create basic instance to prevent circular dep issues in this modular file
321
+ // Since we are inside rules.js, we rely on state.md being available or fallback to simple render
322
+ const renderedContent = state.md.render(tab.content.trim(), state.env);
323
+ const htmlToken = state.push('html_block', '', 0);
324
+ htmlToken.content = renderedContent;
325
+ }
326
+
327
+ state.push('tab_pane_close', 'div', -1);
328
+ });
329
+ state.push('tabs_content_close', 'div', -1);
330
+ state.push('tabs_close', 'div', -1);
331
+ state.line = nextLine + 1;
332
+ return true;
333
+ }
334
+
335
+ // --- 5. Standalone Closing Rule ---
336
+ const standaloneClosingRule = (state, startLine, endLine, silent) => {
337
+ const start = state.bMarks[startLine] + state.tShift[startLine];
338
+ const max = state.eMarks[startLine];
339
+ const lineContent = state.src.slice(start, max).trim();
340
+
341
+ if (lineContent === ':::') {
342
+ if (silent) return true;
343
+ state.line = startLine + 1;
344
+ return true;
345
+ }
346
+ return false;
347
+ };
348
+
349
+ module.exports = {
350
+ advancedContainerRule,
351
+ changelogTimelineRule,
352
+ stepsContainerRule,
353
+ enhancedTabsRule,
354
+ standaloneClosingRule
355
+ };