CI Pipeline with Test Management / 🧪 Run Tests & Generate Reports (push) Waiting to run
CI Pipeline with Test Management / 🐳 Docker Integration Tests (push) Blocked by required conditions
CI Pipeline with Test Management / 🏗️ Build Docker Image (push) Blocked by required conditions
CI Pipeline with Test Management / 📊 Generate Test Summary (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🧪 Run Tests & Generate Reports (push) Waiting to run
Test Reporting & Gherkin Analysis / 📊 Analyze Test Coverage (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🔄 Sync with Linear (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / ⚡ Performance Monitoring (push) Blocked by required conditions
- docs/felhasznaloi-utmutato.md: user guide for the website and the CMS
(login, editing, arrays, save/validation, publish, security)
- /guide endpoint renders the markdown auth-protected via a dependency-free
renderer (scripts/markdown-render.js) in the CMS dark theme
- new ❓ Súgó entry in the CMS bottom bar
- steering rule: the guide must be updated in the same commit as any CMS or
website feature change
Closes MITHOME-57
96 lines
2.5 KiB
JavaScript
96 lines
2.5 KiB
JavaScript
// WHY: the Content Editor runs on system Node without node_modules, so the user
|
|
// guide (docs/felhasznaloi-utmutato.md) is rendered by this small dependency-free
|
|
// markdown renderer instead of an external library.
|
|
// Supported subset: headings (#..####), bold, inline code, links, ul/ol lists,
|
|
// fenced code blocks, horizontal rules, paragraphs. HTML is escaped first.
|
|
|
|
function escapeHtml(value) {
|
|
return String(value)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"');
|
|
}
|
|
|
|
function renderInline(text) {
|
|
return escapeHtml(text)
|
|
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
|
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
|
.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
}
|
|
|
|
function renderMarkdown(markdown) {
|
|
const lines = String(markdown).split('\n');
|
|
const out = [];
|
|
let listTag = null; // 'ul' | 'ol'
|
|
let inCode = false;
|
|
|
|
const closeList = () => {
|
|
if (listTag) {
|
|
out.push(`</${listTag}>`);
|
|
listTag = null;
|
|
}
|
|
};
|
|
|
|
for (const raw of lines) {
|
|
const line = raw.trimEnd();
|
|
|
|
if (line.trim().startsWith('```')) {
|
|
closeList();
|
|
out.push(inCode ? '</code></pre>' : '<pre><code>');
|
|
inCode = !inCode;
|
|
continue;
|
|
}
|
|
if (inCode) {
|
|
out.push(escapeHtml(raw));
|
|
continue;
|
|
}
|
|
if (!line.trim()) {
|
|
closeList();
|
|
continue;
|
|
}
|
|
|
|
const heading = line.match(/^(#{1,4})\s+(.*)$/);
|
|
if (heading) {
|
|
closeList();
|
|
const level = heading[1].length;
|
|
out.push(`<h${level}>${renderInline(heading[2])}</h${level}>`);
|
|
continue;
|
|
}
|
|
if (/^(-{3,}|\*{3,})$/.test(line.trim())) {
|
|
closeList();
|
|
out.push('<hr>');
|
|
continue;
|
|
}
|
|
const unordered = line.match(/^\s*[-*]\s+(.*)$/);
|
|
if (unordered) {
|
|
if (listTag !== 'ul') {
|
|
closeList();
|
|
out.push('<ul>');
|
|
listTag = 'ul';
|
|
}
|
|
out.push(`<li>${renderInline(unordered[1])}</li>`);
|
|
continue;
|
|
}
|
|
const ordered = line.match(/^\s*\d+\.\s+(.*)$/);
|
|
if (ordered) {
|
|
if (listTag !== 'ol') {
|
|
closeList();
|
|
out.push('<ol>');
|
|
listTag = 'ol';
|
|
}
|
|
out.push(`<li>${renderInline(ordered[1])}</li>`);
|
|
continue;
|
|
}
|
|
|
|
closeList();
|
|
out.push(`<p>${renderInline(line)}</p>`);
|
|
}
|
|
|
|
closeList();
|
|
if (inCode) out.push('</code></pre>');
|
|
return out.join('\n');
|
|
}
|
|
|
|
module.exports = { renderMarkdown, renderInline, escapeHtml };
|