gutterpress 0.10.0-alpha.4 → 0.10.0
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/README.md +2 -0
- package/dist/api/index.js +2 -2
- package/dist/{audit-k1vnfwvc.js → audit-cvarpa72.js} +4 -4
- package/dist/{build-hqmwgvdw.js → build-6r502chw.js} +6 -4
- package/dist/{cli-0r0tq16s.js → cli-149edp6b.js} +1 -1
- package/dist/{cli-ra0ed2xt.js → cli-cqtggsng.js} +1 -1
- package/dist/{cli-n25qycwz.js → cli-eq5naw4m.js} +222 -141
- package/dist/{cli-revgt4pr.js → cli-yzf38679.js} +1 -0
- package/dist/{cli-hp9r2pzt.js → cli-zfcryxg8.js} +327 -24
- package/dist/cli.js +16 -16
- package/dist/{doctor-dxms7ehm.js → doctor-akvxbtjb.js} +2 -2
- package/dist/engine/compiler/build.d.ts +1 -1
- package/dist/engine/shared/flush.d.ts +59 -0
- package/dist/engine/shared/margin-box-support.d.ts +24 -0
- package/dist/{engine-z4p9sr4h.js → engine-b159tbns.js} +2 -2
- package/dist/{engine-wa7y9av9.js → engine-ft4cr3ep.js} +1 -1
- package/dist/{gutterpress-agent-1ctgfz92.js → gutterpress-agent-cazqstr1.js} +49 -1
- package/dist/{gutterpress-viewer-cem7dmr5.js → gutterpress-viewer-te8g5grx.js} +191 -71
- package/dist/{index-05y3dnxq.js → index-ge7q9xj3.js} +330 -249
- package/dist/{index-xxg4zfrg.js → index-wq3r5pj7.js} +287 -23
- package/dist/{index-9tyq9kks.js → index-ycpvr0am.js} +47 -4
- package/dist/index.js +3 -3
- package/dist/lib/build-runner.d.ts +7 -0
- package/dist/lib/build-staging.d.ts +51 -0
- package/dist/lib/engine.d.ts +9 -0
- package/dist/lib/markdown/gutterpress-css.d.ts +9 -1
- package/dist/lib/markdown/markers.d.ts +51 -1
- package/dist/{lint-xjwm5ep8.js → lint-p2sw53d9.js} +4 -4
- package/dist/{new-kwdwpf0j.js → new-hvq0x91q.js} +4 -4
- package/dist/{plugin-rg4tnn96.js → plugin-pssmk0dx.js} +4 -4
- package/dist/{preflight-3127y25z.js → preflight-4j00yd0g.js} +4 -4
- package/dist/{preview-ncgfhqmw.js → preview-d4s6gk6p.js} +6 -5
- package/dist/{preview-interface-435cczt5.js → preview-interface-0ssk8bmm.js} +153 -6
- package/dist/{publish-pr0rwh6p.js → publish-tkc26en7.js} +4 -4
- package/dist/render.js +83 -7
- package/dist/{repair-8270smfw.js → repair-2va4w12t.js} +4 -4
- package/dist/{validate-54e17rae.js → validate-w9vfefrw.js} +4 -4
- package/package.json +1 -1
|
@@ -10,6 +10,56 @@ export default function plugin(md: any, pluginOptions?: {}): void;
|
|
|
10
10
|
* on its own page instead of resolving against the document canvas and
|
|
11
11
|
* painting on the last page of the book.
|
|
12
12
|
*
|
|
13
|
+
* `min-height: var(--gp-content-h)` is the other half of that contract: a
|
|
14
|
+
* page root that only shrink-wraps its text is a containing block whose
|
|
15
|
+
* `bottom` edge is the end of the PROSE, so `.gp-pin .gp-bottom` (and every
|
|
16
|
+
* hand-written `position: absolute; bottom: 0`) lands under the last
|
|
17
|
+
* paragraph instead of at the page foot. Paged.js used to supply this height
|
|
18
|
+
* for free — `.pagedjs_pagebox > .pagedjs_area > .pagedjs_page_content > div
|
|
19
|
+
* { height: inherit; }` stretched the page root to the page area — and
|
|
20
|
+
* deleting the polyfill (native-only migration, Phase 6) silently took
|
|
21
|
+
* page-boundary pinning with it. Both renderers now publish the page
|
|
22
|
+
* CONTENT height as `--gp-content-h` for the page context the element is in
|
|
23
|
+
* (the viewer on each `.gp-strip`; the compiler on `:root` plus every
|
|
24
|
+
* `page:` assignment selector), and custom properties inherit, so this one
|
|
25
|
+
* rule reaches a page root at any wrapper depth. Undefined var (plain
|
|
26
|
+
* markdown-it use, no engine) falls back to `1px`, which the cushion below
|
|
27
|
+
* takes back to zero — the rule is inert outside the pipeline, leaving the
|
|
28
|
+
* page root shrink-wrapped exactly as it is today.
|
|
29
|
+
*
|
|
30
|
+
* It is `min-height`, not `height`: a `.page` whose content runs past one
|
|
31
|
+
* sheet must still fragment normally. The whole rule is `:where()`, so an
|
|
32
|
+
* author who wants any of it back sets their own value at any specificity.
|
|
33
|
+
*
|
|
34
|
+
* `box-sizing` and `display` are what make a page-sized min-height safe, and
|
|
35
|
+
* both were found by MEASURING a real book (docs/fixtures/css-authoring-spike)
|
|
36
|
+
* print one extra sheet without them:
|
|
37
|
+
*
|
|
38
|
+
* - `border-box`, because a page root with padding (that fixture's cover
|
|
39
|
+
* has `padding-top: 2.5in`) would otherwise be one content box PLUS its
|
|
40
|
+
* padding tall, which is one sheet plus 2.5in — the overflow lands on a
|
|
41
|
+
* spurious blank sheet.
|
|
42
|
+
* - `flow-root`, because a child margin collapsing THROUGH the root's edge
|
|
43
|
+
* survives the forced break and eats into the new sheet: that fixture's
|
|
44
|
+
* last page opens with an `<h2>` whose 22.4px margin-top pushed a
|
|
45
|
+
* 719px-tall root down 22.4px on a 720px sheet, spilling it. A page root
|
|
46
|
+
* is a page; containing its children's margins is what a page does. The
|
|
47
|
+
* viewer already forces `flow-root` on displaced page roots for exactly
|
|
48
|
+
* this reason (`stabilizeFullHeightPageRoots`, fragment.ts) — declaring
|
|
49
|
+
* it once in core means both renderers start from the same box. Zero
|
|
50
|
+
* specificity, so an authored `display: flex`/`grid` page root still
|
|
51
|
+
* wins, which is the case that function's comment warns about.
|
|
52
|
+
*
|
|
53
|
+
* The `- 1px` is the fragmentation cushion, and it is load-bearing. A box
|
|
54
|
+
* whose bottom edge lands EXACTLY on the fragmentainer's edge costs an empty
|
|
55
|
+
* fragment: MEASURED (Chromium 148, 384x480px sheet, 24px margins, one
|
|
56
|
+
* short page root) `min-height: 432px` prints 2 sheets with the second
|
|
57
|
+
* blank while `431px` prints 1 — and the viewer's multicol strip does the
|
|
58
|
+
* same, which is how it first showed up (an 8pp preview of a 7pp book on
|
|
59
|
+
* the gp-image-positioning fixture). One px at 96dpi is 0.75pt: it moves a
|
|
60
|
+
* pinned foot up by less than a point and absorbs sub-pixel rounding in the
|
|
61
|
+
* published value. Do not "clean it up" — the sheet it costs is silent.
|
|
62
|
+
*
|
|
13
63
|
* The break/orphan rules below (`break-after` on headings, image sizing,
|
|
14
64
|
* first-child glue) are all `:where()` so they carry zero specificity —
|
|
15
65
|
* author CSS at any specificity wins outright, reusing this same
|
|
@@ -29,4 +79,4 @@ export default function plugin(md: any, pluginOptions?: {}): void;
|
|
|
29
79
|
* `text-align: center` still centers it — `display: block` would not).
|
|
30
80
|
*
|
|
31
81
|
*/
|
|
32
|
-
export const MARKER_CSS: "\n/* The UA default of 8px body margin is a screen affordance with no meaning\n in paged media, and engines disagree about it: a polyfill that treats the\n page div as the page box drops it, native print keeps it. Left in place it\n insets EVERY page's content by 8px per side, and -- measured, 300dpi,\n 6x4in sheet -- it is what stops a full-width block from reaching the\n paper: it lands at 0.080..5.917in of a 6in sheet instead of\n 0.000..6.000in, because width:100% resolves against the BODY content box,\n not the page's. Zeroing it here (first in the cascade) makes the two\n agree. Authors who want a body margin still just declare one. */\nbody { margin: 0; }\n\n.gp-page-break { break-before: page; }\n.page { break-before: page; }\n.spread { break-before: page; }\n:where(.page, .spread) { position: relative; }\n.gp-column-break { break-after: column; height: 0; font-size: 0; line-height: 0; visibility: hidden; }\n\n:where(h1,h2,h3,h4,h5,h6) { break-after: avoid; }\n:where(img, svg, video) { max-width: 100%; }\n:where(p > img:only-child, figure > img) { width: fit-content; max-width: 100%; height: auto; vertical-align: bottom; }\n:where(.section, figure) > :where(:first-child) { break-before: avoid; }\n\n";
|
|
82
|
+
export const MARKER_CSS: "\n/* The UA default of 8px body margin is a screen affordance with no meaning\n in paged media, and engines disagree about it: a polyfill that treats the\n page div as the page box drops it, native print keeps it. Left in place it\n insets EVERY page's content by 8px per side, and -- measured, 300dpi,\n 6x4in sheet -- it is what stops a full-width block from reaching the\n paper: it lands at 0.080..5.917in of a 6in sheet instead of\n 0.000..6.000in, because width:100% resolves against the BODY content box,\n not the page's. Zeroing it here (first in the cascade) makes the two\n agree. Authors who want a body margin still just declare one. */\nbody { margin: 0; }\n\n.gp-page-break { break-before: page; }\n.page { break-before: page; }\n.spread { break-before: page; }\n:where(.page, .spread) { position: relative; display: flow-root; box-sizing: border-box; min-height: calc(var(--gp-content-h, 1px) - 1px); }\n.gp-column-break { break-after: column; height: 0; font-size: 0; line-height: 0; visibility: hidden; }\n\n:where(h1,h2,h3,h4,h5,h6) { break-after: avoid; }\n:where(img, svg, video) { max-width: 100%; }\n:where(p > img:only-child, figure > img) { width: fit-content; max-width: 100%; height: auto; vertical-align: bottom; }\n:where(.section, figure) > :where(:first-child) { break-before: avoid; }\n\n";
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MANIFEST_FILENAMES,
|
|
3
3
|
runLint
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-eq5naw4m.js";
|
|
5
5
|
import {
|
|
6
6
|
log
|
|
7
|
-
} from "./cli-
|
|
7
|
+
} from "./cli-cqtggsng.js";
|
|
8
8
|
import {
|
|
9
9
|
UsageError,
|
|
10
10
|
rejectExtraPositionals,
|
|
11
11
|
rejectUnknownFlags
|
|
12
|
-
} from "./cli-
|
|
13
|
-
import"./cli-
|
|
12
|
+
} from "./cli-149edp6b.js";
|
|
13
|
+
import"./cli-zfcryxg8.js";
|
|
14
14
|
import"./cli-c41yr7he.js";
|
|
15
15
|
import {
|
|
16
16
|
EXIT_CODES
|
|
@@ -5,16 +5,16 @@ import {
|
|
|
5
5
|
TARGETS,
|
|
6
6
|
TARGET_IDS,
|
|
7
7
|
scaffoldProject
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-eq5naw4m.js";
|
|
9
9
|
import {
|
|
10
10
|
resolveGhostscript
|
|
11
|
-
} from "./cli-
|
|
11
|
+
} from "./cli-cqtggsng.js";
|
|
12
12
|
import {
|
|
13
13
|
UsageError,
|
|
14
14
|
rejectExtraPositionals,
|
|
15
15
|
rejectUnknownFlags
|
|
16
|
-
} from "./cli-
|
|
17
|
-
import"./cli-
|
|
16
|
+
} from "./cli-149edp6b.js";
|
|
17
|
+
import"./cli-zfcryxg8.js";
|
|
18
18
|
import {
|
|
19
19
|
isToolAvailable
|
|
20
20
|
} from "./cli-c41yr7he.js";
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
addNpmPlugin,
|
|
3
3
|
parseNpmPluginSpec
|
|
4
|
-
} from "./cli-
|
|
5
|
-
import"./cli-
|
|
4
|
+
} from "./cli-eq5naw4m.js";
|
|
5
|
+
import"./cli-cqtggsng.js";
|
|
6
6
|
import {
|
|
7
7
|
UsageError,
|
|
8
8
|
rejectExtraPositionals,
|
|
9
9
|
rejectUnknownFlags
|
|
10
|
-
} from "./cli-
|
|
11
|
-
import"./cli-
|
|
10
|
+
} from "./cli-149edp6b.js";
|
|
11
|
+
import"./cli-zfcryxg8.js";
|
|
12
12
|
import"./cli-c41yr7he.js";
|
|
13
13
|
import {
|
|
14
14
|
EXIT_CODES
|
|
@@ -2,16 +2,16 @@ import {
|
|
|
2
2
|
executeValidation,
|
|
3
3
|
publishTargetFor,
|
|
4
4
|
reportMissingTools
|
|
5
|
-
} from "./cli-
|
|
5
|
+
} from "./cli-eq5naw4m.js";
|
|
6
6
|
import {
|
|
7
7
|
log
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-cqtggsng.js";
|
|
9
9
|
import {
|
|
10
10
|
UsageError,
|
|
11
11
|
rejectExtraPositionals,
|
|
12
12
|
rejectUnknownFlags
|
|
13
|
-
} from "./cli-
|
|
14
|
-
import"./cli-
|
|
13
|
+
} from "./cli-149edp6b.js";
|
|
14
|
+
import"./cli-zfcryxg8.js";
|
|
15
15
|
import"./cli-c41yr7he.js";
|
|
16
16
|
import {
|
|
17
17
|
EXIT_CODES
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
previewArgs
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-yzf38679.js";
|
|
4
4
|
import {
|
|
5
5
|
openPath,
|
|
6
6
|
runBuild,
|
|
7
7
|
splitOutPath,
|
|
8
8
|
startPreviewServer
|
|
9
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-eq5naw4m.js";
|
|
10
10
|
import {
|
|
11
11
|
log
|
|
12
|
-
} from "./cli-
|
|
12
|
+
} from "./cli-cqtggsng.js";
|
|
13
13
|
import {
|
|
14
14
|
UsageError,
|
|
15
15
|
parseEngine,
|
|
@@ -18,8 +18,8 @@ import {
|
|
|
18
18
|
rejectExtraPositionals,
|
|
19
19
|
rejectUnknownFlags,
|
|
20
20
|
resolvePort
|
|
21
|
-
} from "./cli-
|
|
22
|
-
import"./cli-
|
|
21
|
+
} from "./cli-149edp6b.js";
|
|
22
|
+
import"./cli-zfcryxg8.js";
|
|
23
23
|
import"./cli-c41yr7he.js";
|
|
24
24
|
import {
|
|
25
25
|
BuildError
|
|
@@ -82,6 +82,7 @@ var preview_default = defineCommand({
|
|
|
82
82
|
skipLint: !!args["skip-lint"],
|
|
83
83
|
skipPreValidate: !!args["skip-pre-validate"],
|
|
84
84
|
skipPostValidate: !!args["skip-post-validate"],
|
|
85
|
+
allowShrink: !!args["allow-shrink"],
|
|
85
86
|
engine,
|
|
86
87
|
rawArgs: args
|
|
87
88
|
});
|
|
@@ -331,6 +331,125 @@
|
|
|
331
331
|
return null;
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
// Full hit stack at a point, top-most first. Degrades to the single
|
|
335
|
+
// elementFromPoint() hit on hosts without elementsFromPoint().
|
|
336
|
+
function elementStackAtPoint(x, y) {
|
|
337
|
+
try {
|
|
338
|
+
if (typeof document.elementsFromPoint === 'function') {
|
|
339
|
+
var els = document.elementsFromPoint(x, y);
|
|
340
|
+
if (els && els.length) return Array.prototype.slice.call(els);
|
|
341
|
+
}
|
|
342
|
+
} catch (_e) { /* fall through to the single-element path */ }
|
|
343
|
+
var el = elementAtPoint(x, y);
|
|
344
|
+
return el ? [el] : [];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Computed z-index as a number; NaN for 'auto'/unset/unsupported hosts
|
|
348
|
+
// (NaN < 0 is false, so those never qualify as behind-layered below).
|
|
349
|
+
function zIndexOf(el) {
|
|
350
|
+
try {
|
|
351
|
+
if (typeof window.getComputedStyle === 'function') {
|
|
352
|
+
return parseFloat(window.getComputedStyle(el).zIndex);
|
|
353
|
+
}
|
|
354
|
+
} catch (_e) { /* unsupported host */ }
|
|
355
|
+
return NaN;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Why elementsFromPoint (plural) exists here: a `.gp-behind` image — the
|
|
359
|
+
// image-properties "Layer" facet sets `z-index: -1` to paint a pinned
|
|
360
|
+
// plate UNDER the page's own text (gutterpress-css.ts's depth ladder) —
|
|
361
|
+
// hit-tests beneath the covering paragraph boxes and the annotated
|
|
362
|
+
// `.page`/`.section` containers too, so document.elementFromPoint() can
|
|
363
|
+
// NEVER return it. Every right-click used to resolve the covering element
|
|
364
|
+
// instead, leaving the image's context menu unreachable at EVERY point.
|
|
365
|
+
// Probe the full hit stack for the first (= top-most in paint order, so
|
|
366
|
+
// the upper of two overlapping plates wins) image layered at negative z.
|
|
367
|
+
// ONLY negative-z images qualify: a normally-layered image that merely
|
|
368
|
+
// overlaps other content is already reachable at its uncovered points,
|
|
369
|
+
// and stealing the covering content's right-clicks would invert the bug.
|
|
370
|
+
// Computed style, not a `gp-behind` class test, mirrors the build-time
|
|
371
|
+
// engine.layer.trapped audit — book CSS can layer an image behind with a
|
|
372
|
+
// bare `z-index: -1` of its own.
|
|
373
|
+
function behindImageInStack(stack) {
|
|
374
|
+
for (var i = 0; i < stack.length; i++) {
|
|
375
|
+
var el = stack[i];
|
|
376
|
+
if (el.tagName && el.tagName.toLowerCase() === 'img' && zIndexOf(el) < 0) return el;
|
|
377
|
+
}
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// The .gp-sheet whose box contains a viewport point, or null. The sheet
|
|
382
|
+
// chrome lives in the hit-transparent .gp-layer (viewer.css), so it can
|
|
383
|
+
// never appear in the hit stack — this is a geometric scan of the cached
|
|
384
|
+
// page list (refreshPages), not a DOM hit test.
|
|
385
|
+
function sheetAtPoint(x, y) {
|
|
386
|
+
if (pages.length === 0) refreshPages();
|
|
387
|
+
for (var i = 0; i < pages.length; i++) {
|
|
388
|
+
var r = pages[i].getBoundingClientRect();
|
|
389
|
+
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return pages[i];
|
|
390
|
+
}
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// The annotated .page/.spread wrapper that OWNS a sheet: greatest
|
|
395
|
+
// client-rect intersection with the sheet's box — the same rect-vs-sheet
|
|
396
|
+
// resolution nativeRectsFor() uses. Null when the page has no author
|
|
397
|
+
// @page/@spread wrapper (plain flowed content) — callers then keep the
|
|
398
|
+
// plain hit result.
|
|
399
|
+
function pageMarkerElForSheet(sheet) {
|
|
400
|
+
var sheetRect = sheet.getBoundingClientRect();
|
|
401
|
+
var candidates = document.querySelectorAll('.page[data-source-range], .spread[data-source-range]');
|
|
402
|
+
var best = null;
|
|
403
|
+
var bestArea = 0;
|
|
404
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
405
|
+
var rects = rectsOf(candidates[i]);
|
|
406
|
+
for (var j = 0; j < rects.length; j++) {
|
|
407
|
+
var r = rects[j];
|
|
408
|
+
var width = Math.min(r.right, sheetRect.right) - Math.max(r.left, sheetRect.left);
|
|
409
|
+
var height = Math.min(r.bottom, sheetRect.bottom) - Math.max(r.top, sheetRect.top);
|
|
410
|
+
var area = width > 0 && height > 0 ? width * height : 0;
|
|
411
|
+
if (area > bestArea) {
|
|
412
|
+
bestArea = area;
|
|
413
|
+
best = candidates[i];
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return best;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// The element getContextTargetAt() resolves the payload from. `topmostOnly`
|
|
421
|
+
// (the keyboard path) skips the behind-image probe: its anchor is a
|
|
422
|
+
// SYNTHETIC block-center point (keyboardAnchorPoint()), not a user-aimed
|
|
423
|
+
// pointer position — probing beneath it would hijack every keyboard menu
|
|
424
|
+
// on a page with a background plate.
|
|
425
|
+
function contextPointEl(x, y, topmostOnly) {
|
|
426
|
+
var stack = elementStackAtPoint(x, y);
|
|
427
|
+
var topEl = stack.length ? stack[0] : null;
|
|
428
|
+
if (topmostOnly || !topEl || !topEl.closest) return topEl;
|
|
429
|
+
// The user aimed at visible interactive content — a directly-hit image
|
|
430
|
+
// (an <img> only ever matches closest('img') as itself: images have no
|
|
431
|
+
// descendants) or a link's own text — never probe beneath those.
|
|
432
|
+
if (topEl.closest('img') || topEl.closest('a')) return topEl;
|
|
433
|
+
// Margin-box furniture (running headers, page numbers) keeps its native
|
|
434
|
+
// context menu even when a full-bleed plate runs beneath it — the
|
|
435
|
+
// contextmenu listener's kind==='none' contract below.
|
|
436
|
+
if (topEl.closest('.gp-marginbox')) return topEl;
|
|
437
|
+
var behind = behindImageInStack(stack);
|
|
438
|
+
if (behind) return behind;
|
|
439
|
+
// Margin band (protocol v7): the point sits in a page's margin — outside
|
|
440
|
+
// every author box, so nothing annotated is under it (the sheet chrome
|
|
441
|
+
// and the run/strip boxes are hit-transparent viewer chrome). When the
|
|
442
|
+
// point still lies inside a .gp-sheet, resolve to the annotated
|
|
443
|
+
// .page/.spread that owns that sheet, so right-clicking anywhere on the
|
|
444
|
+
// paper reaches the @page marker, not just the content box.
|
|
445
|
+
if (!resolveAnnotatedBlock(topEl)) {
|
|
446
|
+
var sheet = sheetAtPoint(x, y);
|
|
447
|
+
var owner = sheet ? pageMarkerElForSheet(sheet) : null;
|
|
448
|
+
if (owner) return owner;
|
|
449
|
+
}
|
|
450
|
+
return topEl;
|
|
451
|
+
}
|
|
452
|
+
|
|
334
453
|
function elementOf(node) {
|
|
335
454
|
if (!node) return null;
|
|
336
455
|
return node.nodeType === 1 ? node : (node.parentElement || null);
|
|
@@ -454,6 +573,21 @@
|
|
|
454
573
|
|
|
455
574
|
var block = resolveAnnotatedBlock(pointEl);
|
|
456
575
|
|
|
576
|
+
// Secondary page-marker target (protocol v7): resolveAnnotatedBlock()
|
|
577
|
+
// returns the INNERMOST annotated block, so inside a @section the
|
|
578
|
+
// enclosing @page marker never wins the primary slot. Surface the
|
|
579
|
+
// enclosing .page/.spread/.chapter wrapper's own marker line as a
|
|
580
|
+
// secondary field — the same always-populated pattern image/link/
|
|
581
|
+
// selection use — so the menu can offer "Edit page marker…" when it
|
|
582
|
+
// differs from the primary target.
|
|
583
|
+
var pageMarkerEl = pointEl && pointEl.closest ? pointEl.closest('.page, .spread, .chapter') : null;
|
|
584
|
+
var pageMarkerRange = pageMarkerEl ? sourceRangeOf(pageMarkerEl) : null;
|
|
585
|
+
var pageMarker = pageMarkerRange ? {
|
|
586
|
+
chapter: chapterOf(pageMarkerEl),
|
|
587
|
+
range: pageMarkerRange,
|
|
588
|
+
blockTag: pageMarkerEl.tagName ? pageMarkerEl.tagName.toLowerCase() : null
|
|
589
|
+
} : null;
|
|
590
|
+
|
|
457
591
|
var kind;
|
|
458
592
|
if (selection) kind = 'selection';
|
|
459
593
|
else if (image) kind = 'image';
|
|
@@ -471,7 +605,8 @@
|
|
|
471
605
|
rect: plainRect(block || pointEl),
|
|
472
606
|
image: image,
|
|
473
607
|
link: link,
|
|
474
|
-
selection: selection
|
|
608
|
+
selection: selection,
|
|
609
|
+
pageMarker: pageMarker
|
|
475
610
|
};
|
|
476
611
|
}
|
|
477
612
|
|
|
@@ -609,8 +744,10 @@
|
|
|
609
744
|
// `setSpread()` does the real relayout: `column-wrap: wrap` wraps each
|
|
610
745
|
// chapter's columns into a 2-column grid so content, not just sheet
|
|
611
746
|
// chrome, actually moves into pairs — see fragment.ts's
|
|
612
|
-
// `applySpreadMode
|
|
613
|
-
//
|
|
747
|
+
// `applySpreadMode`, which no-ops to single-row where CSS
|
|
748
|
+
// `column-wrap`/`column-height` are unavailable (Chromium < 145).
|
|
749
|
+
// The guard below is a LIB-version check, not a browser one: a
|
|
750
|
+
// hot-updated shell may be paired with an older bundled viewer.
|
|
614
751
|
if (window.Gutterpress && typeof window.Gutterpress.setSpread === 'function') {
|
|
615
752
|
window.Gutterpress.setSpread(currentViewMode !== 'single');
|
|
616
753
|
}
|
|
@@ -660,13 +797,21 @@
|
|
|
660
797
|
// v6 (WORK PACKAGE B item 2): getRectsFor()/setEditMask() dropped the
|
|
661
798
|
// {ref} form entirely — {chapter, range} is the only target shape now
|
|
662
799
|
// (the native viewer never mints a ref — it never clones).
|
|
663
|
-
|
|
800
|
+
// v7: getContextTargetAt() gained the `pageMarker` secondary field (the
|
|
801
|
+
// enclosing .page/.spread/.chapter marker) and the margin-band fallback
|
|
802
|
+
// (a point inside a .gp-sheet but outside every author box resolves to
|
|
803
|
+
// the sheet's owning .page/.spread). An older SPA simply ignores the
|
|
804
|
+
// extra field; a newer SPA feature-detects by the field's presence.
|
|
805
|
+
getProtocolVersion: function () { return 7; },
|
|
664
806
|
|
|
665
807
|
// Resolve the annotated element/selection at a viewport point (protocol
|
|
666
808
|
// v4). Pure read; see buildContextTarget() above for the full contract.
|
|
809
|
+
// Point resolution prefers a behind-layered (negative z-index) image the
|
|
810
|
+
// top-most hit covers — see contextPointEl(); `spec.topmostOnly` opts a
|
|
811
|
+
// synthetic-anchor caller (the keyboard listener) out of that probe.
|
|
667
812
|
getContextTargetAt: function (spec) {
|
|
668
813
|
spec = spec || {};
|
|
669
|
-
return buildContextTarget(
|
|
814
|
+
return buildContextTarget(contextPointEl(spec.x, spec.y, spec.topmostOnly));
|
|
670
815
|
},
|
|
671
816
|
|
|
672
817
|
// All fragment rects for one logical block (protocol v6, §5.3), targeted
|
|
@@ -976,7 +1121,9 @@
|
|
|
976
1121
|
var isMenuKey = e.key === 'ContextMenu';
|
|
977
1122
|
if (!isShiftF10 && !isMenuKey) return;
|
|
978
1123
|
var anchor = keyboardAnchorPoint();
|
|
979
|
-
|
|
1124
|
+
// topmostOnly: the anchor is a synthetic block-center point, so the
|
|
1125
|
+
// behind-image probe must not run — see contextPointEl().
|
|
1126
|
+
var detail = api.getContextTargetAt({ x: anchor.x, y: anchor.y, topmostOnly: true });
|
|
980
1127
|
detail.x = anchor.x;
|
|
981
1128
|
detail.y = anchor.y;
|
|
982
1129
|
detail.via = 'keyboard';
|
|
@@ -7,17 +7,17 @@ import {
|
|
|
7
7
|
publishCredentialKey,
|
|
8
8
|
publishProviderFor,
|
|
9
9
|
runPublish
|
|
10
|
-
} from "./cli-
|
|
10
|
+
} from "./cli-eq5naw4m.js";
|
|
11
11
|
import {
|
|
12
12
|
FileTokenStore,
|
|
13
13
|
log
|
|
14
|
-
} from "./cli-
|
|
14
|
+
} from "./cli-cqtggsng.js";
|
|
15
15
|
import {
|
|
16
16
|
UsageError,
|
|
17
17
|
rejectExtraPositionals,
|
|
18
18
|
rejectUnknownFlags
|
|
19
|
-
} from "./cli-
|
|
20
|
-
import"./cli-
|
|
19
|
+
} from "./cli-149edp6b.js";
|
|
20
|
+
import"./cli-zfcryxg8.js";
|
|
21
21
|
import"./cli-c41yr7he.js";
|
|
22
22
|
import {
|
|
23
23
|
EXIT_CODES
|
package/dist/render.js
CHANGED
|
@@ -172,6 +172,9 @@ function warn(env, line, type, message, marker) {
|
|
|
172
172
|
env.layoutWarnings = [];
|
|
173
173
|
env.layoutWarnings.push({ line, type, message, marker });
|
|
174
174
|
}
|
|
175
|
+
function proseEscapeHint(kind) {
|
|
176
|
+
return ` If this line is prose that happens to begin with "@${kind}" — a wrapped sentence, for` + ` instance — escape it as \\@${kind} or write it as \`@${kind}\` in backticks, and it stays text.`;
|
|
177
|
+
}
|
|
175
178
|
function addClasses(token, baseClass, extraClass) {
|
|
176
179
|
const cls = [];
|
|
177
180
|
if (baseClass)
|
|
@@ -233,14 +236,14 @@ function plugin(md, pluginOptions = {}) {
|
|
|
233
236
|
const unknown = parsed.__unknownTokens;
|
|
234
237
|
delete parsed.__unknownTokens;
|
|
235
238
|
for (const t of unknown) {
|
|
236
|
-
warn(state.env, startLine + 1, "unrecognized_marker_token", `@${parsed.kind}: "${t}" is not something a marker understands, so it was kept verbatim as a class name. Write a class as .my-class (or {.my-class}), an id as #my-id, and anything else as key=value
|
|
239
|
+
warn(state.env, startLine + 1, "unrecognized_marker_token", `@${parsed.kind}: "${t}" is not something a marker understands, so it was kept verbatim as a class name. Write a class as .my-class (or {.my-class}), an id as #my-id, and anything else as key=value.` + proseEscapeHint(parsed.kind), parsed);
|
|
237
240
|
}
|
|
238
241
|
}
|
|
239
242
|
if (parsed.__extraBareTokens) {
|
|
240
243
|
const { tokens, named } = parsed.__extraBareTokens;
|
|
241
244
|
delete parsed.__extraBareTokens;
|
|
242
245
|
const list = tokens.map((t) => `"${t}"`).join(", ");
|
|
243
|
-
warn(state.env, startLine + 1, "extra_bare_marker_token", named ? `@${parsed.kind}: only the first plain word (${list.split(", ")[0]}) is used as the name; the rest (${tokens.slice(1).map((t) => `"${t}"`).join(", ")}) became class names instead. Quote a name that contains spaces ("${tokens.join(" ")}"), or write classes as .${tokens.slice(1).join(" .")}.` : `@${parsed.kind}: ${list} are several plain words, but a marker has only one name slot — so NONE of them was used as the name and they all became class names instead. Quote a name that contains spaces ("${tokens.join(" ")}"), or write classes as .${tokens.join(" .")}
|
|
246
|
+
warn(state.env, startLine + 1, "extra_bare_marker_token", named ? `@${parsed.kind}: only the first plain word (${list.split(", ")[0]}) is used as the name; the rest (${tokens.slice(1).map((t) => `"${t}"`).join(", ")}) became class names instead. Quote a name that contains spaces ("${tokens.join(" ")}"), or write classes as .${tokens.slice(1).join(" .")}.` : `@${parsed.kind}: ${list} are several plain words, but a marker has only one name slot — so NONE of them was used as the name and they all became class names instead. Quote a name that contains spaces ("${tokens.join(" ")}"), or write classes as .${tokens.join(" .")}.` + proseEscapeHint(parsed.kind), parsed);
|
|
244
247
|
}
|
|
245
248
|
state.line = startLine + 1;
|
|
246
249
|
return true;
|
|
@@ -332,7 +335,12 @@ function plugin(md, pluginOptions = {}) {
|
|
|
332
335
|
t.meta = { line: meta.__line };
|
|
333
336
|
addClasses(t, "spread", meta.attrs && meta.attrs.class ? meta.attrs.class : "");
|
|
334
337
|
attachDataAttrs(t, "spread", meta.name, meta.attrs || {});
|
|
335
|
-
stack.open({
|
|
338
|
+
stack.open({
|
|
339
|
+
kind: "spread",
|
|
340
|
+
classes: meta.attrs && meta.attrs.class || "",
|
|
341
|
+
noPagesYet: true,
|
|
342
|
+
sawAnyPage: false
|
|
343
|
+
}, t);
|
|
336
344
|
}
|
|
337
345
|
function openPage(meta) {
|
|
338
346
|
const chapter = stack.get("chapter");
|
|
@@ -347,7 +355,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
347
355
|
const label = chapter ? chapter.label : "";
|
|
348
356
|
if (label)
|
|
349
357
|
t.attrSet("data-chapter-label", label);
|
|
350
|
-
stack.open({ kind: "page" }, t);
|
|
358
|
+
stack.open({ kind: "page", classes: explicit }, t);
|
|
351
359
|
if (label && !chapter.openerEmitted) {
|
|
352
360
|
const opener = new state.Token("html_block", "", 0);
|
|
353
361
|
opener.content = `<div class="chapter-opener" data-chapter-label="${escapeAttr(label)}">${escapeHtml(label)}</div>
|
|
@@ -370,13 +378,51 @@ function plugin(md, pluginOptions = {}) {
|
|
|
370
378
|
t.meta = { hasColumnBreak: false, line: meta.__line };
|
|
371
379
|
stack.open({
|
|
372
380
|
kind: "section",
|
|
381
|
+
classes: meta.attrs && meta.attrs.class || "",
|
|
382
|
+
sawContent: false,
|
|
373
383
|
meta: { name: meta.name || null, attrs: { ...meta.attrs || {} } },
|
|
374
384
|
openToken: t
|
|
375
385
|
}, t);
|
|
376
386
|
}
|
|
387
|
+
function breakHost() {
|
|
388
|
+
return stack.get("section") || stack.get("page") || stack.get("spread");
|
|
389
|
+
}
|
|
390
|
+
function gridClassOf(frame) {
|
|
391
|
+
if (!frame || !frame.classes)
|
|
392
|
+
return null;
|
|
393
|
+
return frame.classes.split(/\s+/).find((c) => c.startsWith("gp-grid-")) || null;
|
|
394
|
+
}
|
|
395
|
+
function warnBreakInsideGrid(kind, line, meta) {
|
|
396
|
+
const host = breakHost();
|
|
397
|
+
const gridClass = gridClassOf(host);
|
|
398
|
+
if (!gridClass)
|
|
399
|
+
return;
|
|
400
|
+
warn(state.env, line, "break_inside_grid", `@${kind} inside a grid container: the enclosing @${host.kind} carries .${gridClass}, so this break's <div> becomes a grid ITEM — it takes a cell of its own, corrupts the grid's placement, and print and the live preview then disagree about which page the content after it lands on. Move the break outside the grid @${host.kind}, or remove it — grid rows already flow and fragment across pages on their own.`, meta);
|
|
401
|
+
}
|
|
402
|
+
function warnIfEmptyDecoratedSection(closingKind, closingLine) {
|
|
403
|
+
const sec = stack.get("section");
|
|
404
|
+
if (!sec || sec.sawContent)
|
|
405
|
+
return;
|
|
406
|
+
const attrs = sec.meta && sec.meta.attrs || {};
|
|
407
|
+
const decorations = [];
|
|
408
|
+
for (const c of (attrs.class || "").split(/\s+/).filter(Boolean))
|
|
409
|
+
decorations.push(`.${c}`);
|
|
410
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
411
|
+
if (k === "class")
|
|
412
|
+
continue;
|
|
413
|
+
decorations.push(k === "id" ? `#${v}` : `${k}=${v}`);
|
|
414
|
+
}
|
|
415
|
+
if (!decorations.length)
|
|
416
|
+
return;
|
|
417
|
+
const openLine = sec.openToken && sec.openToken.meta && Number.isFinite(sec.openToken.meta.line) ? sec.openToken.meta.line : closingLine;
|
|
418
|
+
warn(state.env, openLine, "empty_section", `This @section (${decorations.join(" ")}) was closed by the @${closingKind} on line ${closingLine} with no content between the two markers, so its styling applies to an empty element and nothing prints the layout it asked for. Delete one of the two markers, or move the content that belongs inside the section between them.`, null);
|
|
419
|
+
}
|
|
377
420
|
for (let i = 0;i < state.tokens.length; i++) {
|
|
378
421
|
const tok = state.tokens[i];
|
|
379
422
|
if (tok.type !== "layout_marker") {
|
|
423
|
+
const openSectionFrame = stack.get("section");
|
|
424
|
+
if (openSectionFrame)
|
|
425
|
+
openSectionFrame.sawContent = true;
|
|
380
426
|
out.push(tok);
|
|
381
427
|
continue;
|
|
382
428
|
}
|
|
@@ -402,6 +448,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
402
448
|
continue;
|
|
403
449
|
}
|
|
404
450
|
if (kind === "section") {
|
|
451
|
+
warnIfEmptyDecoratedSection("section", line);
|
|
405
452
|
stack.close("section");
|
|
406
453
|
const spread = stack.get("spread");
|
|
407
454
|
if (spread && spread.noPagesYet) {
|
|
@@ -430,6 +477,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
430
477
|
continue;
|
|
431
478
|
}
|
|
432
479
|
if (kind === "page-break") {
|
|
480
|
+
warnBreakInsideGrid("page-break", line, meta);
|
|
433
481
|
const t = new state.Token("layout_page_break", "div", 0);
|
|
434
482
|
t.meta = { line };
|
|
435
483
|
t.attrSet("class", "gp-page-break");
|
|
@@ -438,6 +486,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
438
486
|
continue;
|
|
439
487
|
}
|
|
440
488
|
if (kind === "column-break") {
|
|
489
|
+
warnBreakInsideGrid("column-break", line, meta);
|
|
441
490
|
const openSectionFrame = stack.get("section");
|
|
442
491
|
if (openSectionFrame && openSectionFrame.openToken && !openSectionFrame.openToken.meta.hasColumnBreak) {
|
|
443
492
|
const sectionCls = openSectionFrame.openToken.attrGet("class") || "";
|
|
@@ -453,6 +502,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
453
502
|
continue;
|
|
454
503
|
}
|
|
455
504
|
if (kind === "end-section") {
|
|
505
|
+
warnIfEmptyDecoratedSection("end-section", line);
|
|
456
506
|
stack.close("section");
|
|
457
507
|
continue;
|
|
458
508
|
}
|
|
@@ -540,7 +590,7 @@ body { margin: 0; }
|
|
|
540
590
|
.gp-page-break { break-before: page; }
|
|
541
591
|
.page { break-before: page; }
|
|
542
592
|
.spread { break-before: page; }
|
|
543
|
-
:where(.page, .spread) { position: relative; }
|
|
593
|
+
:where(.page, .spread) { position: relative; display: flow-root; box-sizing: border-box; min-height: calc(var(--gp-content-h, 1px) - 1px); }
|
|
544
594
|
.gp-column-break { break-after: column; height: 0; font-size: 0; line-height: 0; visibility: hidden; }
|
|
545
595
|
|
|
546
596
|
:where(h1,h2,h3,h4,h5,h6) { break-after: avoid; }
|
|
@@ -624,6 +674,26 @@ var GUTTERPRESS_CSS = `
|
|
|
624
674
|
.gp-columns-2 { columns: 2; column-gap: var(--gp-column-gap, 1.5em); }
|
|
625
675
|
.gp-columns-3 { columns: 3; column-gap: var(--gp-column-gap, 1.5em); }
|
|
626
676
|
|
|
677
|
+
/* grid runs — the SLOTTED counterpart to the column runs above. Grid places
|
|
678
|
+
each child into the next cell, across then down (deterministic slots: card
|
|
679
|
+
layouts, stat blocks, image-plus-caption pairs); columns FLOW one text run
|
|
680
|
+
down then across. Same neutral-primitive rationale as .gp-columns-*, and
|
|
681
|
+
permanent vocabulary for the same reason: standard CSS Grid verbatim, no
|
|
682
|
+
spec gap to remove later. MEASURED (Chromium 151, gp-grid evidence pack):
|
|
683
|
+
grid rows fragment across sheets with EXACT print/viewer parity — 2- and
|
|
684
|
+
3-col, unequal item heights, mid-row cuts, multi-sheet overflow,
|
|
685
|
+
break-inside:avoid, gap geometry — so a grid taller than the page is safe,
|
|
686
|
+
no fit-one-page constraint. Two things to know, not fix:
|
|
687
|
+
- on a min-height page root (MARKER_CSS), default align-content
|
|
688
|
+
stretches rows apart to fill the page — identically in both engines.
|
|
689
|
+
Authors wanting packed rows set align-content: start.
|
|
690
|
+
- a @page-break / @column-break marker DIRECTLY inside a grid container
|
|
691
|
+
becomes a grid item and corrupts placement (the one measured parity
|
|
692
|
+
break); markers.js diagnoses it (break_inside_grid).
|
|
693
|
+
--gp-grid-gap is author-settable. */
|
|
694
|
+
.gp-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gp-grid-gap, 1.5em); }
|
|
695
|
+
.gp-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gp-grid-gap, 1.5em); }
|
|
696
|
+
|
|
627
697
|
/* shape wrap — text follows the image's alpha silhouette instead of its
|
|
628
698
|
rectangular box. shape-outside only applies to floats, so this is inert
|
|
629
699
|
without .gp-left/.gp-right (and under .gp-pin, which un-floats). The
|
|
@@ -686,8 +756,14 @@ img.gp-shape {
|
|
|
686
756
|
opacity, filter, transform on it traps the negative layer inside).
|
|
687
757
|
Core keeps .page/.spread at 'position: relative; z-index: auto'
|
|
688
758
|
precisely so they are not stacking contexts.
|
|
689
|
-
- a clipping ancestor (overflow other than visible)
|
|
690
|
-
|
|
759
|
+
- a clipping ancestor (overflow other than visible) — but only where
|
|
760
|
+
the art actually overhangs that ancestor's clip box on a clipped
|
|
761
|
+
axis: the overhang is cut off, the same mechanism that clips a
|
|
762
|
+
.gp-bleed plate back to the wrapper's width. Clipping never reorders
|
|
763
|
+
layers — within-bounds art under a clipping .page prints whole and
|
|
764
|
+
still behind (measured; see the build audit's comment in
|
|
765
|
+
engine/compiler/build.ts), and a static wrapper's overflow never
|
|
766
|
+
binds an abspos .gp-pin at all.
|
|
691
767
|
The build-time engine.layer.trapped audit reports both against the live
|
|
692
768
|
ancestor chain. printsafe/page-containment is only an early source hint for
|
|
693
769
|
declarations written directly on .page/.spread. */
|
|
@@ -5,17 +5,17 @@ import {
|
|
|
5
5
|
isUnbornRepo,
|
|
6
6
|
repairRepo,
|
|
7
7
|
verifyRepoReadable
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-eq5naw4m.js";
|
|
9
9
|
import {
|
|
10
10
|
FileTokenStore,
|
|
11
11
|
defaultConfigDir
|
|
12
|
-
} from "./cli-
|
|
12
|
+
} from "./cli-cqtggsng.js";
|
|
13
13
|
import {
|
|
14
14
|
UsageError,
|
|
15
15
|
rejectExtraPositionals,
|
|
16
16
|
rejectUnknownFlags
|
|
17
|
-
} from "./cli-
|
|
18
|
-
import"./cli-
|
|
17
|
+
} from "./cli-149edp6b.js";
|
|
18
|
+
import"./cli-zfcryxg8.js";
|
|
19
19
|
import"./cli-c41yr7he.js";
|
|
20
20
|
import"./cli-46ycxe6r.js";
|
|
21
21
|
import"./cli-k1065rkg.js";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
executeAndReport
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-eq5naw4m.js";
|
|
4
4
|
import {
|
|
5
5
|
log
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-cqtggsng.js";
|
|
7
7
|
import {
|
|
8
8
|
UsageError,
|
|
9
9
|
rejectExtraPositionals,
|
|
10
10
|
rejectUnknownFlags
|
|
11
|
-
} from "./cli-
|
|
12
|
-
import"./cli-
|
|
11
|
+
} from "./cli-149edp6b.js";
|
|
12
|
+
import"./cli-zfcryxg8.js";
|
|
13
13
|
import"./cli-c41yr7he.js";
|
|
14
14
|
import {
|
|
15
15
|
EXIT_CODES
|
package/package.json
CHANGED