@mgks/docmd 0.3.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +15 -160
  2. package/bin/docmd.js +6 -69
  3. package/package.json +6 -79
  4. package/bin/postinstall.js +0 -14
  5. package/src/assets/css/docmd-highlight-dark.css +0 -86
  6. package/src/assets/css/docmd-highlight-light.css +0 -86
  7. package/src/assets/css/docmd-main.css +0 -1736
  8. package/src/assets/css/docmd-theme-retro.css +0 -867
  9. package/src/assets/css/docmd-theme-ruby.css +0 -629
  10. package/src/assets/css/docmd-theme-sky.css +0 -617
  11. package/src/assets/favicon.ico +0 -0
  12. package/src/assets/images/docmd-logo-dark.png +0 -0
  13. package/src/assets/images/docmd-logo-light.png +0 -0
  14. package/src/assets/js/docmd-image-lightbox.js +0 -74
  15. package/src/assets/js/docmd-main.js +0 -260
  16. package/src/assets/js/docmd-mermaid.js +0 -205
  17. package/src/assets/js/docmd-search.js +0 -218
  18. package/src/commands/build.js +0 -237
  19. package/src/commands/dev.js +0 -352
  20. package/src/commands/init.js +0 -277
  21. package/src/commands/live.js +0 -145
  22. package/src/core/asset-manager.js +0 -72
  23. package/src/core/config-loader.js +0 -58
  24. package/src/core/config-validator.js +0 -80
  25. package/src/core/file-processor.js +0 -103
  26. package/src/core/fs-utils.js +0 -40
  27. package/src/core/html-generator.js +0 -184
  28. package/src/core/icon-renderer.js +0 -106
  29. package/src/core/logger.js +0 -21
  30. package/src/core/markdown/containers.js +0 -94
  31. package/src/core/markdown/renderers.js +0 -90
  32. package/src/core/markdown/rules.js +0 -402
  33. package/src/core/markdown/setup.js +0 -113
  34. package/src/core/navigation-helper.js +0 -74
  35. package/src/index.js +0 -12
  36. package/src/live/core.js +0 -67
  37. package/src/live/index.html +0 -216
  38. package/src/live/live.css +0 -256
  39. package/src/live/shims.js +0 -1
  40. package/src/plugins/analytics.js +0 -48
  41. package/src/plugins/seo.js +0 -107
  42. package/src/plugins/sitemap.js +0 -127
  43. package/src/templates/layout.ejs +0 -187
  44. package/src/templates/navigation.ejs +0 -87
  45. package/src/templates/no-style.ejs +0 -166
  46. package/src/templates/partials/theme-init.js +0 -30
  47. package/src/templates/toc.ejs +0 -38
