@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.
- package/README.md +15 -160
- package/bin/docmd.js +6 -69
- package/package.json +6 -79
- package/bin/postinstall.js +0 -14
- package/src/assets/css/docmd-highlight-dark.css +0 -86
- package/src/assets/css/docmd-highlight-light.css +0 -86
- package/src/assets/css/docmd-main.css +0 -1757
- package/src/assets/css/docmd-theme-retro.css +0 -867
- package/src/assets/css/docmd-theme-ruby.css +0 -629
- package/src/assets/css/docmd-theme-sky.css +0 -617
- package/src/assets/favicon.ico +0 -0
- package/src/assets/images/docmd-logo-dark.png +0 -0
- package/src/assets/images/docmd-logo-light.png +0 -0
- package/src/assets/js/docmd-image-lightbox.js +0 -74
- package/src/assets/js/docmd-main.js +0 -260
- package/src/assets/js/docmd-mermaid.js +0 -205
- package/src/assets/js/docmd-search.js +0 -218
- package/src/commands/build.js +0 -237
- package/src/commands/dev.js +0 -352
- package/src/commands/init.js +0 -277
- package/src/commands/live.js +0 -145
- package/src/core/asset-manager.js +0 -72
- package/src/core/config-loader.js +0 -58
- package/src/core/config-validator.js +0 -80
- package/src/core/file-processor.js +0 -103
- package/src/core/fs-utils.js +0 -40
- package/src/core/html-generator.js +0 -185
- package/src/core/icon-renderer.js +0 -106
- package/src/core/logger.js +0 -21
- package/src/core/markdown/containers.js +0 -94
- package/src/core/markdown/renderers.js +0 -90
- package/src/core/markdown/rules.js +0 -402
- package/src/core/markdown/setup.js +0 -113
- package/src/core/navigation-helper.js +0 -74
- package/src/index.js +0 -12
- package/src/live/core.js +0 -67
- package/src/live/index.html +0 -216
- package/src/live/live.css +0 -256
- package/src/live/shims.js +0 -1
- package/src/plugins/analytics.js +0 -48
- package/src/plugins/seo.js +0 -107
- package/src/plugins/sitemap.js +0 -127
- package/src/templates/layout.ejs +0 -187
- package/src/templates/navigation.ejs +0 -97
- package/src/templates/no-style.ejs +0 -166
- package/src/templates/partials/theme-init.js +0 -36
- 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 };
|