@mgks/docmd 0.3.11 → 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 -1757
  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 -185
  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 -97
  45. package/src/templates/no-style.ejs +0 -166
  46. package/src/templates/partials/theme-init.js +0 -36
  47. package/src/templates/toc.ejs +0 -38
@@ -1,402 +0,0 @@
1
- // Source file from the docmd project — https://github.com/docmd-io/docmd
2
-
3
- const MarkdownIt = require('markdown-it');
4
- const { containers } = require('./containers');
5
-
6
- // --- Helper: Smart Dedent ---
7
- // This handles the "Tab Indentation" and "Code Block" nesting issues
8
- function smartDedent(str) {
9
- const lines = str.split('\n');
10
-
11
- // 1. Calculate global minimum indent (ignoring empty lines)
12
- let minIndent = Infinity;
13
- lines.forEach(line => {
14
- if (line.trim().length === 0) return;
15
- const match = line.match(/^ */);
16
- const indent = match ? match[0].length : 0;
17
- if (indent < minIndent) minIndent = indent;
18
- });
19
-
20
- if (minIndent === Infinity) return str;
21
-
22
- // 2. Strip the common indent
23
- const dedented = lines.map(line => {
24
- if (line.trim().length === 0) return '';
25
- return line.substring(minIndent);
26
- });
27
-
28
- // 3. Fix Code Fences
29
- // If a line looks like a code fence (```) but is still indented 4+ spaces,
30
- // it will be parsed as an "Indented Code Block" containing text, not a Fence.
31
- // We force-pull these specific lines to the left to ensure they render as Fences.
32
- return dedented.map(line => {
33
- // Regex: 4 or more spaces, followed by 3 or more backticks/tildes
34
- if (/^\s{4,}(`{3,}|~{3,})/.test(line)) {
35
- return line.trimStart();
36
- }
37
- return line;
38
- }).join('\n');
39
- }
40
-
41
- // Helper to check if a line starts a fence
42
- function isFenceLine(line) {
43
- return /^(\s{0,3})(~{3,}|`{3,})/.test(line);
44
- }
45
-
46
- // --- 1. Advanced Container Rule ---
47
- function advancedContainerRule(state, startLine, endLine, silent) {
48
- const start = state.bMarks[startLine] + state.tShift[startLine];
49
- const max = state.eMarks[startLine];
50
- const lineContent = state.src.slice(start, max).trim();
51
-
52
- const containerMatch = lineContent.match(/^:::\s*(\w+)(?:\s+(.+))?$/);
53
- if (!containerMatch) return false;
54
-
55
- const [, containerName, params] = containerMatch;
56
- const container = containers[containerName];
57
- if (!container) return false;
58
- if (silent) return true;
59
-
60
- if (container.selfClosing) {
61
- const openToken = state.push(`container_${containerName}_open`, 'div', 1);
62
- openToken.info = params || '';
63
- const closeToken = state.push(`container_${containerName}_close`, 'div', -1);
64
- state.line = startLine + 1;
65
- return true;
66
- }
67
-
68
- let nextLine = startLine;
69
- let found = false;
70
- let depth = 1;
71
- let inFence = false;
72
-
73
- while (nextLine < endLine) {
74
- nextLine++;
75
- const nextStart = state.bMarks[nextLine] + state.tShift[nextLine];
76
- const nextMax = state.eMarks[nextLine];
77
- const nextContent = state.src.slice(nextStart, nextMax).trim();
78
-
79
- // Check for fences to prevent parsing ::: inside code blocks (#42)
80
- if (isFenceLine(nextContent)) inFence = !inFence;
81
-
82
- if (!inFence) {
83
- if (nextContent.startsWith(':::')) {
84
- const containerMatch = nextContent.match(/^:::\s*(\w+)/);
85
- if (containerMatch && containerMatch[1] !== containerName) {
86
- const innerContainer = containers[containerMatch[1]];
87
- if (innerContainer && innerContainer.render && !innerContainer.selfClosing) {
88
- depth++;
89
- }
90
- continue;
91
- }
92
- }
93
-
94
- if (nextContent === ':::') {
95
- depth--;
96
- if (depth === 0) {
97
- found = true;
98
- break;
99
- }
100
- }
101
- }
102
- }
103
-
104
- if (!found) return false;
105
-
106
- const openToken = state.push(`container_${containerName}_open`, 'div', 1);
107
- openToken.info = params || '';
108
-
109
- const oldParentType = state.parentType;
110
- const oldLineMax = state.lineMax;
111
- state.parentType = 'container';
112
- state.lineMax = nextLine;
113
-
114
- state.md.block.tokenize(state, startLine + 1, nextLine);
115
-
116
- const closeToken = state.push(`container_${containerName}_close`, 'div', -1);
117
-
118
- state.parentType = oldParentType;
119
- state.lineMax = oldLineMax;
120
- state.line = nextLine + 1;
121
-
122
- return true;
123
- }
124
-
125
- // --- 2. Changelog Timeline Rule ---
126
- function changelogTimelineRule(state, startLine, endLine, silent) {
127
- const start = state.bMarks[startLine] + state.tShift[startLine];
128
- const max = state.eMarks[startLine];
129
- const lineContent = state.src.slice(start, max).trim();
130
-
131
- if (lineContent !== '::: changelog') return false;
132
- if (silent) return true;
133
-
134
- let nextLine = startLine;
135
- let found = false;
136
- let depth = 1;
137
- let inFence = false;
138
-
139
- while (nextLine < endLine) {
140
- nextLine++;
141
- const nextStart = state.bMarks[nextLine] + state.tShift[nextLine];
142
- const nextMax = state.eMarks[nextLine];
143
- const nextContent = state.src.slice(nextStart, nextMax).trim();
144
-
145
- if (isFenceLine(nextContent)) inFence = !inFence;
146
-
147
- if (!inFence) {
148
- if (nextContent.startsWith(':::')) {
149
- const match = nextContent.match(/^:::\s*(\w+)/);
150
- if (match) {
151
- const containerName = match[1];
152
- const containerDef = containers[containerName];
153
- if (!containerDef || !containerDef.selfClosing) depth++;
154
- }
155
- }
156
- if (nextContent === ':::') {
157
- depth--;
158
- if (depth === 0) { found = true; break; }
159
- }
160
- }
161
- }
162
-
163
- if (!found) return false;
164
-
165
- let content = '';
166
- for (let i = startLine + 1; i < nextLine; i++) {
167
- const lineStart = state.bMarks[i] + state.tShift[i];
168
- const lineEnd = state.eMarks[i];
169
- content += state.src.slice(lineStart, lineEnd) + '\n';
170
- }
171
-
172
- const lines = content.split('\n');
173
- const entries = [];
174
- let currentEntry = null;
175
- let currentContentLines = [];
176
-
177
- for (let i = 0; i < lines.length; i++) {
178
- const rawLine = lines[i];
179
- const trimmedLine = rawLine.trim();
180
- const markerMatch = trimmedLine.match(/^==\s+(.+)$/);
181
-
182
- if (markerMatch) {
183
- if (currentEntry) {
184
- currentEntry.content = smartDedent(currentContentLines.join('\n'));
185
- entries.push(currentEntry);
186
- }
187
- currentEntry = { meta: markerMatch[1], content: '' };
188
- currentContentLines = [];
189
- } else if (currentEntry) {
190
- currentContentLines.push(rawLine);
191
- }
192
- }
193
- if (currentEntry) {
194
- currentEntry.content = smartDedent(currentContentLines.join('\n'));
195
- entries.push(currentEntry);
196
- }
197
-
198
- state.push('container_changelog_open', 'div', 1);
199
-
200
- entries.forEach(entry => {
201
- const entryOpen = state.push('html_block', '', 0);
202
- entryOpen.content = `<div class="changelog-entry">
203
- <div class="changelog-meta"><span class="changelog-date">${entry.meta}</span></div>
204
- <div class="changelog-body">`;
205
-
206
- entryOpen.content += state.md.render(entry.content, state.env);
207
-
208
- const entryClose = state.push('html_block', '', 0);
209
- entryClose.content = `</div></div>`;
210
- });
211
-
212
- state.push('container_changelog_close', 'div', -1);
213
- state.line = nextLine + 1;
214
- return true;
215
- }
216
-
217
- // --- 3. Steps Container Rule ---
218
- function stepsContainerRule(state, startLine, endLine, silent) {
219
- const start = state.bMarks[startLine] + state.tShift[startLine];
220
- const max = state.eMarks[startLine];
221
- const lineContent = state.src.slice(start, max).trim();
222
- if (lineContent !== '::: steps') return false;
223
- if (silent) return true;
224
-
225
- let nextLine = startLine;
226
- let found = false;
227
- let depth = 1;
228
- let inFence = false;
229
-
230
- while (nextLine < endLine) {
231
- nextLine++;
232
- const nextStart = state.bMarks[nextLine] + state.tShift[nextLine];
233
- const nextMax = state.eMarks[nextLine];
234
- const nextContent = state.src.slice(nextStart, nextMax).trim();
235
-
236
- if (isFenceLine(nextContent)) inFence = !inFence;
237
-
238
- if (!inFence) {
239
- if (nextContent.startsWith('== tab')) continue;
240
- if (nextContent.startsWith(':::')) {
241
- const containerMatch = nextContent.match(/^:::\s*(\w+)/);
242
- if (containerMatch) {
243
- const containerName = containerMatch[1];
244
- const innerContainer = containers[containerName];
245
- if (innerContainer && !innerContainer.selfClosing) depth++;
246
- continue;
247
- }
248
- }
249
- if (nextContent === ':::') {
250
- depth--;
251
- if (depth === 0) { found = true; break; }
252
- }
253
- }
254
- }
255
-
256
- if (!found) return false;
257
-
258
- const openToken = state.push('container_steps_open', 'div', 1);
259
- openToken.info = '';
260
-
261
- const oldParentType = state.parentType;
262
- const oldLineMax = state.lineMax;
263
- state.parentType = 'container';
264
- state.lineMax = nextLine;
265
- state.md.block.tokenize(state, startLine + 1, nextLine);
266
- const closeToken = state.push('container_steps_close', 'div', -1);
267
- state.parentType = oldParentType;
268
- state.lineMax = oldLineMax;
269
- state.line = nextLine + 1;
270
- return true;
271
- }
272
-
273
- // --- 4. Enhanced Tabs Rule (Fixed) ---
274
- function enhancedTabsRule(state, startLine, endLine, silent) {
275
- const start = state.bMarks[startLine] + state.tShift[startLine];
276
- const max = state.eMarks[startLine];
277
- const lineContent = state.src.slice(start, max).trim();
278
-
279
- if (lineContent !== '::: tabs') return false;
280
- if (silent) return true;
281
-
282
- let nextLine = startLine;
283
- let found = false;
284
- let depth = 1;
285
- let inFence = false;
286
-
287
- while (nextLine < endLine) {
288
- nextLine++;
289
- const nextStart = state.bMarks[nextLine] + state.tShift[nextLine];
290
- const nextMax = state.eMarks[nextLine];
291
- const nextContent = state.src.slice(nextStart, nextMax).trim();
292
-
293
- if (isFenceLine(nextContent)) inFence = !inFence;
294
-
295
- if (!inFence) {
296
- if (nextContent.startsWith(':::')) {
297
- const containerMatch = nextContent.match(/^:::\s*(\w+)/);
298
- if (containerMatch && containerMatch[1] !== 'tabs') {
299
- if (containerMatch[1] === 'steps') continue;
300
- const innerContainer = containers[containerMatch[1]];
301
- if (innerContainer && !innerContainer.selfClosing) depth++;
302
- continue;
303
- }
304
- }
305
- if (nextContent === ':::') {
306
- depth--;
307
- if (depth === 0) { found = true; break; }
308
- }
309
- }
310
- }
311
- if (!found) return false;
312
-
313
- let content = '';
314
- // Capture content preserving newlines
315
- for (let i = startLine + 1; i < nextLine; i++) {
316
- const lineStart = state.bMarks[i] + state.tShift[i];
317
- const lineEnd = state.eMarks[i];
318
- // .slice() keeps the indentation of the line as it is in source
319
- content += state.src.slice(lineStart, lineEnd) + '\n';
320
- }
321
-
322
- const lines = content.split('\n');
323
- const tabs = [];
324
- let currentTab = null;
325
- let currentContentLines = [];
326
-
327
- for (let i = 0; i < lines.length; i++) {
328
- const rawLine = lines[i];
329
- const trimmedLine = rawLine.trim();
330
-
331
- const tabMatch = trimmedLine.match(/^==\s*tab\s+(?:"([^"]+)"|(\S+))$/);
332
-
333
- if (tabMatch) {
334
- if (currentTab) {
335
- // Apply Smart Dedent before saving
336
- currentTab.content = smartDedent(currentContentLines.join('\n'));
337
- tabs.push(currentTab);
338
- }
339
- const title = tabMatch[1] || tabMatch[2];
340
- currentTab = { title: title, content: '' };
341
- currentContentLines = [];
342
- } else if (currentTab) {
343
- currentContentLines.push(rawLine);
344
- }
345
- }
346
- if (currentTab) {
347
- currentTab.content = smartDedent(currentContentLines.join('\n'));
348
- tabs.push(currentTab);
349
- }
350
-
351
- const openToken = state.push('tabs_open', 'div', 1);
352
- openToken.attrs = [['class', 'docmd-tabs']];
353
-
354
- const navToken = state.push('tabs_nav_open', 'div', 1);
355
- navToken.attrs = [['class', 'docmd-tabs-nav']];
356
- tabs.forEach((tab, index) => {
357
- const navItemToken = state.push('tabs_nav_item', 'div', 0);
358
- navItemToken.attrs = [['class', `docmd-tabs-nav-item ${index === 0 ? 'active' : ''}`]];
359
- navItemToken.content = tab.title;
360
- });
361
- state.push('tabs_nav_close', 'div', -1);
362
-
363
- const contentToken = state.push('tabs_content_open', 'div', 1);
364
- contentToken.attrs = [['class', 'docmd-tabs-content']];
365
- tabs.forEach((tab, index) => {
366
- const paneToken = state.push('tab_pane_open', 'div', 1);
367
- paneToken.attrs = [['class', `docmd-tab-pane ${index === 0 ? 'active' : ''}`]];
368
-
369
- if (tab.content) {
370
- const renderedContent = state.md.render(tab.content, state.env);
371
- const htmlToken = state.push('html_block', '', 0);
372
- htmlToken.content = renderedContent;
373
- }
374
-
375
- state.push('tab_pane_close', 'div', -1);
376
- });
377
- state.push('tabs_content_close', 'div', -1);
378
- state.push('tabs_close', 'div', -1);
379
- state.line = nextLine + 1;
380
- return true;
381
- }
382
-
383
- const standaloneClosingRule = (state, startLine, endLine, silent) => {
384
- const start = state.bMarks[startLine] + state.tShift[startLine];
385
- const max = state.eMarks[startLine];
386
- const lineContent = state.src.slice(start, max).trim();
387
-
388
- if (lineContent === ':::') {
389
- if (silent) return true;
390
- state.line = startLine + 1;
391
- return true;
392
- }
393
- return false;
394
- };
395
-
396
- module.exports = {
397
- advancedContainerRule,
398
- changelogTimelineRule,
399
- stepsContainerRule,
400
- enhancedTabsRule,
401
- standaloneClosingRule
402
- };
@@ -1,113 +0,0 @@
1
- // Source file from the docmd project — https://github.com/docmd-io/docmd
2
-
3
- const MarkdownIt = require('markdown-it');
4
- const hljs = require('highlight.js');
5
- const attrs = require('markdown-it-attrs');
6
- const markdown_it_footnote = require('markdown-it-footnote');
7
- const markdown_it_task_lists = require('markdown-it-task-lists');
8
- const markdown_it_abbr = require('markdown-it-abbr');
9
- const markdown_it_deflist = require('markdown-it-deflist');
10
-
11
- const { containers } = require('./containers');
12
- const rules = require('./rules');
13
- const renderers = require('./renderers');
14
-
15
- // Custom plugin for Heading IDs
16
- const headingIdPlugin = (md) => {
17
- const originalHeadingOpen = md.renderer.rules.heading_open || function(tokens, idx, options, env, self) {
18
- return self.renderToken(tokens, idx, options);
19
- };
20
-
21
- md.renderer.rules.heading_open = function(tokens, idx, options, env, self) {
22
- const token = tokens[idx];
23
- const existingId = token.attrGet('id');
24
-
25
- if (!existingId) {
26
- const contentToken = tokens[idx + 1];
27
- if (contentToken && contentToken.type === 'inline' && contentToken.content) {
28
- const headingText = contentToken.content;
29
- const id = headingText
30
- .toLowerCase()
31
- .replace(/\s+/g, '-')
32
- .replace(/[^\w\u4e00-\u9fa5-]+/g, '')
33
- .replace(/--+/g, '-')
34
- .replace(/^-+/, '')
35
- .replace(/-+$/, '');
36
-
37
- if (id) {
38
- token.attrSet('id', id);
39
- }
40
- }
41
- }
42
- return originalHeadingOpen(tokens, idx, options, env, self);
43
- };
44
- };
45
-
46
- function createMarkdownItInstance(config) {
47
- const mdOptions = {
48
- html: true,
49
- linkify: true,
50
- typographer: true,
51
- breaks: true,
52
- };
53
-
54
- // Removed newlines from template literals to prevent extra padding in <pre> blocks
55
- const highlightFn = (str, lang) => {
56
- if (lang === 'mermaid') {
57
- return `<pre class="mermaid">${new MarkdownIt().utils.escapeHtml(str)}</pre>`;
58
- }
59
- if (lang && hljs.getLanguage(lang)) {
60
- try {
61
- const highlighted = hljs.highlight(str, { language: lang, ignoreIllegals: true }).value;
62
- return `<pre class="hljs"><code>${highlighted}</code></pre>`;
63
- } catch (e) { console.error(e); }
64
- }
65
- return `<pre class="hljs"><code>${new MarkdownIt().utils.escapeHtml(str)}</code></pre>`;
66
- };
67
-
68
- mdOptions.highlight = config.theme?.codeHighlight !== false ? highlightFn : (str, lang) => {
69
- if (lang === 'mermaid') return `<pre class="mermaid">${new MarkdownIt().utils.escapeHtml(str)}</pre>`;
70
- return `<pre><code>${new MarkdownIt().utils.escapeHtml(str)}</code></pre>`;
71
- };
72
-
73
- const md = new MarkdownIt(mdOptions);
74
-
75
- md.use(attrs, { leftDelimiter: '{', rightDelimiter: '}' });
76
- md.use(markdown_it_footnote);
77
- md.use(markdown_it_task_lists);
78
- md.use(markdown_it_abbr);
79
- md.use(markdown_it_deflist);
80
- md.use(headingIdPlugin);
81
-
82
- Object.keys(containers).forEach(containerName => {
83
- const container = containers[containerName];
84
- md.renderer.rules[`container_${containerName}_open`] = container.render;
85
- md.renderer.rules[`container_${containerName}_close`] = container.render;
86
- });
87
-
88
- md.block.ruler.before('fence', 'steps_container', rules.stepsContainerRule, { alt: ['paragraph', 'reference', 'blockquote', 'list'] });
89
- md.block.ruler.before('fence', 'enhanced_tabs', rules.enhancedTabsRule, { alt: ['paragraph', 'reference', 'blockquote', 'list'] });
90
- md.block.ruler.before('fence', 'changelog_timeline', rules.changelogTimelineRule, { alt: ['paragraph', 'reference', 'blockquote', 'list'] });
91
- md.block.ruler.before('paragraph', 'advanced_container', rules.advancedContainerRule, { alt: ['paragraph', 'reference', 'blockquote', 'list'] });
92
- md.block.ruler.before('paragraph', 'standalone_closing', rules.standaloneClosingRule, { alt: ['paragraph', 'reference', 'blockquote', 'list'] });
93
-
94
- md.renderer.rules.ordered_list_open = renderers.customOrderedListOpenRenderer;
95
- md.renderer.rules.list_item_open = renderers.customListItemOpenRenderer;
96
- md.renderer.rules.image = renderers.customImageRenderer;
97
- md.renderer.rules.table_open = renderers.tableOpenRenderer;
98
- md.renderer.rules.table_close = renderers.tableCloseRenderer;
99
-
100
- md.renderer.rules.tabs_open = renderers.tabsOpenRenderer;
101
- md.renderer.rules.tabs_nav_open = renderers.tabsNavOpenRenderer;
102
- md.renderer.rules.tabs_nav_close = renderers.tabsNavCloseRenderer;
103
- md.renderer.rules.tabs_nav_item = renderers.tabsNavItemRenderer;
104
- md.renderer.rules.tabs_content_open = renderers.tabsContentOpenRenderer;
105
- md.renderer.rules.tabs_content_close = renderers.tabsContentCloseRenderer;
106
- md.renderer.rules.tab_pane_open = renderers.tabPaneOpenRenderer;
107
- md.renderer.rules.tab_pane_close = renderers.tabPaneCloseRenderer;
108
- md.renderer.rules.tabs_close = renderers.tabsCloseRenderer;
109
-
110
- return md;
111
- }
112
-
113
- module.exports = { createMarkdownItInstance };
@@ -1,74 +0,0 @@
1
- // Source file from the docmd project — https://github.com/docmd-io/docmd
2
-
3
- /**
4
- * Normalizes paths to a "canonical" form for comparison.
5
- * Handles: "./path", "/path", "path/index.html", "path.md"
6
- */
7
- function getCanonicalPath(p) {
8
- if (!p) return '';
9
- if (p.startsWith('http')) return p; // Don't touch external URLs
10
-
11
- // 1. Remove leading dot-slash or slash
12
- let path = p.replace(/^(\.\/|\/)+/, '');
13
-
14
- // 2. Remove query strings or hashes
15
- path = path.split('?')[0].split('#')[0];
16
-
17
- // 3. Remove file extensions (.html, .md)
18
- path = path.replace(/(\.html|\.md)$/, '');
19
-
20
- // 4. Handle index files (folder/index -> folder)
21
- if (path.endsWith('/index')) {
22
- path = path.slice(0, -6);
23
- } else if (path === 'index') {
24
- path = '';
25
- }
26
-
27
- // 5. Remove trailing slash
28
- if (path.endsWith('/')) {
29
- path = path.slice(0, -1);
30
- }
31
-
32
- return path;
33
- }
34
-
35
- function findPageNeighbors(navItems, currentPagePath) {
36
- const flatNavigation = [];
37
- const currentCanonical = getCanonicalPath(currentPagePath);
38
-
39
- function recurse(items) {
40
- if (!items || !Array.isArray(items)) return;
41
-
42
- for (const item of items) {
43
- // Logic: Only consider items that have a path and are NOT external
44
- // Also ignore items that are just '#' (placeholder parents)
45
- if (item.path && !item.external && !item.path.startsWith('http') && item.path !== '#') {
46
- flatNavigation.push({
47
- title: item.title,
48
- path: item.path, // Keep original path for the HREF
49
- canonical: getCanonicalPath(item.path) // Use canonical for comparison
50
- });
51
- }
52
-
53
- if (item.children) {
54
- recurse(item.children);
55
- }
56
- }
57
- }
58
-
59
- recurse(navItems);
60
-
61
- // Find index using canonical paths
62
- const index = flatNavigation.findIndex(item => item.canonical === currentCanonical);
63
-
64
- if (index === -1) {
65
- return { prevPage: null, nextPage: null };
66
- }
67
-
68
- return {
69
- prevPage: index > 0 ? flatNavigation[index - 1] : null,
70
- nextPage: index < flatNavigation.length - 1 ? flatNavigation[index + 1] : null
71
- };
72
- }
73
-
74
- module.exports = { findPageNeighbors };
package/src/index.js DELETED
@@ -1,12 +0,0 @@
1
- // Source file from the docmd project — https://github.com/docmd-io/docmd
2
-
3
- // Core build function (Node.js environment)
4
- const { buildSite } = require('./commands/build');
5
-
6
- // Live Editor bundler
7
- const { build: buildLive } = require('./commands/live');
8
-
9
- module.exports = {
10
- build: buildSite,
11
- buildLive
12
- };
package/src/live/core.js DELETED
@@ -1,67 +0,0 @@
1
- // Source file from the docmd project — https://github.com/docmd-io/docmd
2
-
3
- const { processMarkdownContent, createMarkdownItInstance } = require('../core/file-processor');
4
- const { renderHtmlPage } = require('../core/html-generator');
5
-
6
- // Virtual import of templates for the live editor bundler
7
- const templates = require('virtual:docmd-templates');
8
-
9
- function compile(markdown, config = {}, options = {}) {
10
- // Default config values for the browser
11
- const defaults = {
12
- siteTitle: 'Live Preview',
13
- theme: { defaultMode: 'light', name: 'default' },
14
- ...config
15
- };
16
-
17
- const md = createMarkdownItInstance(defaults);
18
- const result = processMarkdownContent(markdown, md, defaults, 'memory');
19
-
20
- if (!result) return '<p>Error parsing markdown</p>';
21
-
22
- const { frontmatter, htmlContent, headings } = result;
23
-
24
- const pageData = {
25
- content: htmlContent,
26
- frontmatter,
27
- headings,
28
- siteTitle: defaults.siteTitle,
29
- pageTitle: frontmatter.title || 'Untitled',
30
- description: frontmatter.description || '',
31
- defaultMode: defaults.theme.defaultMode,
32
- editUrl: null,
33
- editLinkText: '',
34
- navigationHtml: '', // Navigation is usually empty in a single-page preview
35
- relativePathToRoot: options.relativePathToRoot || './', // Important for finding CSS in dist/assets
36
- outputPath: 'index.html',
37
- currentPagePath: '/index',
38
- prevPage: null, nextPage: null,
39
- config: defaults,
40
- // Empty hooks
41
- metaTagsHtml: '', faviconLinkHtml: '', themeCssLinkHtml: '',
42
- pluginStylesHtml: '', pluginHeadScriptsHtml: '', pluginBodyScriptsHtml: '',
43
- themeInitScript: '',
44
- logo: defaults.logo, sidebarConfig: { collapsible: false }, theme: defaults.theme,
45
- customCssFiles: [], customJsFiles: [],
46
- sponsor: { enabled: false }, footer: '', footerHtml: '',
47
- renderIcon: () => '', // Icons disabled in live preview to save weight
48
- isActivePage: true
49
- };
50
-
51
- let templateName = frontmatter.noStyle === true ? 'no-style.ejs' : 'layout.ejs';
52
- const templateContent = templates[templateName];
53
-
54
- if (!templateContent) return `Template ${templateName} not found`;
55
-
56
- const ejsOptions = {
57
- includer: (originalPath) => {
58
- let name = originalPath.endsWith('.ejs') ? originalPath : originalPath + '.ejs';
59
- if (templates[name]) return { template: templates[name] };
60
- return null;
61
- }
62
- };
63
-
64
- return renderHtmlPage(templateContent, pageData, templateName, ejsOptions);
65
- }
66
-
67
- module.exports = { compile };