gutterpress 0.10.7 → 0.10.8
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 +10 -3
- package/dist/README-aaqapam1.md +186 -0
- package/dist/README-mdq34cmb.md +155 -0
- package/dist/api/index.d.ts +6 -2
- package/dist/api/index.js +30 -2
- package/dist/{audit-19e2431j.js → audit-m3ce9pvq.js} +4 -4
- package/dist/base-ypqh9n7f.css +123 -0
- package/dist/book-ted3tzrp.css +58 -0
- package/dist/{build-gqqew2vr.js → build-qnecb3a1.js} +4 -4
- package/dist/callout-hv56m16h.md +7 -0
- package/dist/{cli-kzf64c2g.js → cli-1hx14cgk.js} +946 -401
- package/dist/{cli-s8q42x3r.js → cli-4vtgt2ng.js} +78 -2
- package/dist/{cli-h8ejbtyr.js → cli-vcv5ewqj.js} +1 -1
- package/dist/{cli-pvk2s9d7.js → cli-vxf151ea.js} +1 -1
- package/dist/cli.js +16 -16
- package/dist/components-0mmswvvm.yaml +51 -0
- package/dist/components-aam0fe2z.css +94 -0
- package/dist/{doctor-3ccz35yz.js → doctor-qvqye1wd.js} +2 -2
- package/dist/{engine-pb6h089e.js → engine-daad33qn.js} +1 -1
- package/dist/{engine-88mynx9a.js → engine-db8632w6.js} +2 -2
- package/dist/expected-ycmacem8.html +9 -0
- package/dist/fixture-2qnyqgb5.md +10 -0
- package/dist/gutterpress-m98e2w2d.json +8 -0
- package/dist/gutterpress-x2ycdsb8.json +17 -0
- package/dist/{index-837htyjy.js → index-9pbaznww.js} +78 -2
- package/dist/{index-r4ny4fyr.js → index-dfw8hn8q.js} +1147 -523
- package/dist/{index-a0xpm188.js → index-rpt4vtby.js} +1 -1
- package/dist/index.js +31 -3
- package/dist/lib/build-runner.d.ts +52 -0
- package/dist/lib/extension-manifest.d.ts +164 -0
- package/dist/lib/extension-scaffold.d.ts +65 -0
- package/dist/lib/lint-runner.d.ts +12 -0
- package/dist/lib/markdown/assemble.d.ts +12 -0
- package/dist/lib/markdown/markers.d.ts +20 -0
- package/dist/lib/markdown/plugins.d.ts +1 -1
- package/dist/lib/markdown/renderer.d.ts +99 -0
- package/dist/lib/snippets.d.ts +121 -5
- package/dist/lib/theme-import.d.ts +8 -4
- package/dist/lib/theme-manager.d.ts +52 -39
- package/dist/lib/validation-exec.d.ts +12 -0
- package/dist/{lint-mpz7vfc8.js → lint-3p1s8agk.js} +4 -4
- package/dist/{manifest.schema-kwnrwv8b.json → manifest.schema-1q35yxs9.json} +2 -2
- package/dist/{new-nbrpsw7t.js → new-xffarp40.js} +111 -6
- package/dist/package-rm9d2tht.json +29 -0
- package/dist/page-rules-nsj3aw42.css +128 -0
- package/dist/page-templates-7md4ev7d.css +60 -0
- package/dist/{plugin-gg5mk26h.js → plugin-b05ck7zd.js} +4 -4
- package/dist/plugin-m2w7g47v.css +114 -0
- package/dist/plugin.js-aszz0zgh.tpl +172 -0
- package/dist/plugin.test.js-mv0ghv5n.tpl +232 -0
- package/dist/{preflight-jnmd4mg2.js → preflight-z6xpty9w.js} +4 -4
- package/dist/{preview-hsr9d12d.js → preview-xa1n7why.js} +4 -4
- package/dist/{publish-pcrrn6fd.js → publish-zgmr99kd.js} +4 -4
- package/dist/render.js +275 -8
- package/dist/term-box-4y0w7y32.md +7 -0
- package/dist/{theme-6fa0yjzq.js → theme-m9dtxkhj.js} +4 -4
- package/dist/tokens-e4xk3x4v.css +86 -0
- package/dist/{validate-7n25fx0g.js → validate-43hqgzz5.js} +4 -4
- package/package.json +1 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{SLUG}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "{{DESCRIPTION}}",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "plugin.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"gutterpress.json",
|
|
9
|
+
"plugin.js",
|
|
10
|
+
"styles",
|
|
11
|
+
"snippets",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"keywords": ["gutterpress", "gutterpress-plugin", "markdown-it-plugin"],
|
|
15
|
+
"author": "{{AUTHOR}}",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "bun test"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"markdown-it": "^14.1.0"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"markdown-it": ">=14"
|
|
25
|
+
},
|
|
26
|
+
"peerDependenciesMeta": {
|
|
27
|
+
"markdown-it": { "optional": true }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/* ===========================================================================
|
|
2
|
+
* {{NAME}} — 5. PAGE RULES
|
|
3
|
+
*
|
|
4
|
+
* OWNS
|
|
5
|
+
* The CSS `@page` at-rules: the sheet's own geometry (margins), the margin
|
|
6
|
+
* boxes that carry running heads and folios, and the named-page wiring that
|
|
7
|
+
* connects a markdown `@page .name` to a CSS `@page name`.
|
|
8
|
+
*
|
|
9
|
+
* This is standard CSS Paged Media and GCPM. Everything here is what the
|
|
10
|
+
* spec says, not something Gutterpress invented — `string-set`,
|
|
11
|
+
* `target-counter()` and the margin boxes all work the way they are
|
|
12
|
+
* written down.
|
|
13
|
+
*
|
|
14
|
+
* MUST NOT CONTAIN
|
|
15
|
+
* - Ordinary element or component rules. `@page` is a separate cascade;
|
|
16
|
+
* mixing the two here is how a stylesheet becomes impossible to reason
|
|
17
|
+
* about. The two element rules that ARE here — `string-set` on `h1` and
|
|
18
|
+
* `page:` on the opener template — are the wiring the margin boxes read,
|
|
19
|
+
* not styling; they belong with the boxes that consume them.
|
|
20
|
+
* - Component or template styling of any kind.
|
|
21
|
+
*
|
|
22
|
+
* ONE THING WORTH KNOWING: `@page` at-rules DO work inside a cascade layer —
|
|
23
|
+
* verified against the engine, not assumed. Keeping them in `pages` means a
|
|
24
|
+
* book's own `@page` rule in book.css (a later layer) wins over this one
|
|
25
|
+
* without a specificity fight, which is the whole point of the arrangement.
|
|
26
|
+
* ======================================================================== */
|
|
27
|
+
|
|
28
|
+
@layer pages {
|
|
29
|
+
/* -------------------------------------------------------------------------
|
|
30
|
+
* TRIM SIZE. This is the physical size of the printed page, and it has to
|
|
31
|
+
* be declared in CSS: with no `size:` anywhere, Chromium falls back to US
|
|
32
|
+
* Letter and prints a book at the wrong trim with no error at all. That is
|
|
33
|
+
* the single most expensive silent failure in this whole stack, which is
|
|
34
|
+
* why it is the first thing in the file.
|
|
35
|
+
*
|
|
36
|
+
* KEEP IT IN STEP WITH THE MANIFEST. The book's `preset:`/`page:` is what
|
|
37
|
+
* validation, the publish targets and the PDF/X boxes measure against, and
|
|
38
|
+
* `gutterpress validate` fails the build when the produced PDF does not
|
|
39
|
+
* match it. So the manifest is the source of truth and this line is the
|
|
40
|
+
* instruction that satisfies it — change one and change the other.
|
|
41
|
+
*
|
|
42
|
+
* 6in × 9in is the neutral trade-book size the `book` preset declares.
|
|
43
|
+
* Common alternatives: 5.5in 8.5in (digest), 8.5in 11in (US Letter),
|
|
44
|
+
* 210mm 297mm (A4).
|
|
45
|
+
*
|
|
46
|
+
* Deliberately a literal, not `var(--…)`. A custom property does resolve
|
|
47
|
+
* here, but an indirection whose failure mode is "silently prints Letter"
|
|
48
|
+
* is not worth the tidiness.
|
|
49
|
+
* ---------------------------------------------------------------------- */
|
|
50
|
+
@page {
|
|
51
|
+
size: 6in 9in;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Mirrored margins for a bound book: the inner edge needs the extra room,
|
|
55
|
+
and which edge is "inner" flips every page. */
|
|
56
|
+
@page :left {
|
|
57
|
+
margin: var(--{{PREFIX}}page-margin-top)
|
|
58
|
+
var(--{{PREFIX}}page-margin-inner)
|
|
59
|
+
var(--{{PREFIX}}page-margin-bottom)
|
|
60
|
+
var(--{{PREFIX}}page-margin-outer);
|
|
61
|
+
|
|
62
|
+
@bottom-left {
|
|
63
|
+
content: counter(page);
|
|
64
|
+
font-family: var(--{{PREFIX}}font-body);
|
|
65
|
+
font-size: var(--{{PREFIX}}fs-small);
|
|
66
|
+
color: var(--{{PREFIX}}ink-soft);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* The running head. `string(chaptertitle)` is fed by the `string-set`
|
|
70
|
+
further down — the standard GCPM mechanism.
|
|
71
|
+
|
|
72
|
+
For the book-title-on-verso convention instead, add a second named
|
|
73
|
+
string (`.title-page h1 { string-set: booktitle content(text); }`) and
|
|
74
|
+
read `string(booktitle)` here. Do not reference a named string nothing
|
|
75
|
+
sets: it resolves to empty and the head silently disappears. */
|
|
76
|
+
@top-left {
|
|
77
|
+
content: string(chaptertitle);
|
|
78
|
+
font-family: var(--{{PREFIX}}font-display);
|
|
79
|
+
font-size: var(--{{PREFIX}}fs-small);
|
|
80
|
+
color: var(--{{PREFIX}}ink-soft);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@page :right {
|
|
85
|
+
margin: var(--{{PREFIX}}page-margin-top)
|
|
86
|
+
var(--{{PREFIX}}page-margin-outer)
|
|
87
|
+
var(--{{PREFIX}}page-margin-bottom)
|
|
88
|
+
var(--{{PREFIX}}page-margin-inner);
|
|
89
|
+
|
|
90
|
+
@bottom-right {
|
|
91
|
+
content: counter(page);
|
|
92
|
+
font-family: var(--{{PREFIX}}font-body);
|
|
93
|
+
font-size: var(--{{PREFIX}}fs-small);
|
|
94
|
+
color: var(--{{PREFIX}}ink-soft);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* The recto carries the current chapter — `string(chaptertitle)` picks up
|
|
98
|
+
the most recent `h1` on or before this page. */
|
|
99
|
+
@top-right {
|
|
100
|
+
content: string(chaptertitle);
|
|
101
|
+
font-family: var(--{{PREFIX}}font-display);
|
|
102
|
+
font-size: var(--{{PREFIX}}fs-small);
|
|
103
|
+
color: var(--{{PREFIX}}ink-soft);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Feed the running heads. `string-set` is the standard GCPM mechanism:
|
|
108
|
+
the element's text becomes a named string the margin boxes can read. */
|
|
109
|
+
h1 { string-set: chaptertitle content(text); }
|
|
110
|
+
|
|
111
|
+
/* A chapter opener gets no running head and no folio — the convention in
|
|
112
|
+
nearly every printed book. `@page .{{PREFIX}}opener` in markdown maps to
|
|
113
|
+
the named page below via `page:`. */
|
|
114
|
+
.page.{{PREFIX}}opener { page: {{PREFIX}}opener; }
|
|
115
|
+
|
|
116
|
+
@page {{PREFIX}}opener {
|
|
117
|
+
@top-left { content: none; }
|
|
118
|
+
@top-right { content: none; }
|
|
119
|
+
@bottom-left { content: none; }
|
|
120
|
+
@bottom-right { content: none; }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* First page of the book: no furniture at all. */
|
|
124
|
+
@page :first {
|
|
125
|
+
@top-left { content: none; }
|
|
126
|
+
@top-right { content: none; }
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* ===========================================================================
|
|
2
|
+
* {{NAME}} — 4. PAGE TEMPLATES
|
|
3
|
+
*
|
|
4
|
+
* OWNS
|
|
5
|
+
* The layout of a `@page .name` wrapper — how content is arranged WITHIN
|
|
6
|
+
* one page of the book. Columns, full-bleed art, a centred opener.
|
|
7
|
+
*
|
|
8
|
+
* Authored as:
|
|
9
|
+
*
|
|
10
|
+
* @page .{{PREFIX}}opener
|
|
11
|
+
* # Part One
|
|
12
|
+
* @end-page
|
|
13
|
+
*
|
|
14
|
+
* MUST NOT CONTAIN
|
|
15
|
+
* - `@page` at-rules. Confusingly, `@page .name` in MARKDOWN and `@page` in
|
|
16
|
+
* CSS are different things: the markdown marker makes a `div.page` you
|
|
17
|
+
* style with `.page.name`, while the CSS at-rule sets the sheet's own
|
|
18
|
+
* geometry. This file owns the first; page-rules.css owns the second.
|
|
19
|
+
* - Component styling. A template says where a component sits, never what
|
|
20
|
+
* it looks like.
|
|
21
|
+
* ======================================================================== */
|
|
22
|
+
|
|
23
|
+
@layer templates {
|
|
24
|
+
/* A part/section opener: content parked a third of the way down, centred,
|
|
25
|
+
with the rest of the sheet left empty on purpose. */
|
|
26
|
+
.page.{{PREFIX}}opener {
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
justify-content: center;
|
|
30
|
+
align-items: center;
|
|
31
|
+
text-align: center;
|
|
32
|
+
min-height: 60%;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.page.{{PREFIX}}opener h1 {
|
|
36
|
+
font-size: calc(var(--{{PREFIX}}fs-h1) * 1.35);
|
|
37
|
+
margin-block-end: var(--{{PREFIX}}space-2);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.page.{{PREFIX}}opener p {
|
|
41
|
+
text-indent: 0;
|
|
42
|
+
color: var(--{{PREFIX}}ink-soft);
|
|
43
|
+
max-width: 24em;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* A two-column reference page. Core already ships `.gp-columns-2` as
|
|
47
|
+
general author vocabulary — reach for that first, and use a named
|
|
48
|
+
template like this only when the page needs more than the columns. */
|
|
49
|
+
.page.{{PREFIX}}reference {
|
|
50
|
+
columns: 2;
|
|
51
|
+
column-gap: var(--{{PREFIX}}space-4);
|
|
52
|
+
column-rule: 1px solid var(--{{PREFIX}}rule);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.page.{{PREFIX}}reference h2 {
|
|
56
|
+
column-span: all;
|
|
57
|
+
border-block-end: 1px solid var(--{{PREFIX}}rule);
|
|
58
|
+
padding-block-end: var(--{{PREFIX}}space-1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
addNpmPlugin,
|
|
3
3
|
parseNpmPluginSpec
|
|
4
|
-
} from "./cli-
|
|
5
|
-
import"./cli-
|
|
4
|
+
} from "./cli-1hx14cgk.js";
|
|
5
|
+
import"./cli-vxf151ea.js";
|
|
6
6
|
import {
|
|
7
7
|
UsageError,
|
|
8
8
|
exitForUsage,
|
|
9
9
|
rejectExtraPositionals,
|
|
10
10
|
rejectUnknownFlags
|
|
11
|
-
} from "./cli-
|
|
12
|
-
import"./cli-
|
|
11
|
+
} from "./cli-vcv5ewqj.js";
|
|
12
|
+
import"./cli-4vtgt2ng.js";
|
|
13
13
|
import"./cli-c41yr7he.js";
|
|
14
14
|
import {
|
|
15
15
|
EXIT_CODES
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/* ===========================================================================
|
|
2
|
+
* {{NAME}} — component stylesheet
|
|
3
|
+
*
|
|
4
|
+
* OWNS
|
|
5
|
+
* The look of everything `plugin.js` emits: the `@term-box` container, its
|
|
6
|
+
* variants and label, and the `[[key term]]` inline span. Every one of
|
|
7
|
+
* those looks is driven by a PUBLIC custom property declared at `:root`
|
|
8
|
+
* below, so a book can retune this component without touching this file.
|
|
9
|
+
*
|
|
10
|
+
* MUST NOT CONTAIN
|
|
11
|
+
* - `@page` rules or page geometry. A plugin styles its own components; the
|
|
12
|
+
* book's theme owns the page.
|
|
13
|
+
* - Bare element selectors (`p`, `table`, `h2`). They would leak this
|
|
14
|
+
* plugin's opinions into every book that installs it. Scope every rule
|
|
15
|
+
* under a `{{PREFIX}}` class.
|
|
16
|
+
* - Any `gp-` class. That prefix is Gutterpress core's.
|
|
17
|
+
* - Per-book values. Ship sane defaults; let the book override the tokens.
|
|
18
|
+
*
|
|
19
|
+
* WHY THE @layer
|
|
20
|
+
* Gutterpress injects plugin CSS before the book's own stylesheets, and in
|
|
21
|
+
* CSS an UNLAYERED rule beats a layered one no matter how specific the
|
|
22
|
+
* layered one is. So an unlayered plugin sheet would quietly outrank every
|
|
23
|
+
* rule in a book that had adopted the recommended
|
|
24
|
+
* `@layer tokens, base, components, templates, pages, book;` convention —
|
|
25
|
+
* the author would edit their own CSS and nothing would happen.
|
|
26
|
+
*
|
|
27
|
+
* Declaring our own layer fixes that by construction. This sheet loads
|
|
28
|
+
* first, so `{{SLUG}}` becomes the FIRST (weakest) layer; every layer the
|
|
29
|
+
* book declares afterwards is appended after it and therefore wins, and the
|
|
30
|
+
* book's unlayered rules win too. A plugin should always be the easiest
|
|
31
|
+
* thing in the book to override.
|
|
32
|
+
*
|
|
33
|
+
* Adopt it for the WHOLE sheet: a rule left outside the layer here is
|
|
34
|
+
* unlayered, and would beat every rule inside it — including our own.
|
|
35
|
+
* ======================================================================== */
|
|
36
|
+
|
|
37
|
+
@layer {{SLUG}} {
|
|
38
|
+
/* -----------------------------------------------------------------------
|
|
39
|
+
* PUBLIC TOKENS — this component's entire theming surface.
|
|
40
|
+
*
|
|
41
|
+
* Declared at `:root` with real defaults so the component looks right in
|
|
42
|
+
* any book with no setup at all. A book retunes it by resetting these in
|
|
43
|
+
* its own CSS — either globally, or scoped to one chapter/page/section:
|
|
44
|
+
*
|
|
45
|
+
* #ch-appendix { --{{PREFIX}}term-box-accent: #7a1f1f; }
|
|
46
|
+
*
|
|
47
|
+
* Rules below consume them as BARE `var(--x)`, never `var(--x, fallback)`:
|
|
48
|
+
* the default lives at `:root` exactly once, so there is one place to look
|
|
49
|
+
* and no second copy to drift.
|
|
50
|
+
* -------------------------------------------------------------------- */
|
|
51
|
+
:root {
|
|
52
|
+
--{{PREFIX}}term-box-accent: #2f5d8a;
|
|
53
|
+
--{{PREFIX}}term-box-bg: #f2f6fa;
|
|
54
|
+
--{{PREFIX}}term-box-ink: inherit;
|
|
55
|
+
--{{PREFIX}}term-box-pad: 0.6em 0.9em;
|
|
56
|
+
--{{PREFIX}}term-box-rule: 3px;
|
|
57
|
+
--{{PREFIX}}term-box-label-size: 0.78em;
|
|
58
|
+
--{{PREFIX}}term-box-label-tracking: 0.08em;
|
|
59
|
+
--{{PREFIX}}term-ink: #2f5d8a;
|
|
60
|
+
--{{PREFIX}}term-weight: 600;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* -----------------------------------------------------------------------
|
|
64
|
+
* The container emitted by `@term-box` (plugin.js's `markers` table).
|
|
65
|
+
* -------------------------------------------------------------------- */
|
|
66
|
+
.{{PREFIX}}term-box {
|
|
67
|
+
background: var(--{{PREFIX}}term-box-bg);
|
|
68
|
+
border-left: var(--{{PREFIX}}term-box-rule) solid var(--{{PREFIX}}term-box-accent);
|
|
69
|
+
color: var(--{{PREFIX}}term-box-ink);
|
|
70
|
+
padding: var(--{{PREFIX}}term-box-pad);
|
|
71
|
+
/* Print hygiene: a callout that splits across a page break reads badly,
|
|
72
|
+
and a two-line orphan reads worse. Both are the component's business,
|
|
73
|
+
not the book's. */
|
|
74
|
+
break-inside: avoid;
|
|
75
|
+
margin-block: 1em;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.{{PREFIX}}term-box > :first-child { margin-block-start: 0; }
|
|
79
|
+
.{{PREFIX}}term-box > :last-child { margin-block-end: 0; }
|
|
80
|
+
|
|
81
|
+
/* The label element, present only when the author wrote `label="…"`. */
|
|
82
|
+
.{{PREFIX}}term-box-label {
|
|
83
|
+
color: var(--{{PREFIX}}term-box-accent);
|
|
84
|
+
font-size: var(--{{PREFIX}}term-box-label-size);
|
|
85
|
+
letter-spacing: var(--{{PREFIX}}term-box-label-tracking);
|
|
86
|
+
text-transform: uppercase;
|
|
87
|
+
font-weight: 700;
|
|
88
|
+
margin-block: 0 0.35em;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* -----------------------------------------------------------------------
|
|
92
|
+
* Variants. Each one only ever RESETS a public token — it never writes a
|
|
93
|
+
* second copy of the component's rules. That is what keeps a variant three
|
|
94
|
+
* lines long instead of thirty, and what lets a book add its own variant
|
|
95
|
+
* without this file changing.
|
|
96
|
+
* -------------------------------------------------------------------- */
|
|
97
|
+
.{{PREFIX}}term-box-note {
|
|
98
|
+
--{{PREFIX}}term-box-accent: #2f5d8a;
|
|
99
|
+
--{{PREFIX}}term-box-bg: #f2f6fa;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.{{PREFIX}}term-box-warning {
|
|
103
|
+
--{{PREFIX}}term-box-accent: #9a5b12;
|
|
104
|
+
--{{PREFIX}}term-box-bg: #fdf6ec;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* -----------------------------------------------------------------------
|
|
108
|
+
* The inline span emitted by `[[key term]]` (plugin.js's bespoke rule).
|
|
109
|
+
* -------------------------------------------------------------------- */
|
|
110
|
+
.{{PREFIX}}term {
|
|
111
|
+
color: var(--{{PREFIX}}term-ink);
|
|
112
|
+
font-weight: var(--{{PREFIX}}term-weight);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{NAME}} — a Gutterpress plugin.
|
|
3
|
+
*
|
|
4
|
+
* A Gutterpress plugin is a PLAIN markdown-it plugin: one function with the
|
|
5
|
+
* signature `(md, options) => void`. There is no Gutterpress plugin API, no
|
|
6
|
+
* base class, and no host-injected context object — which is exactly why any
|
|
7
|
+
* of the hundreds of markdown-it plugins on npm works here unchanged.
|
|
8
|
+
*
|
|
9
|
+
* Two ways to add a container, both used below:
|
|
10
|
+
*
|
|
11
|
+
* 1. DECLARATIVELY — `export const markers`. A table of marker names, each
|
|
12
|
+
* describing the wrapper element Gutterpress should emit. This is DATA
|
|
13
|
+
* the loader reads off this module; it is not an API call. Core's own
|
|
14
|
+
* marker parser then handles `@term-box` / `@end-term-box` through the
|
|
15
|
+
* exact same grammar, class merging and warning channel as `@section`.
|
|
16
|
+
* Reach for this whenever "wrap a block in an element with a class" is
|
|
17
|
+
* all you need.
|
|
18
|
+
*
|
|
19
|
+
* 2. BY HAND — an ordinary markdown-it rule, registered on `md` inside the
|
|
20
|
+
* default export. Reach for this when a declarative wrapper cannot
|
|
21
|
+
* express it: inline syntax (below), token rewriting, custom renderers.
|
|
22
|
+
*
|
|
23
|
+
* WHAT THIS FILE MUST NOT DO
|
|
24
|
+
*
|
|
25
|
+
* - It must not `import` from "gutterpress". Plugin code is resolved against
|
|
26
|
+
* the book's folder, and Gutterpress ships as a single compiled binary
|
|
27
|
+
* with no `node_modules` for that import to find. If you need a helper
|
|
28
|
+
* from core, inline a copy of it here. (Type-only imports are erased
|
|
29
|
+
* before runtime and are therefore safe — see README.md.)
|
|
30
|
+
* - It must not emit `gp-` classes. That prefix belongs to Gutterpress core.
|
|
31
|
+
* Everything this plugin emits carries its own prefix instead — see
|
|
32
|
+
* PREFIX below and `test/plugin.test.js`, which fails the build if a
|
|
33
|
+
* `gp-` class ever creeps in.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* THE load-bearing convention: every class, custom property and marker name
|
|
38
|
+
* this package emits starts with this prefix.
|
|
39
|
+
*
|
|
40
|
+
* Why it matters. A book loads core, a theme, and any number of plugins into
|
|
41
|
+
* ONE flat CSS namespace and ONE flat marker namespace. Nothing scopes them
|
|
42
|
+
* for you. A prefix is what keeps `{{SLUG}}`'s callout from colliding with
|
|
43
|
+
* some other plugin's callout — and `gp-` is reserved for core, so taking it
|
|
44
|
+
* would mean silently overriding Gutterpress's own vocabulary.
|
|
45
|
+
*
|
|
46
|
+
* Change it if you like — shorter reads better in CSS (`dc-` for Dimm City,
|
|
47
|
+
* say). It appears in three places, on purpose: here, in `styles/plugin.css`,
|
|
48
|
+
* and as `PREFIX` in `test/plugin.test.js`. The test file states the
|
|
49
|
+
* convention INDEPENDENTLY, so changing it in one place fails the suite
|
|
50
|
+
* instead of silently agreeing with itself.
|
|
51
|
+
*/
|
|
52
|
+
const PREFIX = "{{PREFIX}}";
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Declarative containers (the `markers` export).
|
|
56
|
+
*
|
|
57
|
+
* Every key becomes an author-facing marker: `@term-box` opens it and
|
|
58
|
+
* `@end-term-box` closes it — the closer is derived for you, never declared.
|
|
59
|
+
*
|
|
60
|
+
* @term-box warning label="Read this first"
|
|
61
|
+
* Ordinary **markdown** goes here.
|
|
62
|
+
* @end-term-box
|
|
63
|
+
*
|
|
64
|
+
* Fields, all optional:
|
|
65
|
+
* tag wrapper element (default "div")
|
|
66
|
+
* class base class on the wrapper
|
|
67
|
+
* variants extra classes keyed by the marker's bare word
|
|
68
|
+
* label a label element built from one of the marker's attributes
|
|
69
|
+
* autoCloseAt ["eof"] closes an unclosed container at end of file
|
|
70
|
+
*
|
|
71
|
+
* Names are validated when the book loads: lower-case letters, digits and
|
|
72
|
+
* hyphens; they may not start with `end-`, and they may not shadow a core
|
|
73
|
+
* marker (`@page`, `@section`, `@chapter`, …). A name two loaded plugins both
|
|
74
|
+
* declare is a hard error naming both sides — which is the other half of why
|
|
75
|
+
* the prefix convention exists.
|
|
76
|
+
*/
|
|
77
|
+
export const markers = {
|
|
78
|
+
"term-box": {
|
|
79
|
+
tag: "aside",
|
|
80
|
+
class: `${PREFIX}term-box`,
|
|
81
|
+
variants: {
|
|
82
|
+
note: `${PREFIX}term-box-note`,
|
|
83
|
+
warning: `${PREFIX}term-box-warning`,
|
|
84
|
+
},
|
|
85
|
+
label: {
|
|
86
|
+
tag: "p",
|
|
87
|
+
class: `${PREFIX}term-box-label`,
|
|
88
|
+
from: "attr:label",
|
|
89
|
+
},
|
|
90
|
+
autoCloseAt: ["eof"],
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/** The markdown-it inline rule's name, and the class it emits. */
|
|
95
|
+
const TERM_RULE = `${PREFIX}term`;
|
|
96
|
+
const TERM_TOKEN = `${PREFIX}term`;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The bespoke half: an inline rule turning `[[key term]]` into
|
|
100
|
+
* `<span class="{{PREFIX}}term">key term</span>`.
|
|
101
|
+
*
|
|
102
|
+
* Declarative markers cannot do this — they wrap BLOCKS, and this is inline
|
|
103
|
+
* syntax in the middle of a sentence. That is the dividing line: reach for
|
|
104
|
+
* `markers` for block containers, write a rule by hand for anything else.
|
|
105
|
+
*
|
|
106
|
+
* Returned from a factory so the emitted class can come from plugin options,
|
|
107
|
+
* and exported so `test/plugin.test.js` can drive it directly.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} className class placed on the emitted span
|
|
110
|
+
* @returns {(state: object, silent: boolean) => boolean} a markdown-it inline rule
|
|
111
|
+
*/
|
|
112
|
+
export function createTermRule(className) {
|
|
113
|
+
return function termRule(state, silent) {
|
|
114
|
+
const start = state.pos;
|
|
115
|
+
const max = state.posMax;
|
|
116
|
+
|
|
117
|
+
// Cheapest possible bail-out first: markdown-it runs this on every `[`.
|
|
118
|
+
if (start + 2 >= max) return false;
|
|
119
|
+
if (state.src.charCodeAt(start) !== 0x5b /* [ */) return false;
|
|
120
|
+
if (state.src.charCodeAt(start + 1) !== 0x5b /* [ */) return false;
|
|
121
|
+
|
|
122
|
+
const close = state.src.indexOf("]]", start + 2);
|
|
123
|
+
// The closing `]]` must exist AND sit inside the current inline span —
|
|
124
|
+
// `posMax` is not the end of the document, it is the end of what this
|
|
125
|
+
// inline context is allowed to consume.
|
|
126
|
+
if (close === -1 || close + 2 > max) return false;
|
|
127
|
+
|
|
128
|
+
const raw = state.src.slice(start + 2, close);
|
|
129
|
+
if (raw.includes("\n") || raw.includes("[") || raw.trim() === "") return false;
|
|
130
|
+
|
|
131
|
+
// `silent` means "validate only, emit nothing" — markdown-it uses it to
|
|
132
|
+
// probe. Honouring it is required of every inline rule.
|
|
133
|
+
if (!silent) {
|
|
134
|
+
const open = state.push(`${TERM_TOKEN}_open`, "span", 1);
|
|
135
|
+
open.attrSet("class", className);
|
|
136
|
+
const text = state.push("text", "", 0);
|
|
137
|
+
text.content = raw.trim();
|
|
138
|
+
state.push(`${TERM_TOKEN}_close`, "span", -1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
state.pos = close + 2;
|
|
142
|
+
return true;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The plugin function. This is the module's default export and the only thing
|
|
148
|
+
* Gutterpress requires of it.
|
|
149
|
+
*
|
|
150
|
+
* @param {import("markdown-it").default} md the markdown-it instance
|
|
151
|
+
* @param {{ termClass?: string }} [options] whatever the book's manifest put
|
|
152
|
+
* under this plugin's `options:` key
|
|
153
|
+
*/
|
|
154
|
+
export default function {{IDENT}}(md, options = {}) {
|
|
155
|
+
const termClass =
|
|
156
|
+
typeof options.termClass === "string" && options.termClass
|
|
157
|
+
? options.termClass
|
|
158
|
+
: `${PREFIX}term`;
|
|
159
|
+
|
|
160
|
+
// BEFORE "link", because `[[` would otherwise start being parsed as a link
|
|
161
|
+
// label and this rule would never see it.
|
|
162
|
+
md.inline.ruler.before("link", TERM_RULE, createTermRule(termClass));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Optional metadata. Gutterpress shows this in the desktop app's Extensions
|
|
167
|
+
* panel. Purely descriptive — nothing here changes how the plugin behaves.
|
|
168
|
+
*/
|
|
169
|
+
export const metadata = {
|
|
170
|
+
name: "{{NAME}}",
|
|
171
|
+
description: "{{DESCRIPTION}}",
|
|
172
|
+
};
|