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.
Files changed (59) hide show
  1. package/README.md +10 -3
  2. package/dist/README-aaqapam1.md +186 -0
  3. package/dist/README-mdq34cmb.md +155 -0
  4. package/dist/api/index.d.ts +6 -2
  5. package/dist/api/index.js +30 -2
  6. package/dist/{audit-19e2431j.js → audit-m3ce9pvq.js} +4 -4
  7. package/dist/base-ypqh9n7f.css +123 -0
  8. package/dist/book-ted3tzrp.css +58 -0
  9. package/dist/{build-gqqew2vr.js → build-qnecb3a1.js} +4 -4
  10. package/dist/callout-hv56m16h.md +7 -0
  11. package/dist/{cli-kzf64c2g.js → cli-1hx14cgk.js} +946 -401
  12. package/dist/{cli-s8q42x3r.js → cli-4vtgt2ng.js} +78 -2
  13. package/dist/{cli-h8ejbtyr.js → cli-vcv5ewqj.js} +1 -1
  14. package/dist/{cli-pvk2s9d7.js → cli-vxf151ea.js} +1 -1
  15. package/dist/cli.js +16 -16
  16. package/dist/components-0mmswvvm.yaml +51 -0
  17. package/dist/components-aam0fe2z.css +94 -0
  18. package/dist/{doctor-3ccz35yz.js → doctor-qvqye1wd.js} +2 -2
  19. package/dist/{engine-pb6h089e.js → engine-daad33qn.js} +1 -1
  20. package/dist/{engine-88mynx9a.js → engine-db8632w6.js} +2 -2
  21. package/dist/expected-ycmacem8.html +9 -0
  22. package/dist/fixture-2qnyqgb5.md +10 -0
  23. package/dist/gutterpress-m98e2w2d.json +8 -0
  24. package/dist/gutterpress-x2ycdsb8.json +17 -0
  25. package/dist/{index-837htyjy.js → index-9pbaznww.js} +78 -2
  26. package/dist/{index-r4ny4fyr.js → index-dfw8hn8q.js} +1147 -523
  27. package/dist/{index-a0xpm188.js → index-rpt4vtby.js} +1 -1
  28. package/dist/index.js +31 -3
  29. package/dist/lib/build-runner.d.ts +52 -0
  30. package/dist/lib/extension-manifest.d.ts +164 -0
  31. package/dist/lib/extension-scaffold.d.ts +65 -0
  32. package/dist/lib/lint-runner.d.ts +12 -0
  33. package/dist/lib/markdown/assemble.d.ts +12 -0
  34. package/dist/lib/markdown/markers.d.ts +20 -0
  35. package/dist/lib/markdown/plugins.d.ts +1 -1
  36. package/dist/lib/markdown/renderer.d.ts +99 -0
  37. package/dist/lib/snippets.d.ts +121 -5
  38. package/dist/lib/theme-import.d.ts +8 -4
  39. package/dist/lib/theme-manager.d.ts +52 -39
  40. package/dist/lib/validation-exec.d.ts +12 -0
  41. package/dist/{lint-mpz7vfc8.js → lint-3p1s8agk.js} +4 -4
  42. package/dist/{manifest.schema-kwnrwv8b.json → manifest.schema-1q35yxs9.json} +2 -2
  43. package/dist/{new-nbrpsw7t.js → new-xffarp40.js} +111 -6
  44. package/dist/package-rm9d2tht.json +29 -0
  45. package/dist/page-rules-nsj3aw42.css +128 -0
  46. package/dist/page-templates-7md4ev7d.css +60 -0
  47. package/dist/{plugin-gg5mk26h.js → plugin-b05ck7zd.js} +4 -4
  48. package/dist/plugin-m2w7g47v.css +114 -0
  49. package/dist/plugin.js-aszz0zgh.tpl +172 -0
  50. package/dist/plugin.test.js-mv0ghv5n.tpl +232 -0
  51. package/dist/{preflight-jnmd4mg2.js → preflight-z6xpty9w.js} +4 -4
  52. package/dist/{preview-hsr9d12d.js → preview-xa1n7why.js} +4 -4
  53. package/dist/{publish-pcrrn6fd.js → publish-zgmr99kd.js} +4 -4
  54. package/dist/render.js +275 -8
  55. package/dist/term-box-4y0w7y32.md +7 -0
  56. package/dist/{theme-6fa0yjzq.js → theme-m9dtxkhj.js} +4 -4
  57. package/dist/tokens-e4xk3x4v.css +86 -0
  58. package/dist/{validate-7n25fx0g.js → validate-43hqgzz5.js} +4 -4
  59. 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-kzf64c2g.js";
5
- import"./cli-pvk2s9d7.js";
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-h8ejbtyr.js";
12
- import"./cli-s8q42x3r.js";
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
+ };