gutterpress 0.11.18-alpha.2 → 0.11.18-alpha.4

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.
Files changed (29) hide show
  1. package/dist/api/index.js +2 -2
  2. package/dist/{build-se062swz.js → build-63nwjhzf.js} +4 -4
  3. package/dist/{cli-qp6dtsh3.js → cli-36v78t6w.js} +1 -1
  4. package/dist/{cli-161z4n0s.js → cli-5x7s8bap.js} +2 -2
  5. package/dist/{cli-yqpnksaa.js → cli-88hddvbz.js} +1 -1
  6. package/dist/{cli-0w3961p5.js → cli-dmcy8kay.js} +400 -44
  7. package/dist/cli.js +12 -12
  8. package/dist/{doctor-tkbdx8c1.js → doctor-n1cwrwx2.js} +2 -2
  9. package/dist/{engine-jkf4pxz9.js → engine-6zfj0jte.js} +1 -1
  10. package/dist/{engine-7gfr16t8.js → engine-zw8626be.js} +2 -2
  11. package/dist/{ext-kg8m8vda.js → ext-ddfjfcg4.js} +4 -4
  12. package/dist/{gutterpress-viewer-spq1ge52.js → gutterpress-viewer-k4mpqjh0.js} +57 -29
  13. package/dist/{index-bb2d1ev8.js → index-1ergtg60.js} +401 -45
  14. package/dist/{index-t3zxy69d.js → index-jt552wtf.js} +2 -2
  15. package/dist/{index-3x23by5j.js → index-qbd874dh.js} +1 -1
  16. package/dist/index.js +3 -3
  17. package/dist/lib/markdown/block-names.d.ts +82 -0
  18. package/dist/lib/markdown/renderer.d.ts +3 -2
  19. package/dist/lib/markdown/table-align.d.ts +32 -0
  20. package/dist/lib/printsafe.d.ts +1 -0
  21. package/dist/lib/unknown-classes.d.ts +12 -0
  22. package/dist/{manifest.schema-8m82f37q.json → manifest.schema-5pzhscqh.json} +1 -1
  23. package/dist/{new-w7q0n21v.js → new-ede0f1nm.js} +4 -4
  24. package/dist/{preflight-m54qsjs7.js → preflight-b6c3k3qt.js} +4 -4
  25. package/dist/{preview-bg3wxqxc.js → preview-y2dxxbrk.js} +4 -4
  26. package/dist/{publish-waps63wf.js → publish-n60n0hm1.js} +4 -4
  27. package/dist/render.js +199 -28
  28. package/dist/{validate-c9d8d656.js → validate-2c7sfyxn.js} +4 -4
  29. package/package.json +1 -1
