gutterpress 0.11.16-alpha.1 → 0.11.16-alpha.3

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.js CHANGED
@@ -128,10 +128,10 @@ import {
128
128
  testRemoteAccess,
129
129
  updateExtensions,
130
130
  validateProjectExtensions
131
- } from "../index-azdfgy3t.js";
131
+ } from "../index-ncxfknbv.js";
132
132
  import {
133
133
  BuildError
134
- } from "../index-eawjxgkn.js";
134
+ } from "../index-w1qk3tap.js";
135
135
  import {
136
136
  DEFAULT_PRINT_OPTS,
137
137
  assertMilestone,
@@ -1,18 +1,18 @@
1
1
  import {
2
2
  runBuild,
3
3
  splitOutPath
4
- } from "./cli-sq0awkbf.js";
4
+ } from "./cli-x9bb628z.js";
5
5
  import {
6
6
  log
7
- } from "./cli-6tfc4ed6.js";
7
+ } from "./cli-14j4aqtc.js";
8
8
  import {
9
9
  UsageError,
10
10
  parseFormat,
11
11
  parsePdfxFlavor,
12
12
  rejectExtraPositionals,
13
13
  rejectUnknownFlags
14
- } from "./cli-p8xwsknv.js";
15
- import"./cli-d55z7rtk.js";
14
+ } from "./cli-w9qc8fem.js";
15
+ import"./cli-8597az31.js";
16
16
  import {
17
17
  BuildError
18
18
  } from "./cli-46ycxe6r.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "./cli-p8xwsknv.js";
3
+ } from "./cli-w9qc8fem.js";
4
4
  import {
5
5
  execCapture,
6
6
  findTool,
@@ -1151,7 +1151,7 @@ var package_default = "./package-9rv69pxm.json";
1151
1151
  var README_default = "./README-vsnsc1b3.md";
1152
1152
 
1153
1153
  // src/assets/extension-templates/plugin/plugin.js.tpl
1154
- var plugin_js_default = "./plugin.js-z492w01r.tpl";
1154
+ var plugin_js_default = "./plugin.js-m4a7m3t4.tpl";
1155
1155
 
1156
1156
  // src/assets/extension-templates/plugin/styles/plugin.css
1157
1157
  var plugin_default = "./plugin-spbcem4j.css";
@@ -4,7 +4,7 @@ import {
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "gutterpress",
7
- version: "0.11.16-alpha.1",
7
+ version: "0.11.16-alpha.3",
8
8
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
9
9
  author: "itlackey",
10
10
  license: "MPL-2.0",
@@ -13,17 +13,17 @@ import {
13
13
  stampCreator,
14
14
  stripAnnotations,
15
15
  warn
16
- } from "./cli-6tfc4ed6.js";
16
+ } from "./cli-14j4aqtc.js";
17
17
  import {
18
18
  DEBOUNCE,
19
19
  UsageError,
20
20
  resolvePort
21
- } from "./cli-p8xwsknv.js";
21
+ } from "./cli-w9qc8fem.js";
22
22
  import {
23
23
  MARGIN_BOX_IGNORED_PROPERTIES,
24
24
  getAssetPath,
25
25
  getAssetsDir
26
- } from "./cli-d55z7rtk.js";
26
+ } from "./cli-8597az31.js";
27
27
  import {
28
28
  BuildError
29
29
  } from "./cli-46ycxe6r.js";
@@ -3148,6 +3148,16 @@ function validateDeclaredMarkerName(name, pluginName) {
3148
3148
  }
3149
3149
  var TAG_NAME_RE = /^[a-z][a-z0-9-]*$/;
3150
3150
  function resolveContainerShape(name, pluginName, decl) {
3151
+ if (decl.section !== undefined && typeof decl.section !== "boolean") {
3152
+ throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`section\` that is not a boolean.`);
3153
+ }
3154
+ if (decl.section) {
3155
+ for (const field of ["tag", "label", "autoCloseAt"]) {
3156
+ if (decl[field] !== undefined) {
3157
+ throw new Error(`Plugin "${pluginName}"'s marker "@${name}" sets \`section: true\` and \`${field}\` — a section ` + `marker is a core @section, so \`${field}\` does not apply. Remove one of them.`);
3158
+ }
3159
+ }
3160
+ }
3151
3161
  const tag = decl.tag !== undefined ? decl.tag : "div";
3152
3162
  if (typeof tag !== "string" || !TAG_NAME_RE.test(tag)) {
3153
3163
  throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has an invalid \`tag\` (${JSON.stringify(decl.tag)}) — ` + `must be a lower-case HTML tag name.`);
@@ -3203,7 +3213,7 @@ function resolveContainerShape(name, pluginName, decl) {
3203
3213
  throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`validate\` that is not a function.`);
3204
3214
  }
3205
3215
  const check20 = decl.validate ? { validate: decl.validate, validateOwner: pluginName } : {};
3206
- return { tag, classBase, variants, label, autoCloseAtEof, ...check20 };
3216
+ return { tag, classBase, variants, label, autoCloseAtEof, ...decl.section ? { section: true } : {}, ...check20 };
3207
3217
  }
3208
3218
  function resolveMarkerDeclaration(name, rawDecl, rawRegistry, originOf) {
3209
3219
  const pluginName = originOf.get(name);
@@ -3279,12 +3289,12 @@ function declaredKindWords(declaredMarkers) {
3279
3289
  }
3280
3290
  return words;
3281
3291
  }