@@ -1,103 +0,0 @@
1
- // Source file from the docmd project — https://github.com/docmd-io/docmd
2
-
3
- const fs = require('./fs-utils');
4
- const path = require('path');
5
- const matter = require('gray-matter');
6
- const { createMarkdownItInstance } = require('./markdown/setup');
7
-
8
- function decodeHtmlEntities(html) {
9
- return html.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, ' ');
10
- }
11
-
12
- function stripHtmlTags(str) {
13
- if (!str) return '';
14
- return str.replace(/<[^>]*>?/gm, '');
15
- }
16
-
17
- function extractHeadingsFromHtml(htmlContent) {
18
- const headings = [];
19
- const headingRegex = /<h([1-6])[^>]*?id="([^"]*)"[^>]*?>([\s\S]*?)<\/h\1>/g;
20
- let match;
21
- while ((match = headingRegex.exec(htmlContent)) !== null) {
22
- const level = parseInt(match[1], 10);
23
- const id = match[2];
24
- const text = decodeHtmlEntities(match[3].replace(/<\/?[^>]+(>|$)/g, ''));
25
- headings.push({ id, level, text });
26
- }
27
- return headings;
28
- }
29
-
30
- function formatPathForDisplay(absolutePath) {
31
- const CWD = process.cwd();
32
- const relativePath = path.relative(CWD, absolutePath);
33
- if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
34
- return `./${relativePath}`;
35
- }
36
- return relativePath;
37
- }
38
-
39
- async function processMarkdownFile(filePath, md, config) {
40
- const rawContent = await fs.readFile(filePath, 'utf8');
41
- return processMarkdownContent(rawContent, md, config, filePath);
42
- }
43
-
44
- // Pure logic, no file reading (Used by Live Editor)
45
- function processMarkdownContent(rawContent, md, config, filePath = 'memory') {
46
- let frontmatter, markdownContent;
47
-
48
- try {
49
- ({ data: frontmatter, content: markdownContent } = matter(rawContent));
50
- } catch (e) {
51
- console.error(`❌ Error parsing frontmatter in ${filePath === 'memory' ? 'content' : formatPathForDisplay(filePath)}:`);
52
- console.error(` ${e.message}`);
53
- return null;
54
- }
55
-
56
- if (!frontmatter.title && config.autoTitleFromH1 !== false) {
57
- const h1Match = markdownContent.match(/^#\s+(.*)/m);
58
- if (h1Match) frontmatter.title = h1Match[1].trim();
59
- }
60
-
61
- let htmlContent, headings;
62
- if (frontmatter.noStyle === true) {
63
- htmlContent = markdownContent;
64
- headings = [];
65
- } else {
66
- htmlContent = md.render(markdownContent);
67
- headings = extractHeadingsFromHtml(htmlContent);
68
- }
69
-
70
- let searchData = null;
71
- if (!frontmatter.noindex) {
72
- const rawText = decodeHtmlEntities(stripHtmlTags(htmlContent));
73
- searchData = {
74
- title: frontmatter.title || 'Untitled',
75
- content: rawText.slice(0, 5000), // Safety cap to prevent massive JSON
76
- headings: headings.map(h => h.text)
77
- };
78
- }
79
-
80
- return { frontmatter, htmlContent, headings, searchData };
81
- }
82
-
83
- async function findMarkdownFiles(dir) {
84
- let files = [];
85
- const items = await fs.readdir(dir, { withFileTypes: true });
86
- for (const item of items) {
87
- const fullPath = path.join(dir, item.name);
88
- if (item.isDirectory()) {
89
- files = files.concat(await findMarkdownFiles(fullPath));
90
- } else if (item.isFile() && (item.name.endsWith('.md') || item.name.endsWith('.markdown'))) {
91
- files.push(fullPath);
92
- }
93
- }
94
- return files;
95
- }
96
-
97
- module.exports = {
98
- processMarkdownFile,
99
- processMarkdownContent,
100
- createMarkdownItInstance,
101
- extractHeadingsFromHtml,
102
- findMarkdownFiles
103
- };
@@ -1,40 +0,0 @@
1
- // Source file from the docmd project — https://github.com/docmd-io/docmd
2
-
3
- const fs = require('node:fs/promises');
4
- const path = require('node:path');
5
-
6
- async function ensureDir(dirPath) {
7
- await fs.mkdir(dirPath, { recursive: true });
8
- }
9
-
10
- async function remove(dirPath) {
11
- await fs.rm(dirPath, { recursive: true, force: true });
12
- }
13
-
14
- async function copy(src, dest) {
15
- await fs.cp(src, dest, { recursive: true });
16
- }
17
-
18
- async function exists(filePath) {
19
- try {
20
- await fs.access(filePath);
21
- return true;
22
- } catch {
23
- return false;
24
- }
25
- }
26
-
27
- async function writeJson(file, object, options = {}) {
28
- const content = JSON.stringify(object, null, options.spaces || 2);
29
- await fs.writeFile(file, content, 'utf8');
30
- }
31
-
32
- module.exports = {
33
- ...fs,
34
- ensureDir,
35
- remove,
36
- copy,
37
- pathExists: exists,
38
- exists,
39
- writeJson
40
- };
@@ -1,184 +0,0 @@
1
- // Source file from the docmd project — https://github.com/docmd-io/docmd
2
-
3
- const ejs = require('ejs');
4
- const path = require('path');
5
- const fs = require('../core/fs-utils');
6
- const { createMarkdownItInstance } = require('./file-processor');
7
- const { generateSeoMetaTags } = require('../plugins/seo');
8
- const { generateAnalyticsScripts } = require('../plugins/analytics');
9
- const { renderIcon } = require('./icon-renderer');
10
-
11
- let mdInstance = null;
12
- let themeInitScript = '';
13
-
14
- (async () => {
15
- try {
16
- const themeInitPath = path.join(__dirname, '..', 'templates', 'partials', 'theme-init.js');
17
- if (await fs.exists(themeInitPath)) {
18
- const scriptContent = await fs.readFile(themeInitPath, 'utf8');
19
- themeInitScript = `<script>\n${scriptContent}\n</script>`;
20
- }
21
- } catch (e) { /* ignore */ }
22
- })();
23
-
24
- // Basic whitespace cleanup (keep this simple version)
25
- function cleanupHtml(html) {
26
- if (!html) return '';
27
- return html.replace(/^\s*[\r\n]/gm, '').trim();
28
- }
29
-
30
- function fixHtmlLinks(htmlContent, relativePathToRoot, isOfflineMode, configBase = '/') {
31
- if (!htmlContent) return '';
32
- const root = relativePathToRoot || './';
33
- const baseUrl = configBase.endsWith('/') ? configBase : configBase + '/';
34
-
35
- return htmlContent.replace(/(href|src)=["']([^"']+)["']/g, (match, attr, url) => {
36
- if (url.startsWith('#') || url.startsWith('http') || url.startsWith('mailto:') || url === '') {
37
- return match;
38
- }
39
-
40
- let finalPath = url;
41
-
42
- // 1. Handle Base URL removal
43
- if (baseUrl !== '/' && url.startsWith(baseUrl)) {
44
- finalPath = '/' + url.substring(baseUrl.length);
45
- }
46
-
47
- // 2. Handle Absolute Paths
48
- if (finalPath.startsWith('/')) {
49
- // Simple logic: if root relative, prepend relative path
50
- finalPath = root + finalPath.substring(1);
51
- }
52
-
53
- // 3. Offline Mode Logic
54
- if (isOfflineMode) {
55
- const [pathOnly] = finalPath.split(/[?#]/);
56
- const ext = path.extname(pathOnly);
57
- const isAsset = ['.css', '.js', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico'].includes(ext.toLowerCase());
58
-
59
- if (!isAsset && !ext) {
60
- if (finalPath.endsWith('/')) {
61
- finalPath += 'index.html';
62
- } else if (!finalPath.includes('#')) {
63
- finalPath += '/index.html';
64
- }
65
- }
66
- } else {
67
- if (finalPath.endsWith('/index.html')) {
68
- finalPath = finalPath.substring(0, finalPath.length - 10);
69
- }
70
- }
71
-
72
- return `${attr}="${finalPath}"`;
73
- });
74
- }
75
-
76
- async function processPluginHooks(config, pageData, relativePathToRoot) {
77
- let metaTagsHtml = '';
78
- let faviconLinkHtml = '';
79
- let themeCssLinkHtml = '';
80
- let pluginStylesHtml = '';
81
- let pluginHeadScriptsHtml = '';
82
- let pluginBodyScriptsHtml = '';
83
-
84
- const safeRoot = relativePathToRoot || './';
85
-
86
- if (config.favicon) {
87
- const cleanFaviconPath = config.favicon.startsWith('/') ? config.favicon.substring(1) : config.favicon;
88
- const finalFaviconHref = `${safeRoot}${cleanFaviconPath}`;
89
- faviconLinkHtml = `<link rel="icon" href="${finalFaviconHref}" type="image/x-icon" sizes="any">\n<link rel="shortcut icon" href="${finalFaviconHref}" type="image/x-icon">`;
90
- }
91
-
92
- if (config.theme && config.theme.name && config.theme.name !== 'default') {
93
- const themeCssPath = `assets/css/docmd-theme-${config.theme.name}.css`;
94
- themeCssLinkHtml = `<link rel="stylesheet" href="${safeRoot}${themeCssPath}">`;
95
- }
96
-
97
- if (config.plugins?.seo) {
98
- metaTagsHtml += generateSeoMetaTags(config, pageData, safeRoot);
99
- }
100
-
101
- if (config.plugins?.analytics) {
102
- const analyticsScripts = generateAnalyticsScripts(config, pageData);
103
- pluginHeadScriptsHtml += analyticsScripts.headScriptsHtml;
104
- pluginBodyScriptsHtml += analyticsScripts.bodyScriptsHtml;
105
- }
106
-
107
- return { metaTagsHtml, faviconLinkHtml, themeCssLinkHtml, pluginStylesHtml, pluginHeadScriptsHtml, pluginBodyScriptsHtml };
108
- }
109
-
110
- async function generateHtmlPage(templateData, isOfflineMode = false) {
111
- let { content, frontmatter, outputPath, headings, config } = templateData;
112
- const { currentPagePath, prevPage, nextPage, relativePathToRoot, navigationHtml, siteTitle } = templateData;
113
- const pageTitle = frontmatter.title;
114
-
115
- if (!relativePathToRoot) templateData.relativePathToRoot = './';
116
-
117
- content = fixHtmlLinks(content, templateData.relativePathToRoot, isOfflineMode, config.base);
118
- const pluginOutputs = await processPluginHooks(config, { frontmatter, outputPath }, templateData.relativePathToRoot);
119
-
120
- let footerHtml = '';
121
- if (config.footer) {
122
- if (!mdInstance) mdInstance = createMarkdownItInstance(config);
123
- footerHtml = mdInstance.renderInline(config.footer);
124
- footerHtml = fixHtmlLinks(footerHtml, templateData.relativePathToRoot, isOfflineMode, config.base);
125
- }
126
-
127
- let templateName = frontmatter.noStyle === true ? 'no-style.ejs' : 'layout.ejs';
128
- const layoutTemplatePath = path.join(__dirname, '..', 'templates', templateName);
129
- if (!await fs.exists(layoutTemplatePath)) throw new Error(`Template not found: ${layoutTemplatePath}`);
130
- const layoutTemplate = await fs.readFile(layoutTemplatePath, 'utf8');
131
-
132
- const isActivePage = currentPagePath && content && content.trim().length > 0;
133
-
134
- let editUrl = null;
135
- let editLinkText = 'Edit this page';
136
- if (config.editLink && config.editLink.enabled && config.editLink.baseUrl) {
137
- editUrl = `${config.editLink.baseUrl.replace(/\/$/, '')}/${outputPath.replace(/\/index\.html$/, '.md')}`;
138
- if (outputPath.endsWith('index.html') && outputPath !== 'index.html') editUrl = editUrl.replace('.md', '/index.md');
139
- if (outputPath === 'index.html') editUrl = `${config.editLink.baseUrl.replace(/\/$/, '')}/index.md`;
140
- editLinkText = config.editLink.text || editLinkText;
141
- }
142
-
143
- const ejsData = {
144
- ...templateData,
145
- description: frontmatter.description || '',
146
- footerHtml, editUrl, editLinkText, isActivePage,
147
- defaultMode: config.theme?.defaultMode || 'light',
148
- logo: config.logo, sidebarConfig: config.sidebar || {}, theme: config.theme,
149
- customCssFiles: config.theme?.customCss || [], customJsFiles: config.customJs || [],
150
- sponsor: config.sponsor, footer: config.footer, renderIcon, themeInitScript,
151
- headings: frontmatter.toc !== false ? (headings || []) : [],
152
- ...pluginOutputs,
153
- isOfflineMode
154
- };
155
-
156
- const rawHtml = renderHtmlPage(layoutTemplate, ejsData, layoutTemplatePath);
157
- const pkgVersion = require('../../package.json').version;
158
- const brandingComment = `<!-- Generated by docmd (v${pkgVersion}) - https://docmd.io -->\n`;
159
-
160
- // REMOVED: formatHtml(rawHtml)
161
- return brandingComment + cleanupHtml(rawHtml);
162
- }
163
-
164
- function renderHtmlPage(templateContent, ejsData, filename = 'template.ejs', options = {}) {
165
- try {
166
- return ejs.render(templateContent, ejsData, { filename: filename, ...options });
167
- } catch (e) {
168
- console.error(`❌ Error rendering EJS template: ${e.message}`);
169
- throw e;
170
- }
171
- }
172
-
173
- async function generateNavigationHtml(navItems, currentPagePath, relativePathToRoot, config, isOfflineMode = false) {
174
- const navTemplatePath = path.join(__dirname, '..', 'templates', 'navigation.ejs');
175
- if (!await fs.exists(navTemplatePath)) throw new Error(`Navigation template not found: ${navTemplatePath}`);
176
- const navTemplate = await fs.readFile(navTemplatePath, 'utf8');
177
- const safeRoot = relativePathToRoot || './';
178
-
179
- return ejs.render(navTemplate, {
180
- navItems, currentPagePath, relativePathToRoot: safeRoot, config, isOfflineMode, renderIcon
181
- }, { filename: navTemplatePath });
182
- }
183
-
184
- module.exports = { generateHtmlPage, generateNavigationHtml, renderHtmlPage };
@@ -1,106 +0,0 @@
1
- // Source file from the docmd project — https://github.com/docmd-io/docmd
2
-
3
- const lucideStatic = require('lucide-static');
4
-
5
- // On first load, log debug information about a specific icon to understand its structure
6
- let debugRun = false;
7
- if (debugRun) {
8
- console.log(`[docmd] Lucide static icons loaded - type: ${typeof lucideStatic}`);
9
- if (typeof lucideStatic === 'object') {
10
- console.log(`[docmd] Available icon keys (first 10): ${Object.keys(lucideStatic).slice(0, 10).join(', ')}...`);
11
- console.log(`[docmd] Total icons available: ${Object.keys(lucideStatic).length}`);
12
-
13
- // Inspect a sample icon to understand its structure
14
- const sampleIcon = lucideStatic['Home'];
15
- if (sampleIcon) {
16
- console.log(`[docmd] Sample icon (Home) structure:`,
17
- JSON.stringify(sampleIcon).substring(0, 150) + '...');
18
- }
19
- }
20
- debugRun = false;
21
- }
22
-
23
- // Convert kebab-case to PascalCase for icon names (lucide-static uses PascalCase)
24
- function kebabToPascal(str) {
25
- return str
26
- .split('-')
27
- .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
28
- .join('');
29
- }
30
-
31
- // Map of special case icon mappings that can't be handled by the kebabToPascal conversion
32
- // Only keep truly necessary mappings that can't be derived from kebab-case
33
- const iconSpecialCases = {
34
- 'arrow-up-right-square': 'ExternalLink', // Different name entirely
35
- 'cloud-upload': 'UploadCloud', // Different word order
36
- 'file-cog': 'Settings', // Completely different icon
37
- };
38
-
39
- const warnedMissingIcons = new Set();
40
-
41
- function renderIcon(iconName, options = {}) {
42
- // Try different ways to get the icon data
43
- let iconData;
44
-
45
- // 1. Check special cases mapping for exceptions
46
- if (iconSpecialCases[iconName]) {
47
- iconData = lucideStatic[iconSpecialCases[iconName]];
48
- }
49
-
50
- // 2. If not found, try standard PascalCase conversion
51
- if (!iconData) {
52
- const pascalCaseName = kebabToPascal(iconName);
53
- iconData = lucideStatic[pascalCaseName];
54
- }
55
-
56
- if (!iconData) {
57
- if (!warnedMissingIcons.has(iconName)) { // Check if already warned
58
- console.warn(`[docmd] Lucide icon not found: ${iconName}. Falling back to empty string.`);
59
- warnedMissingIcons.add(iconName); // Add to set so it doesn't warn again
60
- }
61
- return '';
62
- }
63
-
64
- try {
65
- // The iconData is a string containing a complete SVG
66
- // We need to extract the contents and apply our own attributes
67
- const svgString = iconData.trim();
68
-
69
- // Extract the SVG content between the opening and closing tags
70
- const contentMatch = svgString.match(/<svg[^>]*>([\s\S]*)<\/svg>/);
71
- if (!contentMatch) {
72
- return ''; // Not a valid SVG
73
- }
74
-
75
- const svgContent = contentMatch[1];
76
-
77
- // Create our custom attributes for the SVG
78
- const attributes = {
79
- class: `lucide-icon icon-${iconName} ${options.class || ''}`.trim(),
80
- width: options.width || '1em',
81
- height: options.height || '1em',
82
- viewBox: '0 0 24 24',
83
- fill: 'none',
84
- stroke: options.stroke || 'currentColor',
85
- 'stroke-width': options.strokeWidth || '2',
86
- 'stroke-linecap': 'round',
87
- 'stroke-linejoin': 'round',
88
- };
89
-
90
- const attributesString = Object.entries(attributes)
91
- .map(([key, value]) => `${key}="${value}"`)
92
- .join(' ');
93
-
94
- // Return the new SVG with our attributes and the original content
95
- return `<svg ${attributesString}>${svgContent}</svg>`;
96
- } catch (err) {
97
- console.error(`[docmd] Error rendering icon ${iconName}:`, err);
98
- return '';
99
- }
100
- }
101
-
102
- function clearWarnedIcons() {
103
- warnedMissingIcons.clear();
104
- }
105
-
106
- module.exports = { renderIcon, clearWarnedIcons };
@@ -1,21 +0,0 @@
1
- // Source file from the docmd project — https://github.com/docmd-io/docmd
2
-
3
- const chalk = require('chalk');
4
-
5
- const { version } = require('../../package.json');
6
-
7
- const printBanner = () => {
8
- const logo = `
9
-
10
- ${chalk.blue(' _ _ ')}
11
- ${chalk.blue(' _| |___ ___ _____ _| |')}
12
- ${chalk.blue(' | . | . | _| | . |')}
13
- ${chalk.blue(' |___|___|___|_|_|_|___|')}
14
- `;
15
-
16
- console.log(logo);
17
- console.log(` ${chalk.dim(`v${version}`)}`);
18
- console.log(`\n`);
19
- };
20
-
21
- module.exports = { printBanner };
@@ -1,94 +0,0 @@
1
- // Source file from the docmd project — https://github.com/docmd-io/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 };
@@ -1,90 +0,0 @@
1
- // Source file from the docmd project — https://github.com/docmd-io/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
- };