gutterpress 0.11.17 → 0.11.18-alpha.10
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.
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +4 -2
- package/dist/{build-gxy185pv.js → build-2tgs5j4d.js} +4 -4
- package/dist/{cli-zfgvssqz.js → cli-d0cq87s5.js} +1 -1
- package/dist/{cli-a4qtctrw.js → cli-gf1ywzeg.js} +411 -50
- package/dist/{cli-ycbg7mxk.js → cli-ptez7zcc.js} +53 -5
- package/dist/{cli-8nw6d915.js → cli-ytck518d.js} +1 -1
- package/dist/cli.js +12 -12
- package/dist/{doctor-fygmzevd.js → doctor-y4yg51zy.js} +2 -2
- package/dist/engine/compiler/build.d.ts +1 -1
- package/dist/{engine-yf2w9hj7.js → engine-wtnakejg.js} +1 -1
- package/dist/{engine-5d1rpxxq.js → engine-zy6fzs5a.js} +2 -2
- package/dist/{ext-c48s56jm.js → ext-t2sye4as.js} +4 -4
- package/dist/{gutterpress-viewer-ez7k4ha7.js → gutterpress-viewer-k4mpqjh0.js} +406 -14
- package/dist/{index-3k3fyrec.js → index-cf1kanp0.js} +454 -52
- package/dist/{index-vmcfahe2.js → index-jt552wtf.js} +5 -5
- package/dist/{index-09h3n36d.js → index-n8vs6pss.js} +51 -2
- package/dist/index.js +5 -3
- package/dist/lib/markdown/block-names.d.ts +82 -0
- package/dist/lib/markdown/renderer.d.ts +3 -2
- package/dist/lib/markdown/table-align.d.ts +32 -0
- package/dist/lib/presets.d.ts +11 -0
- package/dist/lib/printsafe.d.ts +1 -0
- package/dist/lib/remote-auth/sync.d.ts +21 -0
- package/dist/lib/unknown-classes.d.ts +12 -0
- package/dist/{manifest.schema-gh0jjve9.json → manifest.schema-5pzhscqh.json} +7 -1
- package/dist/{new-a9m2jwyy.js → new-pbd5zrds.js} +4 -4
- package/dist/{preflight-w3tnbtb8.js → preflight-vnkb8tj7.js} +4 -4
- package/dist/preview/file-watcher.d.ts +4 -1
- package/dist/{preview-bridge-8jhd1wpb.js → preview-bridge-evecsh4w.js} +5 -1
- package/dist/{preview-7z8tt0b4.js → preview-g1hp0jt8.js} +4 -4
- package/dist/{preview-interface-c44qk17f.js → preview-interface-2n1d9pbf.js} +77 -1
- package/dist/{preview-shell-xwn84kqr.js → preview-shell-hyp7g158.js} +9 -0
- package/dist/{publish-bbftdxh2.js → publish-vkbmf3tj.js} +4 -4
- package/dist/render.js +199 -28
- package/dist/schema/manifest.types.d.ts +11 -0
- package/dist/{validate-x1ha1gqj.js → validate-q1xj2qxn.js} +4 -4
- package/package.json +1 -1
|
@@ -13,12 +13,12 @@ import {
|
|
|
13
13
|
stampCreator,
|
|
14
14
|
stripAnnotations,
|
|
15
15
|
warn
|
|
16
|
-
} from "./cli-
|
|
16
|
+
} from "./cli-d0cq87s5.js";
|
|
17
17
|
import {
|
|
18
18
|
DEBOUNCE,
|
|
19
19
|
UsageError,
|
|
20
20
|
resolvePort
|
|
21
|
-
} from "./cli-
|
|
21
|
+
} from "./cli-ytck518d.js";
|
|
22
22
|
import {
|
|
23
23
|
blackTextToGray
|
|
24
24
|
} from "./cli-ht1bcqdd.js";
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
getAssetPath,
|
|
28
28
|
getAssetsDir,
|
|
29
29
|
lastPageMode
|
|
30
|
-
} from "./cli-
|
|
30
|
+
} from "./cli-ptez7zcc.js";
|
|
31
31
|
import {
|
|
32
32
|
BuildError,
|
|
33
33
|
EXIT_CODES
|
|
@@ -120,7 +120,8 @@ var DTRPG_PRESET = {
|
|
|
120
120
|
page: {
|
|
121
121
|
width: 621,
|
|
122
122
|
height: 810,
|
|
123
|
-
tolerance: 0.5
|
|
123
|
+
tolerance: 0.5,
|
|
124
|
+
bleed: 9
|
|
124
125
|
},
|
|
125
126
|
ink: {
|
|
126
127
|
maxTac: 240,
|
|
@@ -196,7 +197,8 @@ var BOOK_PRESET = {
|
|
|
196
197
|
page: {
|
|
197
198
|
width: 432,
|
|
198
199
|
height: 648,
|
|
199
|
-
tolerance: 0.5
|
|
200
|
+
tolerance: 0.5,
|
|
201
|
+
bleed: undefined
|
|
200
202
|
},
|
|
201
203
|
ink: {
|
|
202
204
|
maxTac: 400,
|
|
@@ -543,7 +545,8 @@ function resolveCustomPage(c, m) {
|
|
|
543
545
|
return {
|
|
544
546
|
width,
|
|
545
547
|
height,
|
|
546
|
-
tolerance: c.page?.tolerance ?? m.page?.tolerance ?? 0.5
|
|
548
|
+
tolerance: c.page?.tolerance ?? m.page?.tolerance ?? 0.5,
|
|
549
|
+
bleed: c.page?.bleed ?? m.page?.bleed
|
|
547
550
|
};
|
|
548
551
|
}
|
|
549
552
|
function resolveWithPreset(cliOverrides, manifest, preset) {
|
|
@@ -823,10 +826,11 @@ function parseMarkerLine(line, declaredWords) {
|
|
|
823
826
|
const kind = head.slice(1);
|
|
824
827
|
if (!KNOWN_KINDS.includes(kind) && !(declaredWords && declaredWords.has(kind)))
|
|
825
828
|
return null;
|
|
826
|
-
if (kind === "
|
|
829
|
+
if (kind === "continue" || kind.startsWith("end-")) {
|
|
827
830
|
return { kind, name: null, attrs: {} };
|
|
828
831
|
}
|
|
829
|
-
const
|
|
832
|
+
const isBreak = kind === "page-break" || kind === "column-break";
|
|
833
|
+
const body = isBreak ? tokens.slice(1).filter((token) => !isBareToken(token)) : tokens.slice(1);
|
|
830
834
|
const hasExplicitAttrsOrShorthand = body.some((token) => token.includes("=") || token.startsWith(".") || token.startsWith("#"));
|
|
831
835
|
const bareTokens = body.filter(isBareToken);
|
|
832
836
|
let name = null;
|
|
@@ -894,6 +898,8 @@ function parseMarkerLine(line, declaredWords) {
|
|
|
894
898
|
}
|
|
895
899
|
if (classes.length)
|
|
896
900
|
attrs.class = classes.join(" ");
|
|
901
|
+
if (isBreak)
|
|
902
|
+
return { kind, name: null, attrs };
|
|
897
903
|
const marker = { kind, name, attrs };
|
|
898
904
|
if (hasAmbiguousBareToken)
|
|
899
905
|
marker.__ambiguousBareToken = true;
|
|
@@ -1076,6 +1082,11 @@ function attachDataAttrs(token, kind, name, attrs) {
|
|
|
1076
1082
|
token.attrSet(k.startsWith("data-") ? k : `data-${k}`, v);
|
|
1077
1083
|
}
|
|
1078
1084
|
}
|
|
1085
|
+
function attachBreak(token, baseClass, attrs = {}) {
|
|
1086
|
+
addClasses(token, baseClass, attrs.class);
|
|
1087
|
+
token.attrSet("aria-hidden", "true");
|
|
1088
|
+
attachDataAttrs(token, "break", null, attrs);
|
|
1089
|
+
}
|
|
1079
1090
|
var DECLARED_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
1080
1091
|
function validateDeclaredMarkerName(name, pluginName) {
|
|
1081
1092
|
if (!DECLARED_NAME_RE.test(name)) {
|
|
@@ -1632,7 +1643,11 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1632
1643
|
attachDataAttrs(t, component.kind, component.variant, meta.attrs || {});
|
|
1633
1644
|
else
|
|
1634
1645
|
attachDataAttrs(t, "section", meta.name, meta.attrs || {});
|
|
1635
|
-
t.meta =
|
|
1646
|
+
t.meta = {
|
|
1647
|
+
line: meta.__line,
|
|
1648
|
+
...component || {},
|
|
1649
|
+
...meta.__continued ? { continued: true } : {}
|
|
1650
|
+
};
|
|
1636
1651
|
stack.open({
|
|
1637
1652
|
kind: "section",
|
|
1638
1653
|
classes: meta.attrs && meta.attrs.class || "",
|
|
@@ -1730,12 +1745,12 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1730
1745
|
drainDeclaredFrames(keep);
|
|
1731
1746
|
const contMeta = {
|
|
1732
1747
|
name: section.meta.name,
|
|
1733
|
-
attrs: { ...section.meta.attrs || {} }
|
|
1748
|
+
attrs: { ...section.meta.attrs || {} },
|
|
1749
|
+
__continued: true
|
|
1734
1750
|
};
|
|
1735
|
-
|
|
1751
|
+
delete contMeta.attrs.id;
|
|
1752
|
+
if (section.meta.component)
|
|
1736
1753
|
contMeta.__component = section.meta.component;
|
|
1737
|
-
contMeta.__continued = true;
|
|
1738
|
-
}
|
|
1739
1754
|
contMeta.__line = line;
|
|
1740
1755
|
const cls = (contMeta.attrs.class || "").split(/\s+/).filter(Boolean);
|
|
1741
1756
|
if (!cls.includes("gp-continued"))
|
|
@@ -1748,16 +1763,14 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1748
1763
|
if (kind === "page-break") {
|
|
1749
1764
|
const t = new state.Token("layout_page_break", "div", 0);
|
|
1750
1765
|
t.meta = { line };
|
|
1751
|
-
t
|
|
1752
|
-
t.attrSet("aria-hidden", "true");
|
|
1766
|
+
attachBreak(t, "gp-page-break", meta.attrs);
|
|
1753
1767
|
out.push(t);
|
|
1754
1768
|
continue;
|
|
1755
1769
|
}
|
|
1756
1770
|
if (kind === "column-break") {
|
|
1757
1771
|
const t = new state.Token("layout_column_break", "div", 0);
|
|
1758
1772
|
t.meta = { line };
|
|
1759
|
-
t
|
|
1760
|
-
t.attrSet("aria-hidden", "true");
|
|
1773
|
+
attachBreak(t, "gp-column-break", meta.attrs);
|
|
1761
1774
|
out.push(t);
|
|
1762
1775
|
continue;
|
|
1763
1776
|
}
|
|
@@ -1821,24 +1834,10 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1821
1834
|
}
|
|
1822
1835
|
});
|
|
1823
1836
|
}
|
|
1824
|
-
|
|
1825
|
-
const cls = tokens[idx].attrGet("class") || "gp-page-break";
|
|
1826
|
-
const rangeAttr = tokens[idx].attrGet("data-source-range");
|
|
1827
|
-
const rangeHtml = rangeAttr ? ` data-source-range="${escapeAttr(rangeAttr)}"` : "";
|
|
1828
|
-
const chapterAttr = tokens[idx].attrGet("data-chapter-src");
|
|
1829
|
-
const chapterHtml = chapterAttr ? ` data-chapter-src="${escapeAttr(chapterAttr)}"` : "";
|
|
1830
|
-
return `<div class="${escapeAttr(cls)}" aria-hidden="true"${rangeHtml}${chapterHtml}></div>
|
|
1837
|
+
const renderBreak = (tokens, idx, options2, env, self) => `<div${self.renderAttrs(tokens[idx])}></div>
|
|
1831
1838
|
`;
|
|
1832
|
-
|
|
1833
|
-
md.renderer.rules.layout_column_break =
|
|
1834
|
-
const cls = tokens[idx].attrGet("class") || "gp-column-break";
|
|
1835
|
-
const rangeAttr = tokens[idx].attrGet("data-source-range");
|
|
1836
|
-
const rangeHtml = rangeAttr ? ` data-source-range="${escapeAttr(rangeAttr)}"` : "";
|
|
1837
|
-
const chapterAttr = tokens[idx].attrGet("data-chapter-src");
|
|
1838
|
-
const chapterHtml = chapterAttr ? ` data-chapter-src="${escapeAttr(chapterAttr)}"` : "";
|
|
1839
|
-
return `<div class="${escapeAttr(cls)}" aria-hidden="true"${rangeHtml}${chapterHtml}></div>
|
|
1840
|
-
`;
|
|
1841
|
-
};
|
|
1839
|
+
md.renderer.rules.layout_page_break = renderBreak;
|
|
1840
|
+
md.renderer.rules.layout_column_break = renderBreak;
|
|
1842
1841
|
md.renderer.rules.layout_marker = () => "";
|
|
1843
1842
|
}
|
|
1844
1843
|
var NO_MARGIN_BOXES = [
|
|
@@ -2289,6 +2288,177 @@ function gpPinScope(md) {
|
|
|
2289
2288
|
});
|
|
2290
2289
|
}
|
|
2291
2290
|
|
|
2291
|
+
// src/lib/slug.ts
|
|
2292
|
+
function slugify(name, fallback = "") {
|
|
2293
|
+
return name.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || fallback;
|
|
2294
|
+
}
|
|
2295
|
+
function prettify(slug) {
|
|
2296
|
+
const spaced = slug.replace(/[-_]+/g, " ").trim();
|
|
2297
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
// src/lib/markdown/block-names.ts
|
|
2301
|
+
var BLOCK_OPEN = /^layout_(chapter|spread|page|section|component)_open$/;
|
|
2302
|
+
var BLOCK_CLOSE = /^layout_(chapter|spread|page|section|component)_close$/;
|
|
2303
|
+
var FOLDED = {
|
|
2304
|
+
ß: "ss",
|
|
2305
|
+
æ: "ae",
|
|
2306
|
+
œ: "oe",
|
|
2307
|
+
ø: "o",
|
|
2308
|
+
đ: "d",
|
|
2309
|
+
ð: "d",
|
|
2310
|
+
þ: "th",
|
|
2311
|
+
ł: "l",
|
|
2312
|
+
ı: "i"
|
|
2313
|
+
};
|
|
2314
|
+
function nameSlug(text) {
|
|
2315
|
+
return slugify(text.replace(/['’]/g, "").replace(/[ßæœøđðþłı]/gi, (c) => FOLDED[c.toLowerCase()] ?? c));
|
|
2316
|
+
}
|
|
2317
|
+
function nameFromSrc(src) {
|
|
2318
|
+
if (/^data:/i.test(src))
|
|
2319
|
+
return "";
|
|
2320
|
+
const path2 = src.split(/[?#]/, 1)[0] ?? "";
|
|
2321
|
+
const base = path2.split(/[\\/]/).pop() ?? "";
|
|
2322
|
+
let decoded = base;
|
|
2323
|
+
try {
|
|
2324
|
+
decoded = decodeURIComponent(base);
|
|
2325
|
+
} catch {}
|
|
2326
|
+
return nameSlug(decoded.replace(/\.[^.]*$/, ""));
|
|
2327
|
+
}
|
|
2328
|
+
function takeExplicitName(token) {
|
|
2329
|
+
const data = token.attrGet("data-name");
|
|
2330
|
+
const plain = token.attrGet("name");
|
|
2331
|
+
if (plain !== null && token.attrs)
|
|
2332
|
+
token.attrs = token.attrs.filter(([k]) => k !== "name");
|
|
2333
|
+
if (data)
|
|
2334
|
+
return data;
|
|
2335
|
+
if (plain) {
|
|
2336
|
+
token.attrSet("data-name", plain);
|
|
2337
|
+
return plain;
|
|
2338
|
+
}
|
|
2339
|
+
return "";
|
|
2340
|
+
}
|
|
2341
|
+
function inlineText2(inline) {
|
|
2342
|
+
if (!inline || inline.type !== "inline")
|
|
2343
|
+
return "";
|
|
2344
|
+
const parts = [];
|
|
2345
|
+
for (const child of inline.children ?? []) {
|
|
2346
|
+
if (child.type === "text" || child.type === "text_special" || child.type === "code_inline" || child.type === "image") {
|
|
2347
|
+
parts.push(child.content);
|
|
2348
|
+
} else if (child.type === "softbreak" || child.type === "hardbreak") {
|
|
2349
|
+
parts.push(" ");
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
return parts.join("");
|
|
2353
|
+
}
|
|
2354
|
+
function claim(used, slug) {
|
|
2355
|
+
let name = slug;
|
|
2356
|
+
for (let n = 2;used.has(name); n++)
|
|
2357
|
+
name = `${slug}-${n}`;
|
|
2358
|
+
used.add(name);
|
|
2359
|
+
return name;
|
|
2360
|
+
}
|
|
2361
|
+
function nameBlocksAndHeadings(tokens) {
|
|
2362
|
+
const blocks = [];
|
|
2363
|
+
const open = [];
|
|
2364
|
+
const headings = [];
|
|
2365
|
+
const headingSlugs = new Map;
|
|
2366
|
+
for (let i = 0;i < tokens.length; i++) {
|
|
2367
|
+
const tok = tokens[i];
|
|
2368
|
+
const opened = BLOCK_OPEN.exec(tok.type);
|
|
2369
|
+
if (opened) {
|
|
2370
|
+
const block = { token: tok, type: opened[1], heading: "" };
|
|
2371
|
+
blocks.push(block);
|
|
2372
|
+
open.push(block);
|
|
2373
|
+
continue;
|
|
2374
|
+
}
|
|
2375
|
+
const closed = BLOCK_CLOSE.exec(tok.type);
|
|
2376
|
+
if (closed) {
|
|
2377
|
+
for (let j = open.length - 1;j >= 0; j--) {
|
|
2378
|
+
if (open[j].type === closed[1]) {
|
|
2379
|
+
open.splice(j, 1);
|
|
2380
|
+
break;
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
continue;
|
|
2384
|
+
}
|
|
2385
|
+
if (tok.type === "heading_open") {
|
|
2386
|
+
const slug = nameSlug(inlineText2(tokens[i + 1]));
|
|
2387
|
+
headings.push(tok);
|
|
2388
|
+
headingSlugs.set(tok, slug);
|
|
2389
|
+
const owner = open[open.length - 1];
|
|
2390
|
+
if (owner && !owner.heading)
|
|
2391
|
+
owner.heading = slug;
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
const usedBlocks = new Set;
|
|
2395
|
+
const usedHeadings = new Set;
|
|
2396
|
+
const unnamedBlocks = new Set;
|
|
2397
|
+
for (const block of blocks) {
|
|
2398
|
+
const explicit = block.token.attrGet("data-name");
|
|
2399
|
+
if (explicit)
|
|
2400
|
+
usedBlocks.add(explicit);
|
|
2401
|
+
else
|
|
2402
|
+
unnamedBlocks.add(block);
|
|
2403
|
+
}
|
|
2404
|
+
const unnamedHeadings = [];
|
|
2405
|
+
for (const heading of headings) {
|
|
2406
|
+
const explicit = takeExplicitName(heading);
|
|
2407
|
+
if (explicit)
|
|
2408
|
+
usedHeadings.add(explicit);
|
|
2409
|
+
else
|
|
2410
|
+
unnamedHeadings.push(heading);
|
|
2411
|
+
}
|
|
2412
|
+
let previousSection = null;
|
|
2413
|
+
for (const block of blocks) {
|
|
2414
|
+
const { token } = block;
|
|
2415
|
+
if (block.type === "section") {
|
|
2416
|
+
if (token.meta?.continued && previousSection) {
|
|
2417
|
+
const inherited = previousSection.token.attrGet("data-name");
|
|
2418
|
+
if (inherited)
|
|
2419
|
+
token.attrSet("data-name", inherited);
|
|
2420
|
+
previousSection = block;
|
|
2421
|
+
continue;
|
|
2422
|
+
}
|
|
2423
|
+
previousSection = block;
|
|
2424
|
+
}
|
|
2425
|
+
if (!unnamedBlocks.has(block))
|
|
2426
|
+
continue;
|
|
2427
|
+
const slug = nameSlug(token.attrGet("data-label") ?? "") || block.heading;
|
|
2428
|
+
if (slug)
|
|
2429
|
+
token.attrSet("data-name", claim(usedBlocks, slug));
|
|
2430
|
+
}
|
|
2431
|
+
for (const heading of unnamedHeadings) {
|
|
2432
|
+
const slug = headingSlugs.get(heading);
|
|
2433
|
+
if (slug)
|
|
2434
|
+
heading.attrSet("data-name", claim(usedHeadings, slug));
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
function gpBlockNames(md) {
|
|
2438
|
+
md.core.ruler.push("gp_block_names", (state) => nameBlocksAndHeadings(state.tokens));
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
// src/lib/markdown/table-align.ts
|
|
2442
|
+
var ALIGN = /^text-align:(left|center|right)$/;
|
|
2443
|
+
function gpTableAlign(md) {
|
|
2444
|
+
md.core.ruler.after("block", "gp_table_align", (state) => {
|
|
2445
|
+
for (const token of state.tokens) {
|
|
2446
|
+
if (token.type !== "th_open" && token.type !== "td_open")
|
|
2447
|
+
continue;
|
|
2448
|
+
const match = ALIGN.exec(token.attrGet("style") ?? "");
|
|
2449
|
+
if (!match)
|
|
2450
|
+
continue;
|
|
2451
|
+
token.attrs = (token.attrs ?? []).filter(([key]) => key !== "style");
|
|
2452
|
+
token.attrSet("data-align", match[1]);
|
|
2453
|
+
}
|
|
2454
|
+
});
|
|
2455
|
+
}
|
|
2456
|
+
var TABLE_ALIGN_CSS = `
|
|
2457
|
+
th[data-align="left"], td[data-align="left"] { text-align: left; }
|
|
2458
|
+
th[data-align="center"], td[data-align="center"] { text-align: center; }
|
|
2459
|
+
th[data-align="right"], td[data-align="right"] { text-align: right; }
|
|
2460
|
+
`;
|
|
2461
|
+
|
|
2292
2462
|
// src/lib/markdown/renderer.ts
|
|
2293
2463
|
import markdownItSourceMap from "markdown-it-source-map";
|
|
2294
2464
|
import markdownItDeflist from "markdown-it-deflist";
|
|
@@ -2455,6 +2625,10 @@ function registerImageRule(md) {
|
|
|
2455
2625
|
if (srcIdx >= 0 && token.attrs) {
|
|
2456
2626
|
const src = token.attrs[srcIdx][1];
|
|
2457
2627
|
if (src) {
|
|
2628
|
+
const explicit = takeExplicitName(token);
|
|
2629
|
+
const named = explicit || nameFromSrc(src);
|
|
2630
|
+
if (named)
|
|
2631
|
+
token.attrSet("data-name", named);
|
|
2458
2632
|
const bucket = env ?? {};
|
|
2459
2633
|
(bucket.imageRefs ??= []).push(src);
|
|
2460
2634
|
const cls = token.attrGet("class");
|
|
@@ -2830,6 +3004,8 @@ function createMarkdownRenderer(customPlugins) {
|
|
|
2830
3004
|
const declaredMarkers = buildDeclaredMarkerRegistry(declaredMarkerSources);
|
|
2831
3005
|
md.use(plugin, { declaredMarkers });
|
|
2832
3006
|
md.use(gpPinScope);
|
|
3007
|
+
md.use(gpBlockNames);
|
|
3008
|
+
md.use(gpTableAlign);
|
|
2833
3009
|
registerImageRule(md);
|
|
2834
3010
|
if (customPlugins && customPlugins.length > 0) {
|
|
2835
3011
|
applyPlugins(md, customPlugins);
|
|
@@ -4001,15 +4177,6 @@ async function rollbackNpmPluginInstall(installed) {
|
|
|
4001
4177
|
// src/lib/extension-manager.ts
|
|
4002
4178
|
import { gt as semverGt, rcompare as semverRcompare } from "semver";
|
|
4003
4179
|
|
|
4004
|
-
// src/lib/slug.ts
|
|
4005
|
-
function slugify(name, fallback = "") {
|
|
4006
|
-
return name.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || fallback;
|
|
4007
|
-
}
|
|
4008
|
-
function prettify(slug) {
|
|
4009
|
-
const spaced = slug.replace(/[-_]+/g, " ").trim();
|
|
4010
|
-
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
4011
|
-
}
|
|
4012
|
-
|
|
4013
4180
|
// src/lib/project-gitignore.ts
|
|
4014
4181
|
import { readFile as readFile5, writeFile } from "node:fs/promises";
|
|
4015
4182
|
import path4 from "node:path";
|
|
@@ -6183,6 +6350,7 @@ var ruleRemoteUrls = "printsafe/no-remote-urls";
|
|
|
6183
6350
|
var ruleRiskyProps = "printsafe/no-risky-print-effects";
|
|
6184
6351
|
var ruleSyntax = "printsafe/syntax-error";
|
|
6185
6352
|
var rulePageContainment = "printsafe/page-containment";
|
|
6353
|
+
var rulePreviewCannotFollow = "printsafe/preview-cannot-follow";
|
|
6186
6354
|
function extractUrls(value) {
|
|
6187
6355
|
const urls = [];
|
|
6188
6356
|
const re = /url\(\s*(['"]?)(.*?)\1\s*\)/gi;
|
|
@@ -6328,6 +6496,62 @@ function clipsDescendants(decl) {
|
|
|
6328
6496
|
const v = decl.value.trim().toLowerCase();
|
|
6329
6497
|
return v !== "" && !isInertValue(p, v);
|
|
6330
6498
|
}
|
|
6499
|
+
var PREVIEW_AT_RULES = new Set(["media", "supports", "layer", "scope", "container", "document"]);
|
|
6500
|
+
var FORCED_BREAK_VALUE = /^(page|left|right|recto|verso)$/i;
|
|
6501
|
+
var LEGACY_FORCED_VALUE = /^(always|left|right)$/i;
|
|
6502
|
+
function previewBlindSpot(decl) {
|
|
6503
|
+
const prop = decl.prop.toLowerCase();
|
|
6504
|
+
const value = decl.value.trim();
|
|
6505
|
+
const written = `${decl.prop}: ${decl.value}`;
|
|
6506
|
+
const isBreak = prop === "break-before" || prop === "break-after";
|
|
6507
|
+
const isLegacy = prop === "page-break-before" || prop === "page-break-after";
|
|
6508
|
+
if (!isBreak && !isLegacy && prop !== "page")
|
|
6509
|
+
return null;
|
|
6510
|
+
const rules = [];
|
|
6511
|
+
for (let p = decl.parent;p; p = p.parent) {
|
|
6512
|
+
if (p.type === "rule")
|
|
6513
|
+
rules.push(p);
|
|
6514
|
+
else if (p.type === "atrule") {
|
|
6515
|
+
const name = p.name.toLowerCase();
|
|
6516
|
+
if (!PREVIEW_AT_RULES.has(name))
|
|
6517
|
+
return null;
|
|
6518
|
+
}
|
|
6519
|
+
}
|
|
6520
|
+
if (rules.length === 0)
|
|
6521
|
+
return null;
|
|
6522
|
+
const consequence = "the PDF honours it but the live preview does not show it, so the two page counts drift apart";
|
|
6523
|
+
if (isLegacy) {
|
|
6524
|
+
if (!LEGACY_FORCED_VALUE.test(value))
|
|
6525
|
+
return null;
|
|
6526
|
+
const modern = prop.replace("page-break-", "break-");
|
|
6527
|
+
const siblings = decl.parent.nodes ?? [];
|
|
6528
|
+
if (siblings.some((n) => n.type === "decl" && n.prop.toLowerCase() === modern))
|
|
6529
|
+
return null;
|
|
6530
|
+
const keyword = /^always$/i.test(value) ? "page" : value.toLowerCase();
|
|
6531
|
+
return `"${written}" is the legacy spelling, and the live preview reads only ${modern}: ${consequence}. Write "${modern}: ${keyword}".`;
|
|
6532
|
+
}
|
|
6533
|
+
const viaVar = /\bvar\(/i.test(value);
|
|
6534
|
+
if (isBreak && !viaVar && !FORCED_BREAK_VALUE.test(value))
|
|
6535
|
+
return null;
|
|
6536
|
+
if (prop === "page" && !viaVar && (value === "" || /^auto$/i.test(value)))
|
|
6537
|
+
return null;
|
|
6538
|
+
const what = prop === "page" ? "page assignment" : "forced break";
|
|
6539
|
+
if (viaVar) {
|
|
6540
|
+
return `The ${what} "${written}" uses var(): the live preview reads it only as a literal ${prop === "page" ? "page name" : "keyword (page, left, right, recto or verso)"}, so ${consequence}. Write the literal value.`;
|
|
6541
|
+
}
|
|
6542
|
+
const parent = decl.parent;
|
|
6543
|
+
if (rules.length >= 2 || parent.type === "atrule") {
|
|
6544
|
+
const outer = rules[rules.length - 1].selector;
|
|
6545
|
+
return `The ${what} "${written}" is inside a nested rule in "${outer}": the live preview reads only top-level rules and rules inside @media, @supports, @layer, @scope, @container and @document, so ${consequence}. Move it to a flat rule of its own.`;
|
|
6546
|
+
}
|
|
6547
|
+
let prev = decl.prev();
|
|
6548
|
+
while (prev && prev.type === "comment")
|
|
6549
|
+
prev = prev.prev();
|
|
6550
|
+
if (prev && (prev.type === "rule" || prev.type === "atrule" && prev.nodes)) {
|
|
6551
|
+
return `The ${what} "${written}" comes right after a nested rule in "${rules[0].selector}": the live preview's CSS reader drops the first declaration after a nested block, so ${consequence}. Move the declaration above the nested rule.`;
|
|
6552
|
+
}
|
|
6553
|
+
return null;
|
|
6554
|
+
}
|
|
6331
6555
|
function nodeLoc(node) {
|
|
6332
6556
|
return {
|
|
6333
6557
|
line: node.source?.start?.line ?? 1,
|
|
@@ -6440,6 +6664,12 @@ function checkCssRaw(css, from) {
|
|
|
6440
6664
|
});
|
|
6441
6665
|
}
|
|
6442
6666
|
});
|
|
6667
|
+
root.walkDecls((decl) => {
|
|
6668
|
+
const message = previewBlindSpot(decl);
|
|
6669
|
+
if (message) {
|
|
6670
|
+
warnings.push({ rule: rulePreviewCannotFollow, severity: "warning", message, ...nodeLoc(decl) });
|
|
6671
|
+
}
|
|
6672
|
+
});
|
|
6443
6673
|
root.walkRules((rule) => {
|
|
6444
6674
|
if (rule.parent?.type === "atrule") {
|
|
6445
6675
|
const at = rule.parent.name.toLowerCase();
|
|
@@ -7618,10 +7848,117 @@ registerCheck(check25);
|
|
|
7618
7848
|
|
|
7619
7849
|
// src/checks/source/layout-markers.ts
|
|
7620
7850
|
import { readFile as readFile18 } from "node:fs/promises";
|
|
7851
|
+
|
|
7852
|
+
// src/lib/unknown-classes.ts
|
|
7853
|
+
import postcss4 from "postcss";
|
|
7854
|
+
var EMITTED = new Set([
|
|
7855
|
+
"chapter",
|
|
7856
|
+
"spread",
|
|
7857
|
+
"page",
|
|
7858
|
+
"section",
|
|
7859
|
+
"footnotes",
|
|
7860
|
+
"footnotes-sep",
|
|
7861
|
+
"footnote-ref",
|
|
7862
|
+
"footnote-item",
|
|
7863
|
+
"footnote-backref"
|
|
7864
|
+
]);
|
|
7865
|
+
function classesInTokens(tokens) {
|
|
7866
|
+
const uses = new Map;
|
|
7867
|
+
const note = (token, line) => {
|
|
7868
|
+
const cls = token.attrs?.find(([key]) => key === "class")?.[1];
|
|
7869
|
+
if (!cls)
|
|
7870
|
+
return;
|
|
7871
|
+
for (const c of cls.split(/\s+/)) {
|
|
7872
|
+
if (!c || c.startsWith("gp-") || EMITTED.has(c) || /^chapter-\d+$/.test(c))
|
|
7873
|
+
continue;
|
|
7874
|
+
const seen = uses.get(c);
|
|
7875
|
+
if (seen)
|
|
7876
|
+
seen.count++;
|
|
7877
|
+
else
|
|
7878
|
+
uses.set(c, { line, count: 1 });
|
|
7879
|
+
}
|
|
7880
|
+
};
|
|
7881
|
+
for (const token of tokens) {
|
|
7882
|
+
const meta = token.meta;
|
|
7883
|
+
const line = token.map ? token.map[0] + 1 : typeof meta?.line === "number" ? meta.line : 0;
|
|
7884
|
+
note(token, line);
|
|
7885
|
+
for (const child of token.children ?? [])
|
|
7886
|
+
note(child, line);
|
|
7887
|
+
}
|
|
7888
|
+
return uses;
|
|
7889
|
+
}
|
|
7890
|
+
var CLASS_RE = /\.((?:[\w --]|\\.)+)/g;
|
|
7891
|
+
function addSelectorClasses(selector, into) {
|
|
7892
|
+
const bare = selector.replace(/\[(?:[^\]"']|"[^"]*"|'[^']*')*\]/g, "").replace(/"[^"]*"|'[^']*'/g, "");
|
|
7893
|
+
for (const m of bare.matchAll(CLASS_RE)) {
|
|
7894
|
+
const name = m[1].replace(/\\(.)/g, "$1");
|
|
7895
|
+
if (!/^\d/.test(name))
|
|
7896
|
+
into.add(name);
|
|
7897
|
+
}
|
|
7898
|
+
}
|
|
7899
|
+
function addCssClasses(css, into) {
|
|
7900
|
+
try {
|
|
7901
|
+
postcss4.parse(css).walk((node) => {
|
|
7902
|
+
if (node.type === "rule")
|
|
7903
|
+
addSelectorClasses(node.selector, into);
|
|
7904
|
+
else if (node.type === "atrule") {
|
|
7905
|
+
const at = node;
|
|
7906
|
+
if (/^(scope|container)$/i.test(at.name))
|
|
7907
|
+
addSelectorClasses(at.params, into);
|
|
7908
|
+
}
|
|
7909
|
+
});
|
|
7910
|
+
} catch {
|
|
7911
|
+
addSelectorClasses(css.replace(/\/\*[\s\S]*?\*\//g, ""), into);
|
|
7912
|
+
}
|
|
7913
|
+
}
|
|
7914
|
+
function editDistance3(a, b) {
|
|
7915
|
+
let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
7916
|
+
for (let i = 1;i <= a.length; i++) {
|
|
7917
|
+
const cur = [i];
|
|
7918
|
+
for (let j = 1;j <= b.length; j++) {
|
|
7919
|
+
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
7920
|
+
}
|
|
7921
|
+
prev = cur;
|
|
7922
|
+
}
|
|
7923
|
+
return prev[b.length];
|
|
7924
|
+
}
|
|
7925
|
+
function nearestKnownClass(cls, known) {
|
|
7926
|
+
const limit = cls.length >= 6 ? 2 : 1;
|
|
7927
|
+
let best = null;
|
|
7928
|
+
let bestDistance = limit + 1;
|
|
7929
|
+
for (const candidate of known) {
|
|
7930
|
+
if (Math.abs(candidate.length - cls.length) > limit)
|
|
7931
|
+
continue;
|
|
7932
|
+
const d = editDistance3(cls, candidate);
|
|
7933
|
+
if (d > 0 && d < bestDistance) {
|
|
7934
|
+
best = candidate;
|
|
7935
|
+
bestDistance = d;
|
|
7936
|
+
}
|
|
7937
|
+
}
|
|
7938
|
+
return best;
|
|
7939
|
+
}
|
|
7940
|
+
|
|
7941
|
+
// src/checks/source/layout-markers.ts
|
|
7942
|
+
async function collectKnownClasses(ctx, plugins) {
|
|
7943
|
+
const names = new Set;
|
|
7944
|
+
addCssClasses(MARKER_CSS, names);
|
|
7945
|
+
addCssClasses(GUTTERPRESS_CSS, names);
|
|
7946
|
+
for (const p of plugins)
|
|
7947
|
+
if (p.css)
|
|
7948
|
+
addCssClasses(p.css, names);
|
|
7949
|
+
const roots = [...ctx.cssFiles ?? [], ...plugins.flatMap((p) => p.styles ?? [])];
|
|
7950
|
+
const sheets = (await collectStyleDependencies(ctx.inputDir, roots)).filter((f) => /\.css$/i.test(f));
|
|
7951
|
+
for (const sheet of sheets) {
|
|
7952
|
+
try {
|
|
7953
|
+
addCssClasses(await readFile18(sheet, "utf8"), names);
|
|
7954
|
+
} catch {}
|
|
7955
|
+
}
|
|
7956
|
+
return names;
|
|
7957
|
+
}
|
|
7621
7958
|
var check26 = {
|
|
7622
7959
|
id: "source.markdown.layout-markers",
|
|
7623
7960
|
name: "Layout Markers",
|
|
7624
|
-
description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand, plus any unknown gp-* class",
|
|
7961
|
+
description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand, plus any unknown gp-* class and any other class no stylesheet mentions",
|
|
7625
7962
|
category: "source",
|
|
7626
7963
|
phase: "pre-build",
|
|
7627
7964
|
async run(ctx) {
|
|
@@ -7633,11 +7970,31 @@ var check26 = {
|
|
|
7633
7970
|
results.push(inspectionFailed(check26.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
|
|
7634
7971
|
});
|
|
7635
7972
|
const md = createMarkdownRenderer(plugins);
|
|
7973
|
+
let known;
|
|
7974
|
+
const knownClasses = () => known ??= collectKnownClasses(ctx, plugins);
|
|
7636
7975
|
for (const file of files) {
|
|
7637
7976
|
try {
|
|
7638
7977
|
const content = await readFile18(file, "utf8");
|
|
7639
7978
|
const env = {};
|
|
7640
|
-
md.
|
|
7979
|
+
const tokens = md.parse(content, env);
|
|
7980
|
+
md.renderer.render(tokens, md.options, env);
|
|
7981
|
+
const uses = classesInTokens(tokens);
|
|
7982
|
+
if (uses.size > 0) {
|
|
7983
|
+
const names = await knownClasses();
|
|
7984
|
+
const warnedLines = new Set((env.layoutWarnings ?? []).map((w) => w.line));
|
|
7985
|
+
for (const [cls, use] of uses) {
|
|
7986
|
+
if (names.has(cls) || warnedLines.has(use.line))
|
|
7987
|
+
continue;
|
|
7988
|
+
const guess = nearestKnownClass(cls, names);
|
|
7989
|
+
results.push(finding(check26.id, {
|
|
7990
|
+
severity: "warning",
|
|
7991
|
+
message: `Class ".${cls}" is not in any stylesheet (Gutterpress's own, the plugins', or this book's), so it styles nothing.` + (guess ? ` Did you mean ".${guess}"?` : "") + (use.count > 1 ? ` Used ${use.count} times in this file; the first is reported.` : ""),
|
|
7992
|
+
file,
|
|
7993
|
+
...use.line > 0 ? { line: use.line } : {},
|
|
7994
|
+
code: "unknown_class"
|
|
7995
|
+
}));
|
|
7996
|
+
}
|
|
7997
|
+
}
|
|
7641
7998
|
for (const w of env.layoutWarnings ?? []) {
|
|
7642
7999
|
results.push(finding(check26.id, {
|
|
7643
8000
|
severity: w.severity ?? "warning",
|
|
@@ -8612,6 +8969,8 @@ ${GUTTERPRESS_CSS.trim()}
|
|
|
8612
8969
|
}`,
|
|
8613
8970
|
pluginCss ? `/* extension css */
|
|
8614
8971
|
${pluginCss.trim()}` : null,
|
|
8972
|
+
`/* column alignment */
|
|
8973
|
+
${TABLE_ALIGN_CSS.trim()}`,
|
|
8615
8974
|
projectCss ? `/* project css */
|
|
8616
8975
|
${projectCss.trim()}` : null
|
|
8617
8976
|
].filter(Boolean).join(`
|
|
@@ -9723,7 +10082,7 @@ class PdfOutput {
|
|
|
9723
10082
|
const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
|
|
9724
10083
|
await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
|
|
9725
10084
|
log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
|
|
9726
|
-
const { buildNativePdf } = await import("./engine-
|
|
10085
|
+
const { buildNativePdf } = await import("./engine-wtnakejg.js");
|
|
9727
10086
|
const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
|
|
9728
10087
|
title: config.title,
|
|
9729
10088
|
author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
|
|
@@ -9974,8 +10333,10 @@ async function renderPreviewBook(inputPath, config, opts) {
|
|
|
9974
10333
|
}
|
|
9975
10334
|
});
|
|
9976
10335
|
}
|
|
9977
|
-
function injectPreviewScripts(html) {
|
|
9978
|
-
const
|
|
10336
|
+
function injectPreviewScripts(html, insetBleedPt = 0) {
|
|
10337
|
+
const meta = insetBleedPt > 0 ? ` <meta name="gp-inset-bleed" content="${insetBleedPt}">
|
|
10338
|
+
` : "";
|
|
10339
|
+
const scripts = meta + ` <script src="/engine/gutterpress-viewer.js"></script>
|
|
9979
10340
|
` + `<script src="/preview/scripts/preview-interface.js"></script>
|
|
9980
10341
|
` + `<script src="/preview/scripts/preview-bridge.js"></script>
|
|
9981
10342
|
`;
|
|
@@ -10009,7 +10370,7 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets, image
|
|
|
10009
10370
|
for (const ref of nextImageRefs)
|
|
10010
10371
|
imageRefs.add(ref);
|
|
10011
10372
|
}
|
|
10012
|
-
await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html), "utf-8");
|
|
10373
|
+
await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html, config.page?.bleed), "utf-8");
|
|
10013
10374
|
}
|
|
10014
10375
|
function isDotPathUnderRoot(candidatePath, root) {
|
|
10015
10376
|
const normalizedRoot = root.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
@@ -10947,7 +11308,7 @@ import { constants as FS, existsSync as existsSync12 } from "node:fs";
|
|
|
10947
11308
|
import path18 from "node:path";
|
|
10948
11309
|
|
|
10949
11310
|
// src/lib/page-size.ts
|
|
10950
|
-
import
|
|
11311
|
+
import postcss5 from "postcss";
|
|
10951
11312
|
import { Scalar as Scalar2 } from "yaml";
|
|
10952
11313
|
function isPositivePoints(value) {
|
|
10953
11314
|
return typeof value === "number" && Number.isFinite(value) && value > 0;
|