3282
- function nearestDeclaredMarkerName(word, declaredMarkers) {
3292
+ function nearestDeclaredMarkerName(word, names) {
3283
3293
  const w = word.toLowerCase();
3284
3294
  const threshold = Math.max(2, Math.floor(w.length / 3));
3285
3295
  let best = null;
3286
3296
  let bestDistance = Infinity;
3287
- for (const known of declaredMarkers.keys()) {
3297
+ for (const known of names) {
3288
3298
  const d = editDistance(w, known);
3289
3299
  if (d < bestDistance) {
3290
3300
  bestDistance = d;
@@ -3312,13 +3322,23 @@ function scanForUnknownDeclaredMarkers(state, declaredMarkers) {
3312
3322
  const word = m[1];
3313
3323
  if (KNOWN_KINDS.includes(word) || declaredMarkers.has(word))
3314
3324
  continue;
3315
- const suggestion = nearestDeclaredMarkerName(word, declaredMarkers);
3325
+ const suggestion = nearestDeclaredMarkerName(word, declaredMarkers.keys());
3316
3326
  if (!suggestion)
3317
3327
  continue;
3318
3328
  warn2(state.env, tok.map[0] + i + 1, "unknown_marker", `Unknown marker "@${word}". Did you mean "@${suggestion}"?`, null);
3319
3329
  }
3320
3330
  }
3321
3331
  }
3332
+ function warnUnknownVariant(env, meta, decl) {
3333
+ const word = meta.name;
3334
+ if (!word)
3335
+ return;
3336
+ const variants = Object.keys(decl.variants || {});
3337
+ if (variants.includes(word))
3338
+ return;
3339
+ const near = variants.find((v) => v.toLowerCase() === word.toLowerCase()) || (word.length >= 3 ? nearestDeclaredMarkerName(word, variants) : null);
3340
+ warn2(env, meta.__line, "unknown_variant", `"${word}" is not a variant of @${meta.kind}, so it adds no styling. ` + (variants.length ? `Variants: ${variants.join(", ")}.` : `@${meta.kind} has no variants.`) + (near ? ` Did you mean "${near}"?` : ""), meta);
3341
+ }
3322
3342
  function plugin(md, pluginOptions = {}) {
3323
3343
  const options = {
3324
3344
  preferPagesInSpreads: false,
@@ -3428,7 +3448,15 @@ function plugin(md, pluginOptions = {}) {
3428
3448
  function openDeclaredMarker(meta, decl) {
3429
3449
  const t = new state.Token("layout_component_open", decl.tag, 1);
3430
3450
  const variant = meta.name || decl.presetVariant || null;
3431
- t.meta = { line: meta.__line, component: meta.kind, variant, attrs: meta.attrs || {}, labelled: false };
3451
+ warnUnknownVariant(state.env, meta, decl);
3452
+ t.meta = {
3453
+ line: meta.__line,
3454
+ component: meta.kind,
3455
+ kind: decl.baseKind,
3456
+ variant,
3457
+ attrs: meta.attrs || {},
3458
+ labelled: false
3459
+ };
3432
3460
  const variantClass = variant && decl.variants && decl.variants[variant] || "";
3433
3461
  const baseClass = [decl.classBase, variantClass].filter(Boolean).join(" ");
3434
3462
  addClasses(t, baseClass, meta.attrs && meta.attrs.class ? meta.attrs.class : "");
@@ -3536,16 +3564,34 @@ function plugin(md, pluginOptions = {}) {
3536
3564
  warn2(state.env, meta.__line || 0, "page_outside_spread", "@page used outside of a spread; allowed, but spreads are recommended for deliberate grouping.", meta);
3537
3565
  }
3538
3566
  }
3567
+ function sectionMetaFor(meta, decl) {
3568
+ const attrs = meta.attrs || {};
3569
+ const variant = meta.name || decl.presetVariant || null;
3570
+ warnUnknownVariant(state.env, meta, decl);
3571
+ const variantClass = variant && decl.variants && decl.variants[variant] || "";
3572
+ const cls = [decl.classBase, variantClass, attrs.class].filter(Boolean).join(" ");
3573
+ return {
3574
+ kind: "section",
3575
+ name: null,
3576
+ attrs: cls ? { ...attrs, class: cls } : attrs,
3577
+ __line: meta.__line,
3578
+ __component: { component: meta.kind, kind: decl.baseKind, variant, attrs }
3579
+ };
3580
+ }
3539
3581
  function openSection(meta) {
3540
3582
  const t = new state.Token("layout_section_open", "div", 1);
3583
+ const component = meta.__component || null;
3541
3584
  addClasses(t, "section", meta.attrs && meta.attrs.class ? meta.attrs.class : "");
3542
- attachDataAttrs(t, "section", meta.name, meta.attrs || {});
3543
- t.meta = { line: meta.__line };
3585
+ if (component)
3586
+ attachDataAttrs(t, component.kind, component.variant, meta.attrs || {});
3587
+ else
3588
+ attachDataAttrs(t, "section", meta.name, meta.attrs || {});
3589
+ t.meta = component ? { line: meta.__line, ...component, ...meta.__continued ? { continued: true } : {} } : { line: meta.__line };
3544
3590
  stack.open({
3545
3591
  kind: "section",
3546
3592
  classes: meta.attrs && meta.attrs.class || "",
3547
3593
  sawContent: false,
3548
- meta: { name: meta.name || null, attrs: { ...meta.attrs || {} } },
3594
+ meta: { name: meta.name || null, attrs: { ...meta.attrs || {} }, component },
3549
3595
  openToken: t
3550
3596
  }, t);
3551
3597
  }
@@ -3591,9 +3637,21 @@ function plugin(md, pluginOptions = {}) {
3591
3637
  out.push(tok);
3592
3638
  continue;
3593
3639
  }
3594
- const meta = tok.meta || {};
3595
- const kind = meta.kind;
3640
+ let meta = tok.meta || {};
3641
+ let kind = meta.kind;
3596
3642
  const line = meta.__line || 0;
3643
+ if (declaredMarkers) {
3644
+ const closing = kind.startsWith("end-");
3645
+ const sectionDecl = declaredMarkers.get(closing ? kind.slice(4) : kind);
3646
+ if (sectionDecl && sectionDecl.section) {
3647
+ if (closing) {
3648
+ kind = "end-section";
3649
+ } else {
3650
+ meta = sectionMetaFor(meta, sectionDecl);
3651
+ kind = "section";
3652
+ }
3653
+ }
3654
+ }
3597
3655
  if (kind === "chapter") {
3598
3656
  drainDeclaredFrames();
3599
3657
  stack.close("chapter");
@@ -3637,6 +3695,10 @@ function plugin(md, pluginOptions = {}) {
3637
3695
  name: section.meta.name,
3638
3696
  attrs: { ...section.meta.attrs || {} }
3639
3697
  };
3698
+ if (section.meta.component) {
3699
+ contMeta.__component = section.meta.component;
3700
+ contMeta.__continued = true;
3701
+ }
3640
3702
  contMeta.__line = line;
3641
3703
  const cls = (contMeta.attrs.class || "").split(/\s+/).filter(Boolean);
3642
3704
  if (!cls.includes("gp-continued"))
@@ -3710,7 +3772,8 @@ function plugin(md, pluginOptions = {}) {
3710
3772
  const tokens = state.tokens;
3711
3773
  for (let i = 0;i < tokens.length; i++) {
3712
3774
  const tok = tokens[i];
3713
- if (tok.type !== "layout_component_open")
3775
+ const isSection = tok.type === "layout_section_open" && tok.meta && tok.meta.component && !tok.meta.continued;
3776
+ if (tok.type !== "layout_component_open" && !isSection)
3714
3777
  continue;
3715
3778
  const decl = declaredMarkers.get(tok.meta.component);
3716
3779
  if (!decl || !decl.validate)
@@ -8139,7 +8202,7 @@ class PdfOutput {
8139
8202
  const rawPdf = pdfxMode ? path8.join(stage, "raw.pdf") : path8.resolve(pdfFile);
8140
8203
  await fsp2.mkdir(path8.dirname(path8.resolve(pdfFile)), { recursive: true });
8141
8204
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
8142
- const { buildNativePdf } = await import("./engine-nv2xddnk.js");
8205
+ const { buildNativePdf } = await import("./engine-6jnxqfsm.js");
8143
8206
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
8144
8207
  title: config.title,
8145
8208
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  UsageError,
7
7
  package_default,
8
8
  rejectUnknownFlags
9
- } from "./cli-p8xwsknv.js";
9
+ } from "./cli-w9qc8fem.js";
10
10
  import {
11
11
  EXIT_CODES
12
12
  } from "./cli-46ycxe6r.js";
@@ -18,14 +18,14 @@ import {
18
18
  import { defineCommand, parseArgs, runMain } from "citty";
19
19
  import { statSync } from "node:fs";
20
20
  var SUBCOMMANDS = {
21
- new: () => import("./new-daeh9p9p.js").then((m) => m.default),
22
- preview: () => import("./preview-00298rvf.js").then((m) => m.default),
23
- build: () => import("./build-46bb205t.js").then((m) => m.default),
24
- publish: () => import("./publish-jn9t18mr.js").then((m) => m.default),
25
- validate: () => import("./validate-cm1m5bbf.js").then((m) => m.default),
26
- preflight: () => import("./preflight-bfvw2qe5.js").then((m) => m.default),
27
- doctor: () => import("./doctor-tjatz81j.js").then((m) => m.default),
28
- ext: () => import("./ext-ph0770mx.js").then((m) => m.default)
21
+ new: () => import("./new-n43vrq56.js").then((m) => m.default),
22
+ preview: () => import("./preview-v66m0vff.js").then((m) => m.default),
23
+ build: () => import("./build-tse0tq0t.js").then((m) => m.default),
24
+ publish: () => import("./publish-xgmbeerq.js").then((m) => m.default),
25
+ validate: () => import("./validate-281t5k0g.js").then((m) => m.default),
26
+ preflight: () => import("./preflight-qnp6rd1d.js").then((m) => m.default),
27
+ doctor: () => import("./doctor-s6npnst2.js").then((m) => m.default),
28
+ ext: () => import("./ext-tf6x156j.js").then((m) => m.default)
29
29
  };
30
30
  var VERSION = package_default.version;
31
31
  var main = defineCommand({
@@ -50,7 +50,7 @@ async function preflightRequiredInvocations(rawArgs) {
50
50
  const [command, ...commandArgs] = rawArgs;
51
51
  try {
52
52
  if (command === "new") {
53
- const { newArgs } = await import("./new-daeh9p9p.js");
53
+ const { newArgs } = await import("./new-n43vrq56.js");
54
54
  rejectUnknownFlags(commandArgs, newArgs, "new");
55
55
  const parsed = parseArgs(commandArgs, {
56
56
  ...newArgs,
@@ -62,7 +62,7 @@ async function preflightRequiredInvocations(rawArgs) {
62
62
  return;
63
63
  }
64
64
  if (command === "preflight") {
65
- const { preflightArgs } = await import("./preflight-bfvw2qe5.js");
65
+ const { preflightArgs } = await import("./preflight-qnp6rd1d.js");
66
66
  rejectUnknownFlags(commandArgs, preflightArgs, "preflight");
67
67
  const parsed = parseArgs(commandArgs, {
68
68
  ...preflightArgs,
@@ -82,7 +82,7 @@ async function preflightRequiredInvocations(rawArgs) {
82
82
  }
83
83
  if (subcommand.startsWith("-"))
84
84
  return;
85
- const { EXT_SUBCOMMANDS, extAddArgs, extRemoveArgs, extEnableArgs, extDisableArgs } = await import("./ext-ph0770mx.js");
85
+ const { EXT_SUBCOMMANDS, extAddArgs, extRemoveArgs, extEnableArgs, extDisableArgs } = await import("./ext-tf6x156j.js");
86
86
  if (!EXT_SUBCOMMANDS.includes(subcommand)) {
87
87
  throw new UsageError(`gutterpress ext: unknown command "${subcommand}"`);
88
88
  }
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  getSystemDiagnostics,
3
3
  log
4
- } from "./cli-6tfc4ed6.js";
4
+ } from "./cli-14j4aqtc.js";
5
5
  import {
6
6
  UsageError,
7
7
  rejectExtraPositionals,
8
8
  rejectUnknownFlags
9
- } from "./cli-p8xwsknv.js";
9
+ } from "./cli-w9qc8fem.js";
10
10
  import"./cli-46ycxe6r.js";
11
11
  import"./cli-7fkhgtfn.js";
12
12
  import"./cli-37x76zdn.js";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  build
3
- } from "./index-9ydc9q9q.js";
3
+ } from "./index-zb48zwcq.js";
4
4
  import {
5
5
  BuildError
6
- } from "./index-eawjxgkn.js";
6
+ } from "./index-w1qk3tap.js";
7
7
  import"./index-rzm1phg0.js";
8
8
  import"./index-37x76zdn.js";
9
9
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  build
3
- } from "./cli-d55z7rtk.js";
3
+ } from "./cli-8597az31.js";
4
4
  import {
5
5
  BuildError
6
6
  } from "./cli-46ycxe6r.js";
@@ -12,16 +12,16 @@ import {
12
12
  searchNpmExtensions,
13
13
  setExtensionEnabled,
14
14
  updateExtensions
15
- } from "./cli-sq0awkbf.js";
16
- import"./cli-6tfc4ed6.js";
15
+ } from "./cli-x9bb628z.js";
16
+ import"./cli-14j4aqtc.js";
17
17
  import {
18
18
  UsageError,
19
19
  exitForUsage,
20
20
  rejectExtraPositionals,
21
21
  rejectUnknownFlags,
22
22
  resolveProjectDir
23
- } from "./cli-p8xwsknv.js";
24
- import"./cli-d55z7rtk.js";
23
+ } from "./cli-w9qc8fem.js";
24
+ import"./cli-8597az31.js";
25
25
  import {
26
26
  EXIT_CODES
27
27
  } from "./cli-46ycxe6r.js";
@@ -4,7 +4,7 @@ import {
4
4
  MARGIN_BOX_IGNORED_PROPERTIES,
5
5
  getAssetPath,
6
6
  getAssetsDir
7
- } from "./index-eawjxgkn.js";
7
+ } from "./index-w1qk3tap.js";
8
8
  import {
9
9
  INSTALL_HINTS,
10
10
  execCapture,
@@ -1033,6 +1033,16 @@ function validateDeclaredMarkerName(name, pluginName) {
1033
1033
  }
1034
1034
  var TAG_NAME_RE = /^[a-z][a-z0-9-]*$/;
1035
1035
  function resolveContainerShape(name, pluginName, decl) {
1036
+ if (decl.section !== undefined && typeof decl.section !== "boolean") {
1037
+ throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`section\` that is not a boolean.`);
1038
+ }
1039
+ if (decl.section) {
1040
+ for (const field of ["tag", "label", "autoCloseAt"]) {
1041
+ if (decl[field] !== undefined) {
1042
+ throw new Error(`Plugin "${pluginName}"'s marker "@${name}" sets \`section: true\` and \`${field}\` — a section ` + `marker is a core @section, so \`${field}\` does not apply. Remove one of them.`);
1043
+ }
1044
+ }
1045
+ }
1036
1046
  const tag = decl.tag !== undefined ? decl.tag : "div";
1037
1047
  if (typeof tag !== "string" || !TAG_NAME_RE.test(tag)) {
1038
1048
  throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has an invalid \`tag\` (${JSON.stringify(decl.tag)}) — ` + `must be a lower-case HTML tag name.`);
@@ -1088,7 +1098,7 @@ function resolveContainerShape(name, pluginName, decl) {
1088
1098
  throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`validate\` that is not a function.`);
1089
1099
  }
1090
1100
  const check = decl.validate ? { validate: decl.validate, validateOwner: pluginName } : {};
1091
- return { tag, classBase, variants, label, autoCloseAtEof, ...check };
1101
+ return { tag, classBase, variants, label, autoCloseAtEof, ...decl.section ? { section: true } : {}, ...check };
1092
1102
  }
1093
1103
  function resolveMarkerDeclaration(name, rawDecl, rawRegistry, originOf) {
1094
1104
  const pluginName = originOf.get(name);
@@ -1164,12 +1174,12 @@ function declaredKindWords(declaredMarkers) {
1164
1174
  }
1165
1175
  return words;
1166
1176
  }
1167
- function nearestDeclaredMarkerName(word, declaredMarkers) {
1177
+ function nearestDeclaredMarkerName(word, names) {
1168
1178
  const w = word.toLowerCase();
1169
1179
  const threshold = Math.max(2, Math.floor(w.length / 3));
1170
1180
  let best = null;
1171
1181
  let bestDistance = Infinity;
1172
- for (const known of declaredMarkers.keys()) {
1182
+ for (const known of names) {
1173
1183
  const d = editDistance(w, known);
1174
1184
  if (d < bestDistance) {
1175
1185
  bestDistance = d;
@@ -1197,13 +1207,23 @@ function scanForUnknownDeclaredMarkers(state, declaredMarkers) {
1197
1207
  const word = m[1];
1198
1208
  if (KNOWN_KINDS.includes(word) || declaredMarkers.has(word))
1199
1209
  continue;
1200
- const suggestion = nearestDeclaredMarkerName(word, declaredMarkers);
1210
+ const suggestion = nearestDeclaredMarkerName(word, declaredMarkers.keys());
1201
1211
  if (!suggestion)
1202
1212
  continue;
1203
1213
  warn(state.env, tok.map[0] + i + 1, "unknown_marker", `Unknown marker "@${word}". Did you mean "@${suggestion}"?`, null);
1204
1214
  }
1205
1215
  }
1206
1216
  }
1217
+ function warnUnknownVariant(env, meta, decl) {
1218
+ const word = meta.name;
1219
+ if (!word)
1220
+ return;
1221
+ const variants = Object.keys(decl.variants || {});
1222
+ if (variants.includes(word))
1223
+ return;
1224
+ const near = variants.find((v) => v.toLowerCase() === word.toLowerCase()) || (word.length >= 3 ? nearestDeclaredMarkerName(word, variants) : null);
1225
+ warn(env, meta.__line, "unknown_variant", `"${word}" is not a variant of @${meta.kind}, so it adds no styling. ` + (variants.length ? `Variants: ${variants.join(", ")}.` : `@${meta.kind} has no variants.`) + (near ? ` Did you mean "${near}"?` : ""), meta);
1226
+ }
1207
1227
  function plugin(md, pluginOptions = {}) {
1208
1228
  const options = {
1209
1229
  preferPagesInSpreads: false,
@@ -1313,7 +1333,15 @@ function plugin(md, pluginOptions = {}) {
1313
1333
  function openDeclaredMarker(meta, decl) {
1314
1334
  const t = new state.Token("layout_component_open", decl.tag, 1);
1315
1335
  const variant = meta.name || decl.presetVariant || null;
1316
- t.meta = { line: meta.__line, component: meta.kind, variant, attrs: meta.attrs || {}, labelled: false };
1336
+ warnUnknownVariant(state.env, meta, decl);
1337
+ t.meta = {
1338
+ line: meta.__line,
1339
+ component: meta.kind,
1340
+ kind: decl.baseKind,
1341
+ variant,
1342
+ attrs: meta.attrs || {},
1343
+ labelled: false
1344
+ };
1317
1345
  const variantClass = variant && decl.variants && decl.variants[variant] || "";
1318
1346
  const baseClass = [decl.classBase, variantClass].filter(Boolean).join(" ");
1319
1347
  addClasses(t, baseClass, meta.attrs && meta.attrs.class ? meta.attrs.class : "");
@@ -1421,16 +1449,34 @@ function plugin(md, pluginOptions = {}) {
1421
1449
  warn(state.env, meta.__line || 0, "page_outside_spread", "@page used outside of a spread; allowed, but spreads are recommended for deliberate grouping.", meta);
1422
1450
  }
1423
1451
  }
1452
+ function sectionMetaFor(meta, decl) {
1453
+ const attrs = meta.attrs || {};
1454
+ const variant = meta.name || decl.presetVariant || null;
1455
+ warnUnknownVariant(state.env, meta, decl);
1456
+ const variantClass = variant && decl.variants && decl.variants[variant] || "";
1457
+ const cls = [decl.classBase, variantClass, attrs.class].filter(Boolean).join(" ");
1458
+ return {
1459
+ kind: "section",
1460
+ name: null,
1461
+ attrs: cls ? { ...attrs, class: cls } : attrs,
1462
+ __line: meta.__line,
1463
+ __component: { component: meta.kind, kind: decl.baseKind, variant, attrs }
1464
+ };
1465
+ }
1424
1466
  function openSection(meta) {
1425
1467
  const t = new state.Token("layout_section_open", "div", 1);
1468
+ const component = meta.__component || null;
1426
1469
  addClasses(t, "section", meta.attrs && meta.attrs.class ? meta.attrs.class : "");
1427
- attachDataAttrs(t, "section", meta.name, meta.attrs || {});
1428
- t.meta = { line: meta.__line };
1470
+ if (component)
1471
+ attachDataAttrs(t, component.kind, component.variant, meta.attrs || {});
1472
+ else
1473
+ attachDataAttrs(t, "section", meta.name, meta.attrs || {});
1474
+ t.meta = component ? { line: meta.__line, ...component, ...meta.__continued ? { continued: true } : {} } : { line: meta.__line };
1429
1475
  stack.open({
1430
1476
  kind: "section",
1431
1477
  classes: meta.attrs && meta.attrs.class || "",
1432
1478
  sawContent: false,
1433
- meta: { name: meta.name || null, attrs: { ...meta.attrs || {} } },
1479
+ meta: { name: meta.name || null, attrs: { ...meta.attrs || {} }, component },
1434
1480
  openToken: t
1435
1481
  }, t);
1436
1482
  }
@@ -1476,9 +1522,21 @@ function plugin(md, pluginOptions = {}) {
1476
1522
  out.push(tok);
1477
1523
  continue;
1478
1524
  }
1479
- const meta = tok.meta || {};
1480
- const kind = meta.kind;
1525
+ let meta = tok.meta || {};
1526
+ let kind = meta.kind;
1481
1527
  const line = meta.__line || 0;
1528
+ if (declaredMarkers) {
1529
+ const closing = kind.startsWith("end-");
1530
+ const sectionDecl = declaredMarkers.get(closing ? kind.slice(4) : kind);
1531
+ if (sectionDecl && sectionDecl.section) {
1532
+ if (closing) {
1533
+ kind = "end-section";
1534
+ } else {
1535
+ meta = sectionMetaFor(meta, sectionDecl);
1536
+ kind = "section";
1537
+ }
1538
+ }
1539
+ }
1482
1540
  if (kind === "chapter") {
1483
1541
  drainDeclaredFrames();
1484
1542
  stack.close("chapter");
@@ -1522,6 +1580,10 @@ function plugin(md, pluginOptions = {}) {
1522
1580
  name: section.meta.name,
1523
1581
  attrs: { ...section.meta.attrs || {} }
1524
1582
  };
1583
+ if (section.meta.component) {
1584
+ contMeta.__component = section.meta.component;
1585
+ contMeta.__continued = true;
1586
+ }
1525
1587
  contMeta.__line = line;
1526
1588
  const cls = (contMeta.attrs.class || "").split(/\s+/).filter(Boolean);
1527
1589
  if (!cls.includes("gp-continued"))
@@ -1595,7 +1657,8 @@ function plugin(md, pluginOptions = {}) {
1595
1657
  const tokens = state.tokens;
1596
1658
  for (let i = 0;i < tokens.length; i++) {
1597
1659
  const tok = tokens[i];
1598
- if (tok.type !== "layout_component_open")
1660
+ const isSection = tok.type === "layout_section_open" && tok.meta && tok.meta.component && !tok.meta.continued;
1661
+ if (tok.type !== "layout_component_open" && !isSection)
1599
1662
  continue;
1600
1663
  const decl = declaredMarkers.get(tok.meta.component);
1601
1664
  if (!decl || !decl.validate)
@@ -4014,7 +4077,7 @@ import git from "isomorphic-git";
4014
4077
  // package.json
4015
4078
  var package_default = {
4016
4079
  name: "gutterpress",
4017
- version: "0.11.16-alpha.1",
4080
+ version: "0.11.16-alpha.3",
4018
4081
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
4019
4082
  author: "itlackey",
4020
4083
  license: "MPL-2.0",
@@ -8629,7 +8692,7 @@ class PdfOutput {
8629
8692
  const rawPdf = pdfxMode ? path8.join(stage, "raw.pdf") : path8.resolve(pdfFile);
8630
8693
  await fsp2.mkdir(path8.dirname(path8.resolve(pdfFile)), { recursive: true });
8631
8694
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
8632
- const { buildNativePdf } = await import("./engine-szx34415.js");
8695
+ const { buildNativePdf } = await import("./engine-5b92gk8n.js");
8633
8696
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
8634
8697
  title: config.title,
8635
8698
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -12123,16 +12186,27 @@ async function declaredMarkers(entry, projectDir) {
12123
12186
  async function collectLibrary(projectDir) {
12124
12187
  const groups = [];
12125
12188
  const components = [];
12189
+ const loaded = [];
12126
12190
  for (const entry of await listProjectExtensions(projectDir)) {
12127
12191
  if (!entry.enabled)
12128
12192
  continue;
12129
12193
  const source = { kind: "extension", ref: entry.use, name: entry.label };
12130
12194
  const folder = await declaredSnippetsFolder(entry);
12131
12195
  const snippets2 = folder ? (await scanSnippetFiles(path20.join(entry.dir, folder))).map((file) => ({ ...file, source })) : [];
12132
- for (const [name, decl] of Object.entries(await declaredMarkers(entry, projectDir) ?? {})) {
12133
- if (decl.deprecated !== undefined)
12196
+ loaded.push({ entry, source, folder, snippets: snippets2, markers: await declaredMarkers(entry, projectDir) });
12197
+ }
12198
+ let registry;
12199
+ try {
12200
+ registry = buildDeclaredMarkerRegistry(loaded.flatMap((l) => l.markers ? [{ pluginName: l.entry.label, markers: l.markers }] : []));
12201
+ } catch {
12202
+ registry = undefined;
12203
+ }
12204
+ for (const { entry, source, folder, snippets: snippets2, markers } of loaded) {
12205
+ for (const [name, decl] of Object.entries(registry ? markers ?? {} : {})) {
12206
+ const resolved = registry.get(name);
12207
+ if (!resolved || resolved.deprecated !== undefined)
12134
12208
  continue;
12135
- components.push({ name, source });
12209
+ components.push({ name, source, variants: Object.keys(resolved.variants ?? {}) });
12136
12210
  if (!entry.dir)
12137
12211
  continue;
12138
12212
  const own = typeof decl.snippet === "string" && decl.snippet.trim() ? decl.snippet.trim() : null;
@@ -71,7 +71,7 @@ var package_default = "./package-9rv69pxm.json";
71
71
  var README_default = "./README-vsnsc1b3.md";
72
72
 
73
73
  // src/assets/extension-templates/plugin/plugin.js.tpl
74
- var plugin_js_default = "./plugin.js-z492w01r.tpl";
74
+ var plugin_js_default = "./plugin.js-m4a7m3t4.tpl";
75
75
 
76
76
  // src/assets/extension-templates/plugin/styles/plugin.css
77
77
  var plugin_default = "./plugin-spbcem4j.css";
@@ -3,7 +3,7 @@ import {
3
3
  isIgnoredMarginBoxProperty,
4
4
  marginBoxAlign,
5
5
  marginBoxRectPt
6
- } from "./index-eawjxgkn.js";
6
+ } from "./index-w1qk3tap.js";
7
7
  import {
8
8
  launchChromium
9
9
  } from "./index-rzm1phg0.js";
package/dist/index.js CHANGED
@@ -145,13 +145,13 @@ import {
145
145
  testRemoteAccess,
146
146
  updateExtensions,
147
147
  validateProjectExtensions
148
- } from "./index-azdfgy3t.js";
148
+ } from "./index-ncxfknbv.js";
149
149
  import {
150
150
  BUILD_DIAGNOSTIC_CODES
151
- } from "./index-9ydc9q9q.js";
151
+ } from "./index-zb48zwcq.js";
152
152
  import {
153
153
  BuildError
154
- } from "./index-eawjxgkn.js";
154
+ } from "./index-w1qk3tap.js";
155
155
  import {
156
156
  DEFAULT_PRINT_OPTS,
157
157
  assertMilestone,
@@ -29,6 +29,9 @@ export type ReadText = (relPath: string) => Promise<string>;
29
29
  * `severity`) and `component_validate_failed` (the validator threw or
30
30
  * returned something unusable).
31
31
  *
32
+ * `unknown_variant` is emitted for a declared marker (container or
33
+ * `section: true`) whose bare word is not one of its declared `variants`.
34
+ *
32
35
  * There is deliberately no `section_without_page`: a @section with no open
33
36
  * @page is valid authoring (17/17 false positives across two real books).
34
37
  */
@@ -62,13 +62,36 @@ export interface GutterpressMarkerLabel {
62
62
  * One entry in a plugin's declared `markers` table (#240 — "declarative
63
63
  * container components in core"). Exactly one of three shapes:
64
64
  *
65
- * - a CONTAINER: `tag`/`class`/`variants`/`label`/`autoCloseAt`, any/all
65
+ * - a CONTAINER: `section`/`tag`/`class`/`variants`/`label`/`autoCloseAt`, any/all
66
66
  * optional (a bare `{}` is a valid, if pointless, `<div>` wrapper);
67
67
  * - an ALIAS: `alias` (another declared name) + optional `preset`;
68
68
  * - a DEPRECATION: `deprecated` (a human-readable retirement message) —
69
69
  * wins over every other field on the same entry, per
70
70
  * `markers.js`'s `resolveMarkerDeclaration`.
71
71
  *
72
+ * A container may instead set `section: true` (below) to be a core `@section`.
73
+ *
74
+ * TOKEN CONTRACT (public and stable). A declared container renders as a
75
+ * `layout_component_open` / `layout_component_close` token pair, so a plugin
76
+ * can "declare, then transform": declare the marker so core parses it
77
+ * (autocomplete, closing, warnings, source lines), then rewrite the tokens
78
+ * between the pair with an ordinary markdown-it core rule. The open token's
79
+ * `meta` is
80
+ *
81
+ * { line, component, kind, variant, attrs, labelled }
82
+ *
83
+ * - `line` the marker's 1-based line
84
+ * - `component` the marker name as typed (an alias's own name)
85
+ * - `kind` the resolved base marker name (an alias's target)
86
+ * - `variant` the bare word after the marker (or an alias's preset), or null
87
+ * - `attrs` the marker's attributes, as authored
88
+ * - `labelled` true when core injected a label `html_block` right after the open token
89
+ *
90
+ * The close token has no meta. A `section: true` marker produces the core
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 }`.
94
+ *
72
95
  * See `markers.js`'s header comment for a worked example and
73
96
  * `buildDeclaredMarkerRegistry` for the full validation/resolution contract
74
97
  * (collision rules, alias indirection, the auto-derived `@end-<name>`
@@ -78,6 +101,17 @@ export interface GutterpressMarkerLabel {
78
101
  * plugin input.
79
102
  */
80
103
  export interface GutterpressMarkerDeclaration {
104
+ /**
105
+ * This marker IS a core `@section`: `@npc-stat wirephreak .extra` renders
106
+ * exactly like `@section .<class> .<variants.wirephreak> .extra` (same
107
+ * classes, same order), closes like one (at the next `@section` / `@page` /
108
+ * `@chapter` / `@spread`, or `@end-<name>`, which acts as `@end-section`),
109
+ * and `@continue` reopens it with the same classes. The variant word is a
110
+ * variant, not a section name: it emits `data-<name>="<variant>"` (the
111
+ * alias target's name for an alias), never `data-section`. Cannot be combined
112
+ * with `tag`, `label` or `autoCloseAt`. Aliases of a section marker inherit it.
113
+ */
114
+ section?: boolean;
81
115
  /** Wrapper element tag. Defaults to `"div"`. */
82
116
  tag?: string;
83
117
  /** Base class(es) on the wrapper, e.g. `"dc-alert"`. */
@@ -118,7 +152,8 @@ export interface GutterpressMarkerDeclaration {
118
152
  * (validate, Problems panel, build, preview). Receives a plain description
119
153
  * of the component's content — see {@link GutterpressComponent} — and
120
154
  * returns its problems. Must be synchronous; ignored on an alias (which
121
- * uses its target's) and on a deprecated entry.
155
+ * uses its target's) and on a deprecated entry. Works on `section: true`
156
+ * markers too (a `@continue` continuation is not validated on its own).
122
157
  */
123
158
  validate?: (component: GutterpressComponent) => GutterpressComponentProblems;
124
159
  }
@@ -83,6 +83,10 @@ export interface MarkerComponent {
83
83
  source: Extract<SnippetSource, {
84
84
  kind: "extension";
85
85
  }>;
86
+ /** The variant names the marker accepts as its bare word (`@name <variant>`),
87
+ * as the renderer resolves them — an alias lists its target's. A variant
88
+ * only adds a class, so one snippet serves them all. */
89
+ variants: string[];
86
90
  /** The winning example snippet's body (book, then extension, then core). */
87
91
  snippet?: string;
88
92
  }
@@ -7,16 +7,16 @@ import {
7
7
  TARGET_IDS,
8
8
  scaffoldExtension,
9
9
  scaffoldProject
10
- } from "./cli-sq0awkbf.js";
10
+ } from "./cli-x9bb628z.js";
11
11
  import {
12
12
  resolveGhostscript
13
- } from "./cli-6tfc4ed6.js";
13
+ } from "./cli-14j4aqtc.js";
14
14
  import {
15
15
  UsageError,
16
16
  rejectExtraPositionals,
17
17
  rejectUnknownFlags
18
- } from "./cli-p8xwsknv.js";
19
- import"./cli-d55z7rtk.js";
18
+ } from "./cli-w9qc8fem.js";
19
+ import"./cli-8597az31.js";
20
20
  import {
21
21
  EXIT_CODES
22
22
  } from "./cli-46ycxe6r.js";
@@ -62,6 +62,7 @@ const PREFIX = "{{PREFIX}}";
62
62
  * @end-term-box
63
63
  *
64
64
  * Fields, all optional:
65
+ * section true makes the marker a core @section (no tag/label/autoCloseAt)
65
66
  * tag wrapper element (default "div")
66
67
  * class base class on the wrapper
67
68
  * variants extra classes keyed by the marker's bare word
@@ -2,16 +2,16 @@ import {
2
2
  executeValidation,
3
3
  publishTargetFor,
4
4
  reportMissingTools
5
- } from "./cli-sq0awkbf.js";
5
+ } from "./cli-x9bb628z.js";
6
6
  import {
7
7
  log
8
- } from "./cli-6tfc4ed6.js";
8
+ } from "./cli-14j4aqtc.js";
9
9
  import {
10
10
  UsageError,
11
11
  rejectExtraPositionals,
12
12
  rejectUnknownFlags
13
- } from "./cli-p8xwsknv.js";
14
- import"./cli-d55z7rtk.js";
13
+ } from "./cli-w9qc8fem.js";
14
+ import"./cli-8597az31.js";
15
15
  import {
16
16
  EXIT_CODES
17
17
  } from "./cli-46ycxe6r.js";
@@ -6,10 +6,10 @@ import {
6
6
  runBuild,
7
7
  splitOutPath,
8
8
  startPreviewServer
9
- } from "./cli-sq0awkbf.js";
9
+ } from "./cli-x9bb628z.js";
10
10
  import {
11
11
  log
12
- } from "./cli-6tfc4ed6.js";
12
+ } from "./cli-14j4aqtc.js";
13
13
  import {
14
14
  UsageError,
15
15
  parseFormat,
@@ -17,8 +17,8 @@ import {
17
17
  rejectExtraPositionals,
18
18
  rejectUnknownFlags,
19
19
  resolvePort
20
- } from "./cli-p8xwsknv.js";
21
- import"./cli-d55z7rtk.js";
20
+ } from "./cli-w9qc8fem.js";
21
+ import"./cli-8597az31.js";
22
22
  import {
23
23
  BuildError
24
24
  } from "./cli-46ycxe6r.js";
@@ -11,17 +11,17 @@ import {
11
11
  publishProviderFor,
12
12
  resolvePublishFormat,
13
13
  runPublish
14
- } from "./cli-sq0awkbf.js";
14
+ } from "./cli-x9bb628z.js";
15
15
  import {
16
16
  FileTokenStore,
17
17
  log
18
- } from "./cli-6tfc4ed6.js";
18
+ } from "./cli-14j4aqtc.js";
19
19
  import {
20
20
  UsageError,
21
21
  rejectExtraPositionals,
22
22
  rejectUnknownFlags
23
- } from "./cli-p8xwsknv.js";
24
- import"./cli-d55z7rtk.js";
23
+ } from "./cli-w9qc8fem.js";
24
+ import"./cli-8597az31.js";
25
25
  import {
26
26
  EXIT_CODES
27
27
  } from "./cli-46ycxe6r.js";
package/dist/render.js CHANGED
@@ -350,6 +350,16 @@ function validateDeclaredMarkerName(name, pluginName) {
350
350
  }
351
351
  var TAG_NAME_RE = /^[a-z][a-z0-9-]*$/;
352
352
  function resolveContainerShape(name, pluginName, decl) {
353
+ if (decl.section !== undefined && typeof decl.section !== "boolean") {
354
+ throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`section\` that is not a boolean.`);
355
+ }
356
+ if (decl.section) {
357
+ for (const field of ["tag", "label", "autoCloseAt"]) {
358
+ if (decl[field] !== undefined) {
359
+ throw new Error(`Plugin "${pluginName}"'s marker "@${name}" sets \`section: true\` and \`${field}\` — a section ` + `marker is a core @section, so \`${field}\` does not apply. Remove one of them.`);
360
+ }
361
+ }
362
+ }
353
363
  const tag = decl.tag !== undefined ? decl.tag : "div";
354
364
  if (typeof tag !== "string" || !TAG_NAME_RE.test(tag)) {
355
365
  throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has an invalid \`tag\` (${JSON.stringify(decl.tag)}) — ` + `must be a lower-case HTML tag name.`);
@@ -405,7 +415,7 @@ function resolveContainerShape(name, pluginName, decl) {
405
415
  throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`validate\` that is not a function.`);
406
416
  }
407
417
  const check = decl.validate ? { validate: decl.validate, validateOwner: pluginName } : {};
408
- return { tag, classBase, variants, label, autoCloseAtEof, ...check };
418
+ return { tag, classBase, variants, label, autoCloseAtEof, ...decl.section ? { section: true } : {}, ...check };
409
419
  }
410
420
  function resolveMarkerDeclaration(name, rawDecl, rawRegistry, originOf) {
411
421
  const pluginName = originOf.get(name);
@@ -481,12 +491,12 @@ function declaredKindWords(declaredMarkers) {
481
491
  }
482
492
  return words;
483
493
  }
484
- function nearestDeclaredMarkerName(word, declaredMarkers) {
494
+ function nearestDeclaredMarkerName(word, names) {
485
495
  const w = word.toLowerCase();
486
496
  const threshold = Math.max(2, Math.floor(w.length / 3));
487
497
  let best = null;
488
498
  let bestDistance = Infinity;
489
- for (const known of declaredMarkers.keys()) {
499
+ for (const known of names) {
490
500
  const d = editDistance(w, known);
491
501
  if (d < bestDistance) {
492
502
  bestDistance = d;
@@ -514,13 +524,23 @@ function scanForUnknownDeclaredMarkers(state, declaredMarkers) {
514
524
  const word = m[1];
515
525
  if (KNOWN_KINDS.includes(word) || declaredMarkers.has(word))
516
526
  continue;
517
- const suggestion = nearestDeclaredMarkerName(word, declaredMarkers);
527
+ const suggestion = nearestDeclaredMarkerName(word, declaredMarkers.keys());
518
528
  if (!suggestion)
519
529
  continue;
520
530
  warn(state.env, tok.map[0] + i + 1, "unknown_marker", `Unknown marker "@${word}". Did you mean "@${suggestion}"?`, null);
521
531
  }
522
532
  }
523
533
  }
534
+ function warnUnknownVariant(env, meta, decl) {
535
+ const word = meta.name;
536
+ if (!word)
537
+ return;
538
+ const variants = Object.keys(decl.variants || {});
539
+ if (variants.includes(word))
540
+ return;
541
+ const near = variants.find((v) => v.toLowerCase() === word.toLowerCase()) || (word.length >= 3 ? nearestDeclaredMarkerName(word, variants) : null);
542
+ warn(env, meta.__line, "unknown_variant", `"${word}" is not a variant of @${meta.kind}, so it adds no styling. ` + (variants.length ? `Variants: ${variants.join(", ")}.` : `@${meta.kind} has no variants.`) + (near ? ` Did you mean "${near}"?` : ""), meta);
543
+ }
524
544
  function plugin(md, pluginOptions = {}) {
525
545
  const options = {
526
546
  preferPagesInSpreads: false,
@@ -630,7 +650,15 @@ function plugin(md, pluginOptions = {}) {
630
650
  function openDeclaredMarker(meta, decl) {
631
651
  const t = new state.Token("layout_component_open", decl.tag, 1);
632
652
  const variant = meta.name || decl.presetVariant || null;
633
- t.meta = { line: meta.__line, component: meta.kind, variant, attrs: meta.attrs || {}, labelled: false };
653
+ warnUnknownVariant(state.env, meta, decl);
654
+ t.meta = {
655
+ line: meta.__line,
656
+ component: meta.kind,
657
+ kind: decl.baseKind,
658
+ variant,
659
+ attrs: meta.attrs || {},
660
+ labelled: false
661
+ };
634
662
  const variantClass = variant && decl.variants && decl.variants[variant] || "";
635
663
  const baseClass = [decl.classBase, variantClass].filter(Boolean).join(" ");
636
664
  addClasses(t, baseClass, meta.attrs && meta.attrs.class ? meta.attrs.class : "");
@@ -738,16 +766,34 @@ function plugin(md, pluginOptions = {}) {
738
766
  warn(state.env, meta.__line || 0, "page_outside_spread", "@page used outside of a spread; allowed, but spreads are recommended for deliberate grouping.", meta);
739
767
  }
740
768
  }
769
+ function sectionMetaFor(meta, decl) {
770
+ const attrs = meta.attrs || {};
771
+ const variant = meta.name || decl.presetVariant || null;
772
+ warnUnknownVariant(state.env, meta, decl);
773
+ const variantClass = variant && decl.variants && decl.variants[variant] || "";
774
+ const cls = [decl.classBase, variantClass, attrs.class].filter(Boolean).join(" ");
775
+ return {
776
+ kind: "section",
777
+ name: null,
778
+ attrs: cls ? { ...attrs, class: cls } : attrs,
779
+ __line: meta.__line,
780
+ __component: { component: meta.kind, kind: decl.baseKind, variant, attrs }
781
+ };
782
+ }
741
783
  function openSection(meta) {
742
784
  const t = new state.Token("layout_section_open", "div", 1);
785
+ const component = meta.__component || null;
743
786
  addClasses(t, "section", meta.attrs && meta.attrs.class ? meta.attrs.class : "");
744
- attachDataAttrs(t, "section", meta.name, meta.attrs || {});
745
- t.meta = { line: meta.__line };
787
+ if (component)
788
+ attachDataAttrs(t, component.kind, component.variant, meta.attrs || {});
789
+ else
790
+ attachDataAttrs(t, "section", meta.name, meta.attrs || {});
791
+ t.meta = component ? { line: meta.__line, ...component, ...meta.__continued ? { continued: true } : {} } : { line: meta.__line };
746
792
  stack.open({
747
793
  kind: "section",
748
794
  classes: meta.attrs && meta.attrs.class || "",
749
795
  sawContent: false,
750
- meta: { name: meta.name || null, attrs: { ...meta.attrs || {} } },
796
+ meta: { name: meta.name || null, attrs: { ...meta.attrs || {} }, component },
751
797
  openToken: t
752
798
  }, t);
753
799
  }
@@ -793,9 +839,21 @@ function plugin(md, pluginOptions = {}) {
793
839
  out.push(tok);
794
840
  continue;
795
841
  }
796
- const meta = tok.meta || {};
797
- const kind = meta.kind;
842
+ let meta = tok.meta || {};
843
+ let kind = meta.kind;
798
844
  const line = meta.__line || 0;
845
+ if (declaredMarkers) {
846
+ const closing = kind.startsWith("end-");
847
+ const sectionDecl = declaredMarkers.get(closing ? kind.slice(4) : kind);
848
+ if (sectionDecl && sectionDecl.section) {
849
+ if (closing) {
850
+ kind = "end-section";
851
+ } else {
852
+ meta = sectionMetaFor(meta, sectionDecl);
853
+ kind = "section";
854
+ }
855
+ }
856
+ }
799
857
  if (kind === "chapter") {
800
858
  drainDeclaredFrames();
801
859
  stack.close("chapter");
@@ -839,6 +897,10 @@ function plugin(md, pluginOptions = {}) {
839
897
  name: section.meta.name,
840
898
  attrs: { ...section.meta.attrs || {} }
841
899
  };
900
+ if (section.meta.component) {
901
+ contMeta.__component = section.meta.component;
902
+ contMeta.__continued = true;
903
+ }
842
904
  contMeta.__line = line;
843
905
  const cls = (contMeta.attrs.class || "").split(/\s+/).filter(Boolean);
844
906
  if (!cls.includes("gp-continued"))
@@ -912,7 +974,8 @@ function plugin(md, pluginOptions = {}) {
912
974
  const tokens = state.tokens;
913
975
  for (let i = 0;i < tokens.length; i++) {
914
976
  const tok = tokens[i];
915
- if (tok.type !== "layout_component_open")
977
+ const isSection = tok.type === "layout_section_open" && tok.meta && tok.meta.component && !tok.meta.continued;
978
+ if (tok.type !== "layout_component_open" && !isSection)
916
979
  continue;
917
980
  const decl = declaredMarkers.get(tok.meta.component);
918
981
  if (!decl || !decl.validate)
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  executeAndReport
3
- } from "./cli-sq0awkbf.js";
3
+ } from "./cli-x9bb628z.js";
4
4
  import {
5
5
  log
6
- } from "./cli-6tfc4ed6.js";
6
+ } from "./cli-14j4aqtc.js";
7
7
  import {
8
8
  UsageError,
9
9
  rejectExtraPositionals,
10
10
  rejectUnknownFlags
11
- } from "./cli-p8xwsknv.js";
12
- import"./cli-d55z7rtk.js";
11
+ } from "./cli-w9qc8fem.js";
12
+ import"./cli-8597az31.js";
13
13
  import {
14
14
  EXIT_CODES
15
15
  } from "./cli-46ycxe6r.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gutterpress",
3
- "version": "0.11.16-alpha.1",
3
+ "version": "0.11.16-alpha.3",
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",