Files
websitedev/scripts/markdown-render.js
T
Do Siki c5d5198fbf
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
fix(cms): implement v2 security and stability review findings
Resolves:
- CSRF false positive checked (global POST protection)
- Publish mutex to prevent git lock / double deploy
- Basic Auth rate limit checked before credential evaluation
- Memory leak in rate limiter (added GC interval)
- XSS in Toast messages
- XSS in data-path attribute
- CI healthcheck port mismatch (3000 -> 8080)
- Added security headers (X-Frame-Options, X-Content-Type-Options)
2026-08-20 11:35:01 +02:00

99 lines
2.6 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, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
function renderInline(text) {
return escapeHtml(text)
.replace(/`([^`]+)`/g, '<code>$1</code>')
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (match, linkText, url) => {
const safeUrl = /^(https?:\/\/|mailto:|#|\/)/i.test(url) ? url : '#';
return `<a href="${safeUrl}" target="_blank" rel="noopener noreferrer">${linkText}</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 };