gutterpress 0.11.18-alpha.3 → 0.11.18-alpha.5
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 +2 -2
- package/dist/{build-5n7wpqg3.js → build-hc743bws.js} +4 -4
- package/dist/{cli-12aq78aq.js → cli-bx24fj78.js} +1 -1
- package/dist/{cli-zrs92eef.js → cli-dm3pa0cw.js} +2 -2
- package/dist/{cli-72r4c2k4.js → cli-dxs69beq.js} +415 -44
- package/dist/{cli-z835nehj.js → cli-q90qmcvp.js} +1 -1
- package/dist/cli.js +12 -12
- package/dist/{doctor-e3zkvkkh.js → doctor-vq5xetqk.js} +2 -2
- package/dist/{engine-17gjf7t0.js → engine-vvtkew4q.js} +1 -1
- package/dist/{engine-68avsbe2.js → engine-wjz5yhmp.js} +2 -2
- package/dist/{ext-rcf6hfhq.js → ext-zspsqk4y.js} +4 -4
- package/dist/{index-q88g67zg.js → index-89zg3jtk.js} +1 -1
- package/dist/{index-b81jc99n.js → index-pb4fj8m2.js} +2 -2
- package/dist/{index-rpc74pqb.js → index-r2ry6j3x.js} +416 -45
- package/dist/index.js +3 -3
- package/dist/lib/markdown/block-names.d.ts +82 -0
- package/dist/lib/markdown/renderer.d.ts +3 -2
- package/dist/lib/markdown/table-align.d.ts +32 -0
- package/dist/lib/printsafe.d.ts +2 -0
- package/dist/lib/unknown-classes.d.ts +12 -0
- package/dist/{manifest.schema-8m82f37q.json → manifest.schema-nc30dyfs.json} +1 -1
- package/dist/{new-6pm52vh7.js → new-zrvm9vpe.js} +4 -4
- package/dist/{preflight-d84a2ab9.js → preflight-h003cdh9.js} +4 -4
- package/dist/{preview-9s4ktkbk.js → preview-xd02xc2h.js} +4 -4
- package/dist/{publish-wcrtgxgk.js → publish-1yzay4z4.js} +4 -4
- package/dist/render.js +199 -28
- package/dist/{theme-j2bagrfx.css → theme-5729pyq1.css} +4 -1
- package/dist/{validate-q9c00m8g.js → validate-gmma4d2t.js} +4 -4
- 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 }`
|
|
93
|
-
*
|
|
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";
|
package/dist/lib/printsafe.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ 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";
|
|
6
|
+
export declare const ruleEngineHyphenation = "printsafe/engine-hyphenation";
|
|
5
7
|
export interface PrintSafeWarning {
|
|
6
8
|
rule: string;
|
|
7
9
|
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/engine-hyphenation, 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-
|
|
10
|
+
} from "./cli-dxs69beq.js";
|
|
11
11
|
import {
|
|
12
12
|
resolveGhostscript
|
|
13
|
-
} from "./cli-
|
|
13
|
+
} from "./cli-bx24fj78.js";
|
|
14
14
|
import {
|
|
15
15
|
UsageError,
|
|
16
16
|
rejectExtraPositionals,
|
|
17
17
|
rejectUnknownFlags
|
|
18
|
-
} from "./cli-
|
|
18
|
+
} from "./cli-q90qmcvp.js";
|
|
19
19
|
import"./cli-ht1bcqdd.js";
|
|
20
|
-
import"./cli-
|
|
20
|
+
import"./cli-dm3pa0cw.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-
|
|
5
|
+
} from "./cli-dxs69beq.js";
|
|
6
6
|
import {
|
|
7
7
|
log
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-bx24fj78.js";
|
|
9
9
|
import {
|
|
10
10
|
UsageError,
|
|
11
11
|
rejectExtraPositionals,
|
|
12
12
|
rejectUnknownFlags
|
|
13
|
-
} from "./cli-
|
|
13
|
+
} from "./cli-q90qmcvp.js";
|
|
14
14
|
import"./cli-ht1bcqdd.js";
|
|
15
|
-
import"./cli-
|
|
15
|
+
import"./cli-dm3pa0cw.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-
|
|
10
|
+
} from "./cli-dxs69beq.js";
|
|
11
11
|
import {
|
|
12
12
|
log
|
|
13
|
-
} from "./cli-
|
|
13
|
+
} from "./cli-bx24fj78.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-
|
|
21
|
+
} from "./cli-q90qmcvp.js";
|
|
22
22
|
import"./cli-ht1bcqdd.js";
|
|
23
|
-
import"./cli-
|
|
23
|
+
import"./cli-dm3pa0cw.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-
|
|
14
|
+
} from "./cli-dxs69beq.js";
|
|
15
15
|
import {
|
|
16
16
|
FileTokenStore,
|
|
17
17
|
log
|
|
18
|
-
} from "./cli-
|
|
18
|
+
} from "./cli-bx24fj78.js";
|
|
19
19
|
import {
|
|
20
20
|
UsageError,
|
|
21
21
|
rejectExtraPositionals,
|
|
22
22
|
rejectUnknownFlags
|
|
23
|
-
} from "./cli-
|
|
23
|
+
} from "./cli-q90qmcvp.js";
|
|
24
24
|
import"./cli-ht1bcqdd.js";
|
|
25
|
-
import"./cli-
|
|
25
|
+
import"./cli-dm3pa0cw.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 === "
|
|
89
|
+
if (kind === "continue" || kind.startsWith("end-")) {
|
|
90
90
|
return { kind, name: null, attrs: {} };
|
|
91
91
|
}
|
|
92
|
-
const
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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(`
|
|
@@ -44,7 +44,10 @@ h1 { font-size: var(--fs-h1); margin: 0 0 0.6em; }
|
|
|
44
44
|
h2 { font-size: var(--fs-h2); margin: 1.4em 0 0.4em; }
|
|
45
45
|
h3 { font-size: var(--fs-h3); margin: 1.2em 0 0.3em; }
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
/* hyphens: manual, not auto: auto breaks words only where the running Chromium
|
|
48
|
+
ships dictionaries (desktop app and CLI differ), which changes the page count.
|
|
49
|
+
Authors add soft hyphens (­) where they want a break. */
|
|
50
|
+
p { margin: 0 0 0.8em; text-align: justify; hyphens: manual; }
|
|
48
51
|
|
|
49
52
|
a { color: var(--color-accent); text-decoration: none; }
|
|
50
53
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
executeAndReport
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-dxs69beq.js";
|
|
4
4
|
import {
|
|
5
5
|
log
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-bx24fj78.js";
|
|
7
7
|
import {
|
|
8
8
|
UsageError,
|
|
9
9
|
rejectExtraPositionals,
|
|
10
10
|
rejectUnknownFlags
|
|
11
|
-
} from "./cli-
|
|
11
|
+
} from "./cli-q90qmcvp.js";
|
|
12
12
|
import"./cli-ht1bcqdd.js";
|
|
13
|
-
import"./cli-
|
|
13
|
+
import"./cli-dm3pa0cw.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.
|
|
3
|
+
"version": "0.11.18-alpha.5",
|
|
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",
|