@@ -0,0 +1,82 @@
1
+ /**
2
+ * `data-name` — a stable hook for targeting ONE block from a stylesheet.
3
+ *
4
+ * A book's markdown holds content and semantic markers; its look lives in the
5
+ * plugin's and the book's CSS. What CSS needs, to restyle a single block
6
+ * without a markdown edit, is something on that block to select by. Class
7
+ * names are shared by every instance, an `id` is something the author must
8
+ * type, and position selectors break on any insertion. So every block this
9
+ * pipeline can name gets a `data-name`:
10
+ *
11
+ * [data-name="sporos"] { break-inside: avoid; }
12
+ * img[data-name="founder-house"] { float: right; width: 40%; }
13
+ *
14
+ * Plain HTML: a `data-*` attribute is standard, selected with the standard
15
+ * attribute selector, and nothing here is a behaviour a future browser
16
+ * feature would replace. The viewer never reads it; it exists for the
17
+ * author's CSS.
18
+ *
19
+ * WHAT GETS A NAME
20
+ *
21
+ * Marker-opened blocks (`.chapter`, `.spread`, `.page`, `.section`, every
22
+ * declared marker, `@columns` included, their variants too) take, in order:
23
+ * 1. the marker's explicit `name=` (core already emits it as `data-name`);
24
+ * 2. its `label=`, slugged;
25
+ * 3. the slug of the block's OWN first heading: the first heading whose
26
+ * innermost marker block is this one, so a `@page` that holds a
27
+ * `@section` does not borrow the section's title and the two never
28
+ * share a name;
29
+ * 4. nothing. A block with no explicit name, label or own heading stays
30
+ * unnamed rather than getting a position-based name that moves when
31
+ * a block is inserted.
32
+ * Headings take an explicit `{name=…}`, else the slug of their own text.
33
+ * Images take an explicit `{name=…}`, else the slug of the file's basename
34
+ * without its extension (see {@link nameFromSrc}, applied in images.ts).
35
+ *
36
+ * SLUGS: lower-case, accents folded to ASCII, apostrophes (' and ’) dropped,
37
+ * every run of other characters becomes one `-`, trimmed. `slug.ts`'s `slugify` plus a few letters NFKD
38
+ * does not decompose.
39
+ *
40
+ * COLLISIONS. Names are unique within one source file, because a file is
41
+ * rendered on its own (a book is the concatenation of its files):
42
+ * - blocks share ONE namespace, so `[data-name="x"]` is exactly one block
43
+ * in a file whatever its kind; headings have their own namespace, so a
44
+ * section and its title can both be `sporos`. Pair the attribute with an
45
+ * element (`h3[data-name="sporos"]`) or a class to say which;
46
+ * - a later duplicate gets `-2`, `-3`, … in document order. Explicit names
47
+ * are authored and never rewritten; they are reserved first, so a
48
+ * derived name steps around them;
49
+ * - images are NOT numbered: two images from the same file share a name,
50
+ * which is usually what a book wants.
51
+ * A book that needs a name unique across files scopes the selector by chapter
52
+ * (`#ch-06 [data-name="x"]`) or types `name=`.
53
+ *
54
+ * CONTINUATIONS. A `gp-continued` section (made by `@continue`) is the SAME
55
+ * block, so it keeps its original's `data-name` and carries the
56
+ * `gp-continued` class; `:not(.gp-continued)` picks the first piece. It does
57
+ * NOT repeat the `id` (an id names one element; see markers.js).
58
+ *
59
+ * NO `id`s ARE ADDED. Ids feed `target-counter()` and links; inventing them
60
+ * would change what a document links to.
61
+ *
62
+ * Runs last among the built-in core rules and before any loaded plugin's own
63
+ * rules, so it sees `markdown-it-attrs`' result and a plugin sees the names.
64
+ */
65
+ import type MarkdownIt from "markdown-it";
66
+ import type Token from "markdown-it/lib/token.mjs";
67
+ /** The `data-name` slug of `text`: `""` when nothing usable is left. */
68
+ export declare function nameSlug(text: string): string;
69
+ /**
70
+ * The image name for `src`: its file's basename without extension, slugged.
71
+ * `images/chapter-00/Founder%20House.png?v=2` is `founder-house`. A `data:`
72
+ * URI has no file name, so it has none.
73
+ */
74
+ export declare function nameFromSrc(src: string): string;
75
+ /**
76
+ * The author's explicit name on a token: `data-name`, or the `name` a
77
+ * `{name=…}` brace leaves behind (not a valid attribute on these elements, so
78
+ * it is moved to `data-name`). `""` when there is none.
79
+ */
80
+ export declare function takeExplicitName(token: Token): string;
81
+ /** Register the rule. Called by `createMarkdownRenderer` right after the marker plugin. */
82
+ export declare function gpBlockNames(md: MarkdownIt): void;
@@ -89,8 +89,9 @@ export interface GutterpressMarkerLabel {
89
89
  *
90
90
  * The close token has no meta. A `section: true` marker produces the core
91
91
  * `layout_section_open` / `layout_section_close` pair instead; its open token
92
- * carries `{ line, component, kind, variant, attrs }` (plus `continued: true`
93
- * on a `@continue` reopening) and a plain `@section`'s carries only `{ line }`.
92
+ * carries `{ line, component, kind, variant, attrs }` and a plain `@section`'s
93
+ * carries only `{ line }`; a `@continue` reopening of either adds
94
+ * `continued: true`.
94
95
  *
95
96
  * See `markers.js`'s header comment for a worked example and
96
97
  * `buildDeclaredMarkerRegistry` for the full validation/resolution contract
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Table column alignment as `data-align`, not an inline style.
3
+ *
4
+ * markdown-it writes `|:--|--:|` as `style="text-align:left"` on every cell.
5
+ * An inline style beats every stylesheet, so a book could never restyle a
6
+ * column without `!important`. The same fact as a plain attribute,
7
+ * `data-align="left|center|right"`, is applied by {@link TABLE_ALIGN_CSS} and
8
+ * loses to the book's own rules.
9
+ *
10
+ * Only markdown-it's own exact `text-align:` value is converted. An author's
11
+ * own `{style=…}` on a cell is left untouched.
12
+ */
13
+ import type MarkdownIt from "markdown-it";
14
+ export declare function gpTableAlign(md: MarkdownIt): void;
15
+ /**
16
+ * The rules that apply `data-align`. `assemble.ts` injects them UNLAYERED,
17
+ * just before the book's own stylesheets, and with the natural specificity of
18
+ * an element plus an attribute (0,1,1). That puts them where the inline style
19
+ * they replace sat against the two things that used to lose to it:
20
+ *
21
+ * - a plugin's default (`th { text-align: left }`, always in a cascade
22
+ * layer): unlayered beats layered, so a markdown `:-:` still wins;
23
+ * - a book's generic element rule (`th { text-align: left }`, specificity
24
+ * 0,0,1): the attribute selector outranks it, so the same book prints the
25
+ * same. Measured on the user guide and the design guide, whose `th` rule
26
+ * centred headers only until a layered version of these rules let it win.
27
+ *
28
+ * Unlike an inline style they can be overridden without `!important`: any
29
+ * book rule at least as specific that comes later wins, e.g.
30
+ * `td[data-align="right"] { text-align: center }` or `.stats td { … }`.
31
+ */
32
+ export declare const TABLE_ALIGN_CSS = "\nth[data-align=\"left\"], td[data-align=\"left\"] { text-align: left; }\nth[data-align=\"center\"], td[data-align=\"center\"] { text-align: center; }\nth[data-align=\"right\"], td[data-align=\"right\"] { text-align: right; }\n";
@@ -2,6 +2,7 @@ export declare const ruleRemoteUrls = "printsafe/no-remote-urls";
2
2
  export declare const ruleRiskyProps = "printsafe/no-risky-print-effects";
3
3
  export declare const ruleSyntax = "printsafe/syntax-error";
4
4
  export declare const rulePageContainment = "printsafe/page-containment";
5
+ export declare const rulePreviewCannotFollow = "printsafe/preview-cannot-follow";
5
6
  export interface PrintSafeWarning {
6
7
  rule: string;
7
8
  severity: "error" | "warning";
@@ -0,0 +1,12 @@
1
+ import type Token from "markdown-it/lib/token.mjs";
2
+ /** One class as the markdown uses it: where it first appears, and how often. */
3
+ export interface ClassUse {
4
+ line: number;
5
+ count: number;
6
+ }
7
+ /** The author-written classes the tokens carry (`gp-*` and pipeline-emitted ones left out), keyed by class. */
8
+ export declare function classesInTokens(tokens: readonly Token[]): Map<string, ClassUse>;
9
+ /** Add every class a selector in `css` names to `into`. Unparseable CSS contributes what a text scan finds. */
10
+ export declare function addCssClasses(css: string, into: Set<string>): void;
11
+ /** The known class `cls` is most likely a typo of, or null: one edit for short names, two for longer ones. */
12
+ export declare function nearestKnownClass(cls: string, known: ReadonlySet<string>): string | null;
@@ -243,7 +243,7 @@
243
243
  },
244
244
  "rules": {
245
245
  "type": "object",
246
- "description": "Per-rule level overrides, keyed by rule id: a printsafe rule (printsafe/no-risky-print-effects, printsafe/page-containment, printsafe/no-remote-urls) or a check id such as source.accessibility.heading-order. \"off\" silences the rule; \"warn\" / \"error\" set its severity. An id that matches no rule is ignored. To silence one site instead, put a `gutterpress-disable-next-line <rule-id> -- reason` comment (CSS `/* */` or markdown `<!-- -->`) on the line above it.",
246
+ "description": "Per-rule level overrides, keyed by rule id: a printsafe rule (printsafe/no-risky-print-effects, printsafe/page-containment, printsafe/preview-cannot-follow, printsafe/no-remote-urls) or a check id such as source.accessibility.heading-order. \"off\" silences the rule; \"warn\" / \"error\" set its severity. An id that matches no rule is ignored. To silence one site instead, put a `gutterpress-disable-next-line <rule-id> -- reason` comment (CSS `/* */` or markdown `<!-- -->`) on the line above it.",
247
247
  "additionalProperties": { "enum": ["off", "warn", "error"] },
248
248
  "examples": [{ "printsafe/no-risky-print-effects": "off" }]
249
249
  }
@@ -7,17 +7,17 @@ import {
7
7
  TARGET_IDS,
8
8
  scaffoldExtension,
9
9
  scaffoldProject
10
- } from "./cli-0w3961p5.js";
10
+ } from "./cli-dmcy8kay.js";
11
11
  import {
12
12
  resolveGhostscript
13
- } from "./cli-qp6dtsh3.js";
13
+ } from "./cli-36v78t6w.js";
14
14
  import {
15
15
  UsageError,
16
16
  rejectExtraPositionals,
17
17
  rejectUnknownFlags
18
- } from "./cli-yqpnksaa.js";
18
+ } from "./cli-88hddvbz.js";
19
19
  import"./cli-ht1bcqdd.js";
