feat: feel cargo doc
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Generate output HTML from template + data for cargo-filling documents.
|
||||
*
|
||||
* Usage: node generate.mjs <docId>
|
||||
* Example: node generate.mjs cargo-filling-1
|
||||
*
|
||||
* Reads: docs/<docId>.template.html
|
||||
* Reads: docs/<docId>.data.json (optional)
|
||||
* Writes: docs/<docId>.output.html
|
||||
*
|
||||
* data.json format:
|
||||
* {
|
||||
* pages: [{
|
||||
* page: 1,
|
||||
* cards: [{
|
||||
* index: 0,
|
||||
* asteroidScale: 1.15,
|
||||
* ship: { right: -10, top: -3, width: 85, height: 40 },
|
||||
* innerAst: { left: 60, top: 70 }
|
||||
* }]
|
||||
* }]
|
||||
* }
|
||||
*
|
||||
* Transforms:
|
||||
* - asteroidScale: scale on .space-asteroid img, .remnant-shape .shape-container, .inner-ast
|
||||
* - ship: inline right/top/width/height on .cargo-ship
|
||||
* - innerAst: inline left/top on .inner-ast (composed with translate and optional scale)
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { postGenerate } from '../../../src/scripts/post-generate.mjs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const docsDir = join(__dirname, '..', 'docs');
|
||||
|
||||
const docId = process.argv[2];
|
||||
if (!docId) {
|
||||
console.error('Usage: node generate.mjs <docId>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const templatePath = join(docsDir, `${docId}.template.html`);
|
||||
const dataPath = join(docsDir, `${docId}.data.json`);
|
||||
const outputPath = join(docsDir, `${docId}.output.html`);
|
||||
|
||||
if (!existsSync(templatePath)) {
|
||||
console.error(`Template not found: ${templatePath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let html = readFileSync(templatePath, 'utf-8');
|
||||
|
||||
if (existsSync(dataPath)) {
|
||||
const data = JSON.parse(readFileSync(dataPath, 'utf-8'));
|
||||
html = applyData(html, data);
|
||||
console.log(`Applied data from ${data.pages?.length || 0} pages`);
|
||||
}
|
||||
|
||||
writeFileSync(outputPath, html);
|
||||
console.log(`Generated: ${outputPath}`);
|
||||
await postGenerate(outputPath);
|
||||
|
||||
function applyData(html, data) {
|
||||
if (!data.pages) return html;
|
||||
|
||||
// Split HTML into pages by w-[210mm] h-[297mm] divs
|
||||
const pageRegex = /<div class="w-\[210mm\] h-\[297mm\]/g;
|
||||
const starts = [];
|
||||
let match;
|
||||
while ((match = pageRegex.exec(html)) !== null) {
|
||||
starts.push(match.index);
|
||||
}
|
||||
if (starts.length === 0) return html;
|
||||
|
||||
// Process pages in reverse to preserve indices
|
||||
for (let i = data.pages.length - 1; i >= 0; i--) {
|
||||
const pageData = data.pages[i];
|
||||
const pageNum = pageData.page || (i + 1);
|
||||
const pageIdx = pageNum - 1;
|
||||
if (pageIdx >= starts.length) continue;
|
||||
|
||||
const pageStart = starts[pageIdx];
|
||||
const pageEnd = pageIdx + 1 < starts.length ? starts[pageIdx + 1] : html.length;
|
||||
let pageHtml = html.slice(pageStart, pageEnd);
|
||||
|
||||
if (pageData.cards) {
|
||||
pageHtml = applyCards(pageHtml, pageData.cards);
|
||||
}
|
||||
|
||||
html = html.slice(0, pageStart) + pageHtml + html.slice(pageEnd);
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function applyCards(pageHtml, cards) {
|
||||
// Find card boundaries by <!-- Card comments (two formats: "Card 1:" and "Card:")
|
||||
const cardRegex = /<!-- Card[\s:]/g;
|
||||
const cardStarts = [];
|
||||
let match;
|
||||
while ((match = cardRegex.exec(pageHtml)) !== null) {
|
||||
cardStarts.push(match.index);
|
||||
}
|
||||
if (cardStarts.length === 0) return pageHtml;
|
||||
|
||||
// Process cards in reverse to preserve indices
|
||||
for (let i = cards.length - 1; i >= 0; i--) {
|
||||
const cardData = cards[i];
|
||||
const cardIdx = cardData.index;
|
||||
if (cardIdx >= cardStarts.length) continue;
|
||||
|
||||
const cardStart = cardStarts[cardIdx];
|
||||
const cardEnd = cardIdx + 1 < cardStarts.length
|
||||
? cardStarts[cardIdx + 1]
|
||||
: pageHtml.length;
|
||||
|
||||
let cardHtml = pageHtml.slice(cardStart, cardEnd);
|
||||
|
||||
const scale = cardData.asteroidScale;
|
||||
const ship = cardData.ship;
|
||||
const innerAst = cardData.innerAst;
|
||||
|
||||
// Apply asteroidScale to .space-asteroid img
|
||||
if (scale != null && scale !== 1) {
|
||||
// Match <div class="space-asteroid"> then find the <img inside
|
||||
cardHtml = cardHtml.replace(
|
||||
/(<div class="space-asteroid">[\s]*<img [^>]*?)(>)/,
|
||||
function(full, before, close) {
|
||||
// Remove any existing style, then add new one
|
||||
const cleaned = before.replace(/ style="[^"]*"/, '');
|
||||
return cleaned + ' style="transform: scale(' + scale + ')"' + close;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Apply asteroidScale to .remnant-shape .shape-container
|
||||
if (scale != null && scale !== 1) {
|
||||
cardHtml = cardHtml.replace(
|
||||
/<div class="shape-container">/,
|
||||
'<div class="shape-container" style="transform: scale(' + scale + ')">'
|
||||
);
|
||||
}
|
||||
|
||||
// Apply ship position/size to .cargo-ship
|
||||
if (ship) {
|
||||
const shipRight = ship.right != null ? ship.right : -12.5;
|
||||
const shipTop = ship.top != null ? ship.top : -5;
|
||||
const shipWidth = ship.width != null ? ship.width : 90;
|
||||
const shipHeight = ship.height != null ? ship.height : 42;
|
||||
const shipStyle = 'right: ' + shipRight + 'mm; top: ' + shipTop + 'mm; width: ' + shipWidth + 'mm; height: ' + shipHeight + 'mm';
|
||||
|
||||
cardHtml = cardHtml.replace(
|
||||
/(<div class="cargo-ship")([\s>])/,
|
||||
function(full, tag, after) {
|
||||
return tag + ' style="' + shipStyle + '"' + after;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Apply innerAst position (and compose with asteroidScale if present)
|
||||
if (innerAst || (scale != null && scale !== 1)) {
|
||||
const innerLeft = innerAst && innerAst.left != null ? innerAst.left : 58;
|
||||
const innerTop = innerAst && innerAst.top != null ? innerAst.top : 73;
|
||||
|
||||
let transformPart = 'translate(-50%, -50%)';
|
||||
if (scale != null && scale !== 1) {
|
||||
transformPart = 'translate(-50%, -50%) scale(' + scale + ')';
|
||||
}
|
||||
|
||||
let innerStyle = 'left: ' + innerLeft + '%; top: ' + innerTop + '%; transform: ' + transformPart;
|
||||
|
||||
// Only apply if we actually have something to change
|
||||
const needsInnerChange = (innerAst && (innerAst.left != null || innerAst.top != null)) || (scale != null && scale !== 1);
|
||||
if (needsInnerChange) {
|
||||
cardHtml = cardHtml.replace(
|
||||
/(<div class="inner-ast")([\s>])/,
|
||||
function(full, tag, after) {
|
||||
return tag + ' style="' + innerStyle + '"' + after;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pageHtml = pageHtml.slice(0, cardStart) + cardHtml + pageHtml.slice(cardEnd);
|
||||
}
|
||||
|
||||
return pageHtml;
|
||||
}
|
||||
Reference in New Issue
Block a user