Fix SDLC delivery rendering and regenerate package
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 18mm 16mm 28mm;
|
||||
margin: 18mm 16mm 20mm;
|
||||
}
|
||||
|
||||
:root {
|
||||
@@ -64,7 +64,7 @@ body { margin: 0; }
|
||||
font-size: 15pt;
|
||||
font-weight: 500;
|
||||
flex: 0 1 72mm;
|
||||
overflow-wrap: anywhere;
|
||||
overflow-wrap: break-word;
|
||||
min-width: 52mm;
|
||||
padding: 4mm 6mm;
|
||||
text-align: center;
|
||||
@@ -80,7 +80,7 @@ body { margin: 0; }
|
||||
.document-control th,
|
||||
.document-control td {
|
||||
border: .5pt solid var(--brn-line);
|
||||
padding: 2.2mm 3mm;
|
||||
padding: 2mm 3mm;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
@@ -88,43 +88,75 @@ body { margin: 0; }
|
||||
.document-control th {
|
||||
background: var(--brn-pale-blue);
|
||||
font-weight: 700;
|
||||
width: 26%;
|
||||
white-space: nowrap;
|
||||
width: 1%;
|
||||
}
|
||||
|
||||
h1, h2, h3 { break-after: avoid; color: var(--brn-ink); }
|
||||
h1 { font-size: 18pt; margin: 0 0 5mm; }
|
||||
h2 { border-left: 4pt solid var(--brn-blue); font-size: 14pt; margin: 9mm 0 4mm; padding-left: 3mm; }
|
||||
h3 { color: var(--brn-blue); font-size: 11.5pt; margin: 6mm 0 3mm; }
|
||||
h2 { border-left: 4pt solid var(--brn-blue); font-size: 14pt; margin: 6mm 0 3mm; padding-left: 3mm; }
|
||||
h3 { color: var(--brn-blue); font-size: 11.5pt; margin: 4mm 0 2mm; }
|
||||
|
||||
p { margin: 0 0 3.5mm; }
|
||||
p { margin: 0 0 3mm; }
|
||||
|
||||
ul, ol { margin: 0 0 3.5mm; padding-left: 7mm; }
|
||||
li { margin: 0 0 1mm; }
|
||||
|
||||
code {
|
||||
font-family: "DejaVu Sans Mono", "Courier New", monospace;
|
||||
font-size: .9em;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
table:not(.document-control) {
|
||||
border-collapse: collapse;
|
||||
border: .75pt solid #718096;
|
||||
break-inside: auto;
|
||||
font-size: 9pt;
|
||||
margin: 0 0 5mm;
|
||||
table-layout: fixed;
|
||||
table-layout: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table:not(.document-control) th,
|
||||
table:not(.document-control) td {
|
||||
border: .5pt solid var(--brn-line);
|
||||
overflow-wrap: anywhere;
|
||||
padding: 2mm;
|
||||
break-inside: avoid;
|
||||
overflow-wrap: break-word;
|
||||
padding: 1.6mm 2mm;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
table:not(.document-control) th { background: var(--brn-pale-blue); font-weight: 700; }
|
||||
|
||||
/* Column alignment carried over from the Markdown separator row. */
|
||||
table:not(.document-control) th.a-right,
|
||||
table:not(.document-control) td.a-right { text-align: right; }
|
||||
table:not(.document-control) th.a-center,
|
||||
table:not(.document-control) td.a-center { text-align: center; }
|
||||
|
||||
/* Short columns (IDs, dates, counts) shrink to their content instead of
|
||||
taking an equal share of the table width. */
|
||||
table:not(.document-control) th.tight,
|
||||
table:not(.document-control) td.tight { white-space: nowrap; width: 1%; }
|
||||
|
||||
thead { display: table-header-group; }
|
||||
tr { break-inside: avoid; }
|
||||
|
||||
.approval-block { break-inside: avoid; margin-top: 9mm; }
|
||||
.approval-block__person { margin: 5mm 0; min-height: 21mm; }
|
||||
.approval-block__line { border-bottom: .5pt solid var(--brn-ink); display: inline-block; min-width: 70mm; }
|
||||
.approval-block__person { break-inside: avoid; margin: 0 0 6mm; }
|
||||
.approval-block__person h3 { margin: 0 0 2mm; }
|
||||
|
||||
.approval-block__line {
|
||||
border-bottom: .5pt solid var(--brn-ink);
|
||||
display: inline-block;
|
||||
min-width: 68mm;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.approval-fields {
|
||||
line-height: 1.62;
|
||||
margin: 0 0 8mm;
|
||||
padding: 2.5mm 0 4mm;
|
||||
line-height: 1.55;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user