Fix SDLC delivery rendering and regenerate package

This commit is contained in:
Thanakorn
2026-08-19 10:18:32 +07:00
parent 849158059b
commit bbc3654180
48 changed files with 99 additions and 22 deletions
+51 -6
View File
@@ -14,7 +14,9 @@ const escapeHtml = (value) => value
const renderInline = (value) => escapeHtml(value)
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/`([^`]+)`/g, '<code>$1</code>');
.replace(/`([^`]+)`/g, '<code>$1</code>')
.replace(/_{3,}/g, '<span class="approval-block__line"></span>')
.replace(/\\([\\*_`~])/g, '$1');
const applyTemplate = (template, values) => template.replace(/{{(\w+)}}/g, (_, key) => values[key] ?? '');
@@ -30,11 +32,41 @@ function isSeparator(cells) {
return cells.every((cell) => /^:?-{3,}:?$/.test(cell));
}
const TIGHT_COLUMN_MAX = 12;
function columnAlignment(separatorCells) {
return separatorCells.map((cell) => {
const left = cell.startsWith(':');
const right = cell.endsWith(':');
if (left && right) return 'a-center';
if (right) return 'a-right';
return '';
});
}
function renderTable(lines, className = '') {
const rows = lines.map(tableCells);
const header = rows[0];
const data = isSeparator(rows[1] ?? []) ? rows.slice(2) : rows.slice(1);
const cells = (tag, row) => row.map((cell) => `<${tag}>${renderInline(cell)}</${tag}>`).join('');
const hasSeparator = isSeparator(rows[1] ?? []);
const align = hasSeparator ? columnAlignment(rows[1]) : header.map(() => '');
const data = hasSeparator ? rows.slice(2) : rows.slice(1);
// A column whose longest cell is short (IDs, dates, counts, statuses) is
// marked "tight" so auto table layout shrinks it to its content instead of
// handing every column an equal share of the page width.
const widest = header.map((_, column) => Math.max(
(header[column] ?? '').length,
...data.map((row) => (row[column] ?? '').replace(/[`*]/g, '').length)
));
const classFor = (column) => [
align[column] ?? '',
widest[column] <= TIGHT_COLUMN_MAX ? 'tight' : ''
].filter(Boolean).join(' ');
const cells = (tag, row) => row.map((cell, column) => {
const attr = classFor(column);
return `<${tag}${attr ? ` class="${attr}"` : ''}>${renderInline(cell)}</${tag}>`;
}).join('');
return `<table${className ? ` class="${className}"` : ''}><thead><tr>${cells('th', header)}</tr></thead><tbody>${data.map((row) => `<tr>${cells('td', row)}</tr>`).join('')}</tbody></table>`;
}
@@ -79,10 +111,23 @@ function markdownToHtml(markdown) {
continue;
}
if (/^\d+\.\s+/.test(line)) {
const items = [];
while (index < lines.length && /^\d+\.\s+/.test(lines[index])) items.push(`<li>${renderInline(lines[index++].replace(/^\d+\.\s+/, ''))}</li>`);
output.push(`<ol>${items.join('')}</ol>`);
continue;
}
const paragraph = [];
while (index < lines.length && lines[index].trim() && !isTableLine(lines[index]) && !/^(#{1,3})\s+/.test(lines[index]) && !/^[-*]\s+/.test(lines[index])) paragraph.push(lines[index++]);
const approvalClass = paragraph.some((line) => /^(Name|Role|Signature|Date|Position|Company|Project roles|Decision):/.test(line.trimEnd())) ? ' class="approval-fields"' : '';
output.push(`<p${approvalClass}>${renderParagraph(paragraph)}</p>`);
while (index < lines.length && lines[index].trim() && !isTableLine(lines[index]) && !/^(#{1,3})\s+/.test(lines[index]) && !/^[-*]\s+/.test(lines[index]) && !/^\d+\.\s+/.test(lines[index])) paragraph.push(lines[index++]);
const isApproval = paragraph.some((line) => /^(Name|Role|Signature|Date|Position|Company|Project roles|Decision):/.test(line.trimEnd()));
const html = `<p${isApproval ? ' class="approval-fields"' : ''}>${renderParagraph(paragraph)}</p>`;
// Keep a signatory's heading and fields on one page.
if (isApproval && /^<h3>/.test(output[output.length - 1] ?? '')) {
output.push(`<div class="approval-block__person">${output.pop()}${html}</div>`);
} else {
output.push(html);
}
}
return output.join('\n');