ghost-paper 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -284,6 +284,10 @@ function parse(markdown) {
|
|
|
284
284
|
}
|
|
285
285
|
continue;
|
|
286
286
|
}
|
|
287
|
+
if (node.type === "thematicBreak") {
|
|
288
|
+
currentTab.elements.push({ kind: "pagebreak" });
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
287
291
|
if (node.type === "blockquote") {
|
|
288
292
|
const html = blockquoteToHtml(node);
|
|
289
293
|
currentTab.elements.push({ kind: "aside", html });
|
|
@@ -957,6 +961,12 @@ var CSS = `
|
|
|
957
961
|
color: var(--text-primary);
|
|
958
962
|
}
|
|
959
963
|
|
|
964
|
+
hr.pagebreak {
|
|
965
|
+
border: none;
|
|
966
|
+
border-top: 1px solid var(--border);
|
|
967
|
+
margin: 32px 0;
|
|
968
|
+
}
|
|
969
|
+
|
|
960
970
|
.tab-content .footnote {
|
|
961
971
|
font-size: 13px;
|
|
962
972
|
color: var(--text-muted);
|
|
@@ -1128,31 +1138,26 @@ var PRINT_CSS = `
|
|
|
1128
1138
|
.sidebar, .mobile-header, .mobile-dropdown, .mobile-overlay { display: none !important; }
|
|
1129
1139
|
.main { margin-left: 0; min-height: auto; }
|
|
1130
1140
|
|
|
1131
|
-
/* \u2500\u2500 Cover page \u2014 full bleed dark background \u2500\u2500 */
|
|
1141
|
+
/* \u2500\u2500 Cover page \u2014 full bleed dark background, content at bottom \u2500\u2500 */
|
|
1132
1142
|
.print-header {
|
|
1133
1143
|
min-height: 100vh;
|
|
1134
1144
|
display: flex;
|
|
1135
1145
|
flex-direction: column;
|
|
1136
|
-
justify-content: flex-
|
|
1146
|
+
justify-content: flex-end;
|
|
1137
1147
|
background: var(--charcoal);
|
|
1138
1148
|
margin: -0.7in -0.65in 0;
|
|
1139
|
-
padding: calc(0.
|
|
1149
|
+
padding: 56px calc(0.65in + 48px) calc(0.7in + 56px);
|
|
1140
1150
|
page-break-after: always;
|
|
1141
1151
|
break-after: page;
|
|
1142
1152
|
}
|
|
1143
1153
|
.print-title {
|
|
1144
|
-
font-size:
|
|
1154
|
+
font-size: 72px;
|
|
1145
1155
|
font-weight: 700;
|
|
1146
|
-
letter-spacing: -
|
|
1156
|
+
letter-spacing: -3px;
|
|
1147
1157
|
color: #fff;
|
|
1148
|
-
padding-bottom:
|
|
1158
|
+
padding-bottom: 24px;
|
|
1149
1159
|
border-bottom: 5px solid var(--accent);
|
|
1150
|
-
line-height:
|
|
1151
|
-
word-spacing: 100vw;
|
|
1152
|
-
}
|
|
1153
|
-
.print-title .title-minor {
|
|
1154
|
-
color: rgba(255,255,255,0.4);
|
|
1155
|
-
font-weight: 400;
|
|
1160
|
+
line-height: 1.05;
|
|
1156
1161
|
}
|
|
1157
1162
|
.print-subtitle {
|
|
1158
1163
|
font-size: 15px;
|
|
@@ -1225,19 +1230,28 @@ var PRINT_CSS = `
|
|
|
1225
1230
|
}
|
|
1226
1231
|
|
|
1227
1232
|
/* Tables: in-column with light grey box */
|
|
1228
|
-
.
|
|
1229
|
-
|
|
1233
|
+
.table-wrap {
|
|
1234
|
+
overflow: hidden;
|
|
1230
1235
|
margin: 12px 0;
|
|
1231
1236
|
break-inside: avoid;
|
|
1232
|
-
width: 100%;
|
|
1233
1237
|
background: #F6F6F4;
|
|
1238
|
+
border-radius: 4px;
|
|
1239
|
+
}
|
|
1240
|
+
.table-wrap.table-wide { column-span: all; }
|
|
1241
|
+
.tab-content table {
|
|
1242
|
+
font-size: 10px;
|
|
1243
|
+
margin: 0;
|
|
1244
|
+
width: 100%;
|
|
1245
|
+
table-layout: fixed;
|
|
1234
1246
|
border-collapse: separate;
|
|
1235
1247
|
border-spacing: 0;
|
|
1236
|
-
padding:
|
|
1237
|
-
|
|
1248
|
+
padding: 0;
|
|
1249
|
+
background: transparent;
|
|
1250
|
+
border-radius: 0;
|
|
1238
1251
|
}
|
|
1239
|
-
.tab-content
|
|
1240
|
-
.tab-content
|
|
1252
|
+
.tab-content tbody td { word-wrap: break-word; overflow-wrap: break-word; }
|
|
1253
|
+
.tab-content thead th { font-size: 8px; letter-spacing: 0.8px; padding: 8px 10px; border-bottom: 2px solid #D0D0CB; }
|
|
1254
|
+
.tab-content tbody td { padding: 8px 10px; font-weight: 400; border-bottom: 1px solid #E8E8E4; }
|
|
1241
1255
|
.tab-content tbody tr:last-child td { border-bottom: none; }
|
|
1242
1256
|
|
|
1243
1257
|
/* KPIs: compact grid for column width */
|
|
@@ -1263,10 +1277,19 @@ var PRINT_CSS = `
|
|
|
1263
1277
|
.chart-container { height: 190px; overflow: hidden; }
|
|
1264
1278
|
|
|
1265
1279
|
.kpi-strip { break-inside: avoid; page-break-inside: avoid; }
|
|
1266
|
-
table { break-inside: auto; }
|
|
1267
1280
|
thead { display: table-header-group; }
|
|
1268
1281
|
tr { break-inside: avoid; }
|
|
1269
1282
|
|
|
1283
|
+
hr.pagebreak {
|
|
1284
|
+
border: none;
|
|
1285
|
+
margin: 0;
|
|
1286
|
+
padding: 0;
|
|
1287
|
+
height: 0;
|
|
1288
|
+
column-span: all;
|
|
1289
|
+
break-after: page;
|
|
1290
|
+
page-break-after: always;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1270
1293
|
/* First appendix section starts on a new page */
|
|
1271
1294
|
.tab-appendix-start { page-break-before: always; break-before: page; }
|
|
1272
1295
|
|
|
@@ -1333,12 +1356,13 @@ function renderTable(table) {
|
|
|
1333
1356
|
}).join("");
|
|
1334
1357
|
return `<tr>${cells}</tr>`;
|
|
1335
1358
|
}).join("\n ");
|
|
1336
|
-
|
|
1359
|
+
const wideClass = table.headers.length >= 5 ? " table-wide" : "";
|
|
1360
|
+
return ` <div class="table-wrap${wideClass}"><table>
|
|
1337
1361
|
<thead><tr>${thead}</tr></thead>
|
|
1338
1362
|
<tbody>
|
|
1339
1363
|
${tbody}
|
|
1340
1364
|
</tbody>
|
|
1341
|
-
</table>`;
|
|
1365
|
+
</table></div>`;
|
|
1342
1366
|
}
|
|
1343
1367
|
function renderChart(chartId, caption, chartType, rowCount) {
|
|
1344
1368
|
let styleAttr = "";
|
|
@@ -1365,6 +1389,8 @@ function renderElement(el) {
|
|
|
1365
1389
|
return renderTable(el.table);
|
|
1366
1390
|
case "aside":
|
|
1367
1391
|
return ` <aside>${el.html}</aside>`;
|
|
1392
|
+
case "pagebreak":
|
|
1393
|
+
return ` <hr class="pagebreak">`;
|
|
1368
1394
|
}
|
|
1369
1395
|
}
|
|
1370
1396
|
function buildSidebar(doc) {
|
|
@@ -1447,32 +1473,11 @@ function buildChartScript(doc, printMode = false) {
|
|
|
1447
1473
|
if (printMode) return script + "\nwindow.__ghostPaperChartsReady = true;";
|
|
1448
1474
|
return script;
|
|
1449
1475
|
}
|
|
1450
|
-
function stylizePrintTitle(raw) {
|
|
1451
|
-
const MINOR = /* @__PURE__ */ new Set(["the", "a", "an", "for", "of", "in", "and", "or", "to", "with", "by", "on", "at", "is"]);
|
|
1452
|
-
const words = escapeHtml3(raw).split(/\s+/);
|
|
1453
|
-
const result = [];
|
|
1454
|
-
let i = 0;
|
|
1455
|
-
while (i < words.length) {
|
|
1456
|
-
const lower = words[i].toLowerCase();
|
|
1457
|
-
if (i + 1 < words.length && MINOR.has(lower) && MINOR.has(words[i + 1].toLowerCase())) {
|
|
1458
|
-
result.push(`<span class="title-minor" style="white-space:nowrap;word-spacing:normal">${words[i]} ${words[i + 1]}</span>`);
|
|
1459
|
-
i += 2;
|
|
1460
|
-
} else if (MINOR.has(lower)) {
|
|
1461
|
-
result.push(`<span class="title-minor">${words[i]}</span>`);
|
|
1462
|
-
i++;
|
|
1463
|
-
} else {
|
|
1464
|
-
result.push(words[i]);
|
|
1465
|
-
i++;
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
|
-
return result.join(" ");
|
|
1469
|
-
}
|
|
1470
1476
|
function buildPrintContent(doc) {
|
|
1471
1477
|
const subtitle = doc.frontmatter.subtitle ? `
|
|
1472
1478
|
<div class="print-subtitle">${escapeHtml3(doc.frontmatter.subtitle)}</div>` : "";
|
|
1473
|
-
const styledTitle = stylizePrintTitle(doc.frontmatter.title);
|
|
1474
1479
|
const header = ` <div class="print-header">
|
|
1475
|
-
<div class="print-title">${
|
|
1480
|
+
<div class="print-title">${escapeHtml3(doc.frontmatter.title)}</div>${subtitle}
|
|
1476
1481
|
</div>`;
|
|
1477
1482
|
const tabs = doc.tabs.map((tab, i) => {
|
|
1478
1483
|
const elements = tab.elements.map(renderElement).join("\n\n");
|
|
@@ -1517,7 +1522,7 @@ function buildPrintHtml(markdown) {
|
|
|
1517
1522
|
async function buildPdf(markdown, outputPath, options) {
|
|
1518
1523
|
const doc = parse(markdown);
|
|
1519
1524
|
const html = render(doc, { printMode: true });
|
|
1520
|
-
const { generatePdf } = await import("./pdf-
|
|
1525
|
+
const { generatePdf } = await import("./pdf-CVTVSJBX.js");
|
|
1521
1526
|
await generatePdf({
|
|
1522
1527
|
html,
|
|
1523
1528
|
outputPath,
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
build
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VH6KYYQG.js";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -72,7 +72,7 @@ buildCmd.command("pdf").description("Convert a markdown file to a PDF report").a
|
|
|
72
72
|
const inputPath = resolve(input);
|
|
73
73
|
const markdown = readFileSync(inputPath, "utf-8");
|
|
74
74
|
const outputPath = opts.output ? resolve(opts.output) : inputPath.replace(/\.md$/, ".pdf");
|
|
75
|
-
const { buildPdf } = await import("./-
|
|
75
|
+
const { buildPdf } = await import("./-3QXRJ2CJ.js");
|
|
76
76
|
await buildPdf(markdown, outputPath, {
|
|
77
77
|
pageSize: opts.pageSize ?? "A4",
|
|
78
78
|
landscape: opts.landscape ?? false
|
|
@@ -52,8 +52,9 @@ async function generatePdf(opts) {
|
|
|
52
52
|
});
|
|
53
53
|
await new Promise((r) => setTimeout(r, 300));
|
|
54
54
|
const title = escapeHtml(opts.title ?? "");
|
|
55
|
-
const
|
|
56
|
-
const
|
|
55
|
+
const hideOnPage1 = `<script>if(document.querySelector('.pageNumber').textContent==='1')document.querySelector('#c').style.display='none';</script>`;
|
|
56
|
+
const headerTemplate = `<div style="font-size: 8px; font-family: -apple-system, system-ui, sans-serif; color: #999; width: 100%; padding: 0 0.65in; letter-spacing: 0.5px; text-transform: uppercase;"><span id="c">${title}</span>${hideOnPage1}</div>`;
|
|
57
|
+
const footerTemplate = `<div style="font-size: 8px; font-family: -apple-system, system-ui, sans-serif; color: #999; width: 100%; text-align: right; padding: 0 0.65in;"><span id="c"><span class="pageNumber"></span> / <span class="totalPages"></span></span>${hideOnPage1}</div>`;
|
|
57
58
|
const pdfBuffer = await page.pdf({
|
|
58
59
|
format: opts.pageSize ?? "A4",
|
|
59
60
|
landscape: opts.landscape ?? false,
|