20
- import"./cli-161z4n0s.js";
20
+ import"./cli-5x7s8bap.js";
21
21
  import {
22
22
  EXIT_CODES
23
23
  } from "./cli-46ycxe6r.js";
@@ -2,17 +2,17 @@ import {
2
2
  executeValidation,
3
3
  publishTargetFor,
4
4
  reportMissingTools
5
- } from "./cli-0w3961p5.js";
5
+ } from "./cli-dmcy8kay.js";
6
6
  import {
7
7
  log
8
- } from "./cli-qp6dtsh3.js";
8
+ } from "./cli-36v78t6w.js";
9
9
  import {
10
10
  UsageError,
11
11
  rejectExtraPositionals,
12
12
  rejectUnknownFlags
13
- } from "./cli-yqpnksaa.js";
13
+ } from "./cli-88hddvbz.js";
14
14
  import"./cli-ht1bcqdd.js";
15
- import"./cli-161z4n0s.js";
15
+ import"./cli-5x7s8bap.js";
16
16
  import {
17
17
  BuildError,
18
18
  EXIT_CODES
@@ -7,10 +7,10 @@ import {
7
7
  runBuild,
8
8
  splitOutPath,
9
9
  startPreviewServer
10
- } from "./cli-0w3961p5.js";
10
+ } from "./cli-dmcy8kay.js";
11
11
  import {
12
12
  log
13
- } from "./cli-qp6dtsh3.js";
13
+ } from "./cli-36v78t6w.js";
14
14
  import {
15
15
  UsageError,
16
16
  parseFormat,
@@ -18,9 +18,9 @@ import {
18
18
  rejectExtraPositionals,
19
19
  rejectUnknownFlags,
20
20
  resolvePort
21
- } from "./cli-yqpnksaa.js";
21
+ } from "./cli-88hddvbz.js";
22
22
  import"./cli-ht1bcqdd.js";
