class MarkdownReader { constructor(rootFolder = '../../', backend = 'php') { this.rootFolder = rootFolder; this.backend = backend; // 'php', 'node', or 'manual' this.files = []; this.currentFile = null; } // Initialize the reader async init() { await this.scanFiles(); this.renderFileList(); } // Scan files based on backend type async scanFiles() { switch(this.backend) { case 'php': await this.scanFilesWithPHP(); break; case 'node': await this.scanFilesWithNode(); break; case 'manual': default: await this.scanFilesManually(); break; } } // Scan files using PHP backend async scanFilesWithPHP() { try { const response = await fetch('Core/php/list-files.php'); const data = await response.json(); if (data.success) { this.files = data.files; console.log(`Loaded ${data.count} markdown files using PHP`); } else { console.error('PHP Error:', data.error); this.files = []; } } catch (error) { console.error('Error fetching file list from PHP:', error); this.files = []; } } // Scan files using Node.js backend async scanFilesWithNode() { try { const response = await fetch('/api/list-markdown-files'); const data = await response.json(); this.files = data.files || []; console.log(`Loaded ${this.files.length} markdown files using Node.js`); } catch (error) { console.error('Error fetching file list from Node.js:', error); this.files = []; } } // Manual file list (fallback method) async scanFilesManually() { // Manually define your markdown files here this.files = [ 'files/document1.md', 'files/document2.md', 'files/subfolder/document3.md', // Add more files here... ]; console.log(`Loaded ${this.files.length} markdown files manually`); } // Render the list of files renderFileList() { const fileList = document.getElementById('fileList'); fileList.innerHTML = ''; if (this.files.length === 0) { fileList.innerHTML = '
Error loading file: ${error.message}
`; } } parseMarkdown(markdown) { // STEP 1: Extract and protect code blocks FIRST const codeBlocks = []; let codeBlockIndex = 0; let html = markdown.replace(/```(\w+)?\n?([\s\S]*?)```/g, (match, lang, code) => { const language = lang || 'text'; const placeholder = `___CODE_BLOCK_${codeBlockIndex}___`; const escapedCode = this.escapeHtml(code); const codeId = 'code-' + Math.random().toString(36).substr(2, 9); codeBlocks.push(`${escapedCode}$1');
// STEP 3: Process everything else
html = html.replace(/^###### (.*$)/gim, (match, content) => {
const id = this.generateId(content);
return `$1'); // Horizontal rules html = html.replace(/^---$/gim, '
');
html = html.replace(/\n/g, '
');
html = `
${html}
`; // Clean up: Remove<\/p>/g, ''); html = html.replace(/
( ( ( ( ( ( ${paragraph} ${paragraph})/g, '$1');
html = html.replace(/(<\/table>)<\/p>/g, '$1');
html = html.replace(/
';
result.push(tableHtml);
}
return result.join('\n');
}
// Wrap text in paragraph tags
wrapParagraphs(html) {
const lines = html.split('\n');
const result = [];
let paragraph = '';
for (let line of lines) {
line = line.trim();
// Skip if line is already wrapped in a tag
if (line.match(/^<(h[1-6]|ul|ol|blockquote|pre|table|hr|div)/i) ||
line.match(/<\/(h[1-6]|ul|ol|blockquote|pre|table|hr|div)>$/i) ||
line === '') {
if (paragraph) {
result.push(`)/g, '$1');
html = html.replace(/(<\/ul>)<\/p>/g, '$1');
html = html.replace(/
)/g, '$1');
html = html.replace(/(<\/ol>)<\/p>/g, '$1');
html = html.replace(/
)/g, '$1');
html = html.replace(/(
)<\/p>/g, '$1');
html = html.replace(/)/g, '$1');
html = html.replace(/(<\/blockquote>)<\/p>/g, '$1');
// STEP 4: Restore tables FIRST
tables.forEach((table, index) => {
html = html.replace(`___TABLE_${index}___`, table);
});
// STEP 5: Restore code blocks LAST
codeBlocks.forEach((block, index) => {
html = html.replace(`___CODE_BLOCK_${index}___`, block);
});
return html;
}
// Helper method to process tables
processTable(tableLines) {
const rows = tableLines.map(line => {
const cells = line.slice(1, -1).split('|').map(cell => cell.trim());
return cells;
});
// Find separator row (like |---|---|)
const sepIndex = rows.findIndex(row =>
row.every(cell => /^:?-+:?$/.test(cell))
);
if (sepIndex === -1) {
// No header separator - treat all as body
const bodyRows = rows.map(cells =>
'
' + cells.map(cell => ` '
).join('\n');
return `${cell} `).join('') + '${bodyRows}
`;
}
// Has header
const headerCells = rows[sepIndex - 1];
const header = '' + headerCells.map(cell => ` ';
const bodyRows = rows.slice(sepIndex + 1).map(cells =>
'${cell} `).join('') + '' + cells.map(cell => ` '
).join('\n');
return `${cell} `).join('') + '\n${header}\n${bodyRows}\n
`;
}
escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, m => map[m]);
}
// Generate ID from text for anchor links
generateId(text) {
return text.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/--+/g, '-')
.trim();
}
// Escape HTML special characters
escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, m => map[m]);
}
// Merge adjacent tags
mergeAdjacentTags(html, tag) {
const regex = new RegExp(`${tag}>\\s*<${tag}>`, 'g');
return html.replace(regex, '\n');
}
// Wrap list items in ul/ol tags
wrapLists(html) {
// Wrap task lists
html = html.replace(/($1
');
// Wrap regular lists
html = html.replace(/(${match}
`;
});
// Clean up nested ul tags
html = html.replace(/<\/ul>\s*/g, '');
html = html.replace(/<\/ul>\s*
/g, '
');
return html;
}
// Parse markdown tables
parseTables(html) {
const lines = html.split('\n');
let inTable = false;
let tableHtml = '';
let result = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
// Check if line is a table row
if (line.startsWith('|') && line.endsWith('|')) {
const cells = line.split('|').slice(1, -1).map(cell => cell.trim());
// Check if next line is separator
if (!inTable && i + 1 < lines.length) {
const nextLine = lines[i + 1].trim();
if (nextLine.match(/^\|[\s:-]+\|/)) {
// Start table
inTable = true;
tableHtml = '
';
result.push(tableHtml);
tableHtml = '';
inTable = false;
}
result.push(line);
}
}
if (inTable) {
tableHtml += '';
cells.forEach(cell => {
tableHtml += ` ';
i++; // Skip separator line
continue;
}
}
if (inTable) {
tableHtml += '${cell} `;
});
tableHtml += '';
cells.forEach(cell => {
tableHtml += ` ';
}
} else {
if (inTable) {
tableHtml += '${cell} `;
});
tableHtml += '