23
- import"./cli-161z4n0s.js";
23
+ import"./cli-5x7s8bap.js";
24
24
  import {
25
25
  BuildError
26
26
  } from "./cli-46ycxe6r.js";
@@ -11,18 +11,18 @@ import {
11
11
  publishProviderFor,
12
12
  resolvePublishFormat,
13
13
  runPublish
14
- } from "./cli-0w3961p5.js";
14
+ } from "./cli-dmcy8kay.js";
15
15
  import {
16
16
  FileTokenStore,
17
17
  log
18
- } from "./cli-qp6dtsh3.js";
18
+ } from "./cli-36v78t6w.js";
19
19
  import {
20
20
  UsageError,
21
21
  rejectExtraPositionals,
22
22
  rejectUnknownFlags
23
- } from "./cli-yqpnksaa.js";
23
+ } from "./cli-88hddvbz.js";
24
24
  import"./cli-ht1bcqdd.js";
25
- import"./cli-161z4n0s.js";
25
+ import"./cli-5x7s8bap.js";
26
26
  import {
27
27
  EXIT_CODES
28
28
  } from "./cli-46ycxe6r.js";
package/dist/render.js CHANGED
@@ -86,10 +86,11 @@ function parseMarkerLine(line, declaredWords) {
86
86
  const kind = head.slice(1);
87
87
  if (!KNOWN_KINDS.includes(kind) && !(declaredWords && declaredWords.has(kind)))
88
88
  return null;
89
- if (kind === "page-break" || kind === "column-break" || kind === "continue" || kind.startsWith("end-")) {
89
+ if (kind === "continue" || kind.startsWith("end-")) {
90
90
  return { kind, name: null, attrs: {} };
91
91
  }
92
- const body = tokens.slice(1);
92
+ const isBreak = kind === "page-break" || kind === "column-break";
93
+ const body = isBreak ? tokens.slice(1).filter((token) => !isBareToken(token)) : tokens.slice(1);
93
94
  const hasExplicitAttrsOrShorthand = body.some((token) => token.includes("=") || token.startsWith(".") || token.startsWith("#"));
94
95
  const bareTokens = body.filter(isBareToken);
95
96
  let name = null;
@@ -157,6 +158,8 @@ function parseMarkerLine(line, declaredWords) {
157
158
  }
158
159
  if (classes.length)
159
160
  attrs.class = classes.join(" ");
161
+ if (isBreak)
162
+ return { kind, name: null, attrs };
160
163
  const marker = { kind, name, attrs };
161
164
  if (hasAmbiguousBareToken)
162
165
  marker.__ambiguousBareToken = true;
@@ -339,6 +342,11 @@ function attachDataAttrs(token, kind, name, attrs) {
339
342
  token.attrSet(k.startsWith("data-") ? k : `data-${k}`, v);
340
343
  }
341
344
  }
345
+ function attachBreak(token, baseClass, attrs = {}) {
346
+ addClasses(token, baseClass, attrs.class);
347
+ token.attrSet("aria-hidden", "true");
348
+ attachDataAttrs(token, "break", null, attrs);
349
+ }
342
350
  var DECLARED_NAME_RE = /^[a-z][a-z0-9-]*$/;
343
351
  function validateDeclaredMarkerName(name, pluginName) {
344
352
  if (!DECLARED_NAME_RE.test(name)) {
@@ -895,7 +903,11 @@ function plugin(md, pluginOptions = {}) {
895
903
  attachDataAttrs(t, component.kind, component.variant, meta.attrs || {});
896
904
  else
897
905
  attachDataAttrs(t, "section", meta.name, meta.attrs || {});
898
- t.meta = component ? { line: meta.__line, ...component, ...meta.__continued ? { continued: true } : {} } : { line: meta.__line };
906
+ t.meta = {
907
+ line: meta.__line,
908
+ ...component || {},
909
+ ...meta.__continued ? { continued: true } : {}
910
+ };
899
911
  stack.open({
900
912
  kind: "section",
901
913
  classes: meta.attrs && meta.attrs.class || "",
@@ -993,12 +1005,12 @@ function plugin(md, pluginOptions = {}) {
993
1005
  drainDeclaredFrames(keep);
994
1006
  const contMeta = {
995
1007
  name: section.meta.name,
996
- attrs: { ...section.meta.attrs || {} }
1008
+ attrs: { ...section.meta.attrs || {} },
1009
+ __continued: true
997
1010
  };
998
- if (section.meta.component) {
1011
+ delete contMeta.attrs.id;
1012
+ if (section.meta.component)
999
1013
  contMeta.__component = section.meta.component;
1000
- contMeta.__continued = true;
1001
- }
1002
1014
  contMeta.__line = line;
1003
1015
  const cls = (contMeta.attrs.class || "").split(/\s+/).filter(Boolean);
1004
1016
  if (!cls.includes("gp-continued"))
@@ -1011,16 +1023,14 @@ function plugin(md, pluginOptions = {}) {
1011
1023
  if (kind === "page-break") {
1012
1024
  const t = new state.Token("layout_page_break", "div", 0);
1013
1025
  t.meta = { line };
1014
- t.attrSet("class", "gp-page-break");
1015
- t.attrSet("aria-hidden", "true");
1026
+ attachBreak(t, "gp-page-break", meta.attrs);
1016
1027
  out.push(t);
1017
1028
  continue;
1018
1029
  }
1019
1030
  if (kind === "column-break") {
1020
1031
  const t = new state.Token("layout_column_break", "div", 0);
1021
1032
  t.meta = { line };
1022
- t.attrSet("class", "gp-column-break");
1023
- t.attrSet("aria-hidden", "true");
1033
+ attachBreak(t, "gp-column-break", meta.attrs);
1024
1034
  out.push(t);
1025
1035
  continue;
1026
1036
  }
@@ -1084,24 +1094,10 @@ function plugin(md, pluginOptions = {}) {
1084
1094
  }
1085
1095
  });
1086
1096
  }
1087
- md.renderer.rules.layout_page_break = (tokens, idx) => {
1088
- const cls = tokens[idx].attrGet("class") || "gp-page-break";
1089
- const rangeAttr = tokens[idx].attrGet("data-source-range");
1090
- const rangeHtml = rangeAttr ? ` data-source-range="${escapeAttr(rangeAttr)}"` : "";
1091
- const chapterAttr = tokens[idx].attrGet("data-chapter-src");
1092
- const chapterHtml = chapterAttr ? ` data-chapter-src="${escapeAttr(chapterAttr)}"` : "";
1093
- return `<div class="${escapeAttr(cls)}" aria-hidden="true"${rangeHtml}${chapterHtml}></div>
1094
- `;
1095
- };
1096
- md.renderer.rules.layout_column_break = (tokens, idx) => {
1097
- const cls = tokens[idx].attrGet("class") || "gp-column-break";
1098
- const rangeAttr = tokens[idx].attrGet("data-source-range");
1099
- const rangeHtml = rangeAttr ? ` data-source-range="${escapeAttr(rangeAttr)}"` : "";
1100
- const chapterAttr = tokens[idx].attrGet("data-chapter-src");
1101
- const chapterHtml = chapterAttr ? ` data-chapter-src="${escapeAttr(chapterAttr)}"` : "";
1102
- return `<div class="${escapeAttr(cls)}" aria-hidden="true"${rangeHtml}${chapterHtml}></div>
1097
+ const renderBreak = (tokens, idx, options2, env, self) => `<div${self.renderAttrs(tokens[idx])}></div>
1103
1098
  `;
1104
- };
1099
+ md.renderer.rules.layout_page_break = renderBreak;
1100
+ md.renderer.rules.layout_column_break = renderBreak;
1105
1101
  md.renderer.rules.layout_marker = () => "";
1106
1102
  }
1107
1103
  var NO_MARGIN_BOXES = [
@@ -1453,6 +1449,27 @@ var GP_CLASSES = new Set([
1453
1449
  "gp-flush"
1454
1450
  ]);
1455
1451
 
1452
+ // src/lib/markdown/table-align.ts
1453
+ var ALIGN = /^text-align:(left|center|right)$/;
1454
+ function gpTableAlign(md) {
1455
+ md.core.ruler.after("block", "gp_table_align", (state) => {
1456
+ for (const token of state.tokens) {
1457
+ if (token.type !== "th_open" && token.type !== "td_open")
1458
+ continue;
1459
+ const match = ALIGN.exec(token.attrGet("style") ?? "");
1460
+ if (!match)
1461
+ continue;
1462
+ token.attrs = (token.attrs ?? []).filter(([key]) => key !== "style");
1463
+ token.attrSet("data-align", match[1]);
1464
+ }
1465
+ });
1466
+ }
1467
+ var TABLE_ALIGN_CSS = `
1468
+ th[data-align="left"], td[data-align="left"] { text-align: left; }
1469
+ th[data-align="center"], td[data-align="center"] { text-align: center; }
1470
+ th[data-align="right"], td[data-align="right"] { text-align: right; }
1471
+ `;
1472
+
1456
1473
  // src/lib/markdown/chapter-id.ts
1457
1474
  function canonicalChapterId(p) {
1458
1475
  let s = String(p).replace(/\\/g, "/");
@@ -1598,6 +1615,152 @@ function gpPinScope(md) {
1598
1615
  });
1599
1616
  }
1600
1617
 
1618
+ // src/lib/slug.ts
1619
+ function slugify(name, fallback = "") {
1620
+ return name.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || fallback;
1621
+ }
1622
+
1623
+ // src/lib/markdown/block-names.ts
1624
+ var BLOCK_OPEN = /^layout_(chapter|spread|page|section|component)_open$/;
1625
+ var BLOCK_CLOSE = /^layout_(chapter|spread|page|section|component)_close$/;
1626
+ var FOLDED = {
1627
+ ß: "ss",
1628
+ æ: "ae",
1629
+ œ: "oe",
1630
+ ø: "o",
1631
+ đ: "d",
1632
+ ð: "d",
1633
+ þ: "th",
1634
+ ł: "l",
1635
+ ı: "i"
1636
+ };
1637
+ function nameSlug(text) {
1638
+ return slugify(text.replace(/['’]/g, "").replace(/[ßæœøđðþłı]/gi, (c) => FOLDED[c.toLowerCase()] ?? c));
1639
+ }
1640
+ function nameFromSrc(src) {
1641
+ if (/^data:/i.test(src))
1642
+ return "";
1643
+ const path = src.split(/[?#]/, 1)[0] ?? "";
1644
+ const base = path.split(/[\\/]/).pop() ?? "";
1645
+ let decoded = base;
1646
+ try {
1647
+ decoded = decodeURIComponent(base);
1648
+ } catch {}
1649
+ return nameSlug(decoded.replace(/\.[^.]*$/, ""));
1650
+ }
1651
+ function takeExplicitName(token) {
1652
+ const data = token.attrGet("data-name");
1653
+ const plain = token.attrGet("name");
1654
+ if (plain !== null && token.attrs)
1655
+ token.attrs = token.attrs.filter(([k]) => k !== "name");
1656
+ if (data)
1657
+ return data;
1658
+ if (plain) {
1659
+ token.attrSet("data-name", plain);
1660
+ return plain;
1661
+ }
1662
+ return "";
1663
+ }
1664
+ function inlineText2(inline) {
1665
+ if (!inline || inline.type !== "inline")
1666
+ return "";
1667
+ const parts = [];
1668
+ for (const child of inline.children ?? []) {
1669
+ if (child.type === "text" || child.type === "text_special" || child.type === "code_inline" || child.type === "image") {
1670
+ parts.push(child.content);
1671
+ } else if (child.type === "softbreak" || child.type === "hardbreak") {
1672
+ parts.push(" ");
1673
+ }
1674
+ }
1675
+ return parts.join("");
1676
+ }
1677
+ function claim(used, slug) {
1678
+ let name = slug;
1679
+ for (let n = 2;used.has(name); n++)
1680
+ name = `${slug}-${n}`;
1681
+ used.add(name);
1682
+ return name;
1683
+ }
1684
+ function nameBlocksAndHeadings(tokens) {
1685
+ const blocks = [];
1686
+ const open = [];
1687
+ const headings = [];
1688
+ const headingSlugs = new Map;
1689
+ for (let i = 0;i < tokens.length; i++) {
1690
+ const tok = tokens[i];
1691
+ const opened = BLOCK_OPEN.exec(tok.type);
1692
+ if (opened) {
1693
+ const block = { token: tok, type: opened[1], heading: "" };
1694
+ blocks.push(block);
1695
+ open.push(block);
1696
+ continue;
1697
+ }
1698
+ const closed = BLOCK_CLOSE.exec(tok.type);
1699
+ if (closed) {
1700
+ for (let j = open.length - 1;j >= 0; j--) {
1701
+ if (open[j].type === closed[1]) {
1702
+ open.splice(j, 1);
1703
+ break;
1704
+ }
1705
+ }
1706
+ continue;
1707
+ }
1708
+ if (tok.type === "heading_open") {
1709
+ const slug = nameSlug(inlineText2(tokens[i + 1]));
1710
+ headings.push(tok);
1711
+ headingSlugs.set(tok, slug);
1712
+ const owner = open[open.length - 1];
1713
+ if (owner && !owner.heading)
1714
+ owner.heading = slug;
1715
+ }
1716
+ }
1717
+ const usedBlocks = new Set;
1718
+ const usedHeadings = new Set;
1719
+ const unnamedBlocks = new Set;
1720
+ for (const block of blocks) {
1721
+ const explicit = block.token.attrGet("data-name");
1722
+ if (explicit)
1723
+ usedBlocks.add(explicit);
1724
+ else
1725
+ unnamedBlocks.add(block);
1726
+ }
1727
+ const unnamedHeadings = [];
1728
+ for (const heading of headings) {
1729
+ const explicit = takeExplicitName(heading);
1730
+ if (explicit)
1731
+ usedHeadings.add(explicit);
1732
+ else
1733
+ unnamedHeadings.push(heading);
1734
+ }
1735
+ let previousSection = null;
1736
+ for (const block of blocks) {
1737
+ const { token } = block;
1738
+ if (block.type === "section") {
1739
+ if (token.meta?.continued && previousSection) {
1740
+ const inherited = previousSection.token.attrGet("data-name");
1741
+ if (inherited)
1742
+ token.attrSet("data-name", inherited);
1743
+ previousSection = block;
1744
+ continue;
1745
+ }
1746
+ previousSection = block;
1747
+ }
1748
+ if (!unnamedBlocks.has(block))
1749
+ continue;
1750
+ const slug = nameSlug(token.attrGet("data-label") ?? "") || block.heading;
1751
+ if (slug)
1752
+ token.attrSet("data-name", claim(usedBlocks, slug));
1753
+ }
1754
+ for (const heading of unnamedHeadings) {
1755
+ const slug = headingSlugs.get(heading);
1756
+ if (slug)
1757
+ heading.attrSet("data-name", claim(usedHeadings, slug));
1758
+ }
1759
+ }
1760
+ function gpBlockNames(md) {
1761
+ md.core.ruler.push("gp_block_names", (state) => nameBlocksAndHeadings(state.tokens));
1762
+ }
1763
+
1601
1764
  // src/lib/markdown/renderer.ts
1602
1765
  import markdownItSourceMap from "markdown-it-source-map";
1603
1766
  import markdownItDeflist from "markdown-it-deflist";
@@ -1764,6 +1927,10 @@ function registerImageRule(md) {
1764
1927
  if (srcIdx >= 0 && token.attrs) {
1765
1928
  const src = token.attrs[srcIdx][1];
1766
1929
  if (src) {
1930
+ const explicit = takeExplicitName(token);
1931
+ const named = explicit || nameFromSrc(src);
1932
+ if (named)
1933
+ token.attrSet("data-name", named);
1767
1934
  const bucket = env ?? {};
1768
1935
  (bucket.imageRefs ??= []).push(src);
1769
1936
  const cls = token.attrGet("class");
@@ -2139,6 +2306,8 @@ function createMarkdownRenderer(customPlugins) {
2139
2306
  const declaredMarkers = buildDeclaredMarkerRegistry(declaredMarkerSources);
2140
2307
  md.use(plugin, { declaredMarkers });
2141
2308
  md.use(gpPinScope);
2309
+ md.use(gpBlockNames);
2310
+ md.use(gpTableAlign);
2142
2311
  registerImageRule(md);
2143
2312
  if (customPlugins && customPlugins.length > 0) {
2144
2313
  applyPlugins(md, customPlugins);
@@ -2224,6 +2393,8 @@ ${GUTTERPRESS_CSS.trim()}
2224
2393
  }`,
2225
2394
  pluginCss ? `/* extension css */
2226
2395
  ${pluginCss.trim()}` : null,
2396
+ `/* column alignment */
2397
+ ${TABLE_ALIGN_CSS.trim()}`,
2227
2398
  projectCss ? `/* project css */
2228
2399
  ${projectCss.trim()}` : null
2229
2400
  ].filter(Boolean).join(`
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  executeAndReport
3
- } from "./cli-0w3961p5.js";
3
+ } from "./cli-dmcy8kay.js";
4
4
  import {
5
5
  log
6
- } from "./cli-qp6dtsh3.js";
6
+ } from "./cli-36v78t6w.js";
7
7
  import {
8
8
  UsageError,
9
9
  rejectExtraPositionals,
10
10
  rejectUnknownFlags
11
- } from "./cli-yqpnksaa.js";
11
+ } from "./cli-88hddvbz.js";
12
12
  import"./cli-ht1bcqdd.js";
13
- import"./cli-161z4n0s.js";
13
+ import"./cli-5x7s8bap.js";
14
14
  import {
15
15
  BuildError,
16
16
  EXIT_CODES
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gutterpress",
3
- "version": "0.11.18-alpha.2",
3
+ "version": "0.11.18-alpha.4",
4
4
  "description": "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
5
5
  "author": "itlackey",
6
6
  "license": "MPL-2.0",