@jarenjs/md 0.34.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.
Files changed (49) hide show
  1. package/README.md +520 -0
  2. package/dist/types/ast.d.ts +181 -0
  3. package/dist/types/bake.d.ts +61 -0
  4. package/dist/types/compiler.d.ts +141 -0
  5. package/dist/types/component/index.d.ts +101 -0
  6. package/dist/types/directives.d.ts +126 -0
  7. package/dist/types/entities.d.ts +40 -0
  8. package/dist/types/footnotes.d.ts +83 -0
  9. package/dist/types/frontmatter.d.ts +67 -0
  10. package/dist/types/html.d.ts +72 -0
  11. package/dist/types/index.d.ts +30 -0
  12. package/dist/types/loader.d.ts +84 -0
  13. package/dist/types/mdx.d.ts +45 -0
  14. package/dist/types/parser.d.ts +116 -0
  15. package/dist/types/plugins/highlight.d.ts +64 -0
  16. package/dist/types/plugins/index.d.ts +64 -0
  17. package/dist/types/plugins/mermaid.d.ts +12 -0
  18. package/dist/types/scanner.d.ts +240 -0
  19. package/dist/types/to-html.d.ts +104 -0
  20. package/dist/types/to-md.d.ts +23 -0
  21. package/dist/types/to-vnode.d.ts +161 -0
  22. package/dist/types/utils.d.ts +63 -0
  23. package/docs/LOADER.md +92 -0
  24. package/docs/MD-FORMAT.md +502 -0
  25. package/docs/PLUGINS.md +277 -0
  26. package/package.json +80 -0
  27. package/schemas/jaren-md-ast.schema.json +296 -0
  28. package/src/ast.js +346 -0
  29. package/src/bake.js +104 -0
  30. package/src/compiler.js +167 -0
  31. package/src/component/index.js +191 -0
  32. package/src/directives.js +371 -0
  33. package/src/entities.js +107 -0
  34. package/src/footnotes.js +180 -0
  35. package/src/frontmatter.js +947 -0
  36. package/src/html.js +281 -0
  37. package/src/index.js +76 -0
  38. package/src/loader.js +0 -0
  39. package/src/mdx.js +219 -0
  40. package/src/parser.js +1685 -0
  41. package/src/plugins/highlight.js +325 -0
  42. package/src/plugins/index.js +75 -0
  43. package/src/plugins/mermaid.js +14 -0
  44. package/src/scanner.js +832 -0
  45. package/src/to-html.js +425 -0
  46. package/src/to-md.js +396 -0
  47. package/src/to-vnode.js +766 -0
  48. package/src/utils.js +107 -0
  49. package/styles/md.css +238 -0
package/src/utils.js ADDED
@@ -0,0 +1,107 @@
1
+ //@ts-check
2
+ /**
3
+ * @file Small shared helpers for the md package.
4
+ *
5
+ * The content hash is the package's identity primitive — block vnode
6
+ * keys, the document `meta.hash`, and the mermaid SVG cache are all keyed
7
+ * by it — so md re-exports the suite's single `hashContent` from
8
+ * `@jarenjs/core` rather than carrying its own copy; equal content hits
9
+ * O(1) fast paths everywhere downstream. `fnv1a` is that same mixing
10
+ * step, exposed for the two callers that fold a hash incrementally (the
11
+ * streaming parser's chunks, the structural block-key walk) and so must
12
+ * seed it themselves from `FNV1A_OFFSET_BASIS`. Heading slugs come from
13
+ * the same place for the same reason: `slugify` is a pure text→fragment
14
+ * transform with no Markdown knowledge, so the suite keeps exactly one
15
+ * of it. The remaining helpers are md's own allocation-light scanner
16
+ * utilities.
17
+ */
18
+
19
+ import { slugify } from '@jarenjs/core/string';
20
+
21
+ export { hashContent, fnv1a, FNV1A_OFFSET_BASIS, slugify } from '@jarenjs/core/string';
22
+
23
+ /**
24
+ * The `id` for one heading, unique within one emission.
25
+ *
26
+ * Both emitters mint ids, so the rule lives here once: slug the text,
27
+ * substitute `section` when nothing slug-worthy survives, number
28
+ * repeats the way GitHub numbers them (`setup`, `setup-1`, `setup-2`)
29
+ * and prefix the result. The COUNTER belongs to the caller — one map per
30
+ * emission, never shared with another numbering (a block key's hash and
31
+ * a slug share a namespace only by accident, and a collision there would
32
+ * shift an unrelated heading's number).
33
+ *
34
+ * @param {string} text the heading's plain text (`textOf`)
35
+ * @param {Map<string, number>} seen the emission's slug counter
36
+ * @param {string} prefix prepended to the result
37
+ * @returns {string}
38
+ */
39
+ export function headingId(text, seen, prefix) {
40
+ const base = slugify(text) || 'section';
41
+ const count = seen.get(base) ?? 0;
42
+ seen.set(base, count + 1);
43
+ return prefix + (count === 0 ? base : base + '-' + count);
44
+ }
45
+
46
+ /**
47
+ * The accessible name for a heading's permalink affordance — `#` alone
48
+ * names nothing, so the link says which section it points at.
49
+ * @param {string} text the heading's plain text (`textOf`)
50
+ * @returns {string}
51
+ */
52
+ export function permalinkLabel(text) {
53
+ const trimmed = text.trim();
54
+ return trimmed === '' ? 'Permalink to this section' : 'Permalink to ' + trimmed;
55
+ }
56
+
57
+ /**
58
+ * Count leading space characters (U+0020 only; the scanner expands no
59
+ * tabs here — callers pass detabbed text).
60
+ * @param {string} line
61
+ * @returns {number}
62
+ */
63
+ export function countIndent(line) {
64
+ let i = 0;
65
+ while (i < line.length && line.charCodeAt(i) === 32) i++;
66
+ return i;
67
+ }
68
+
69
+ /**
70
+ * Is the line blank (empty or whitespace-only)?
71
+ * @param {string} line
72
+ * @returns {boolean}
73
+ */
74
+ export function isBlankLine(line) {
75
+ for (let i = 0; i < line.length; i++) {
76
+ const c = line.charCodeAt(i);
77
+ if (c !== 32 && c !== 9) return false;
78
+ }
79
+ return true;
80
+ }
81
+
82
+ /**
83
+ * Replace tabs with spaces to the next 4-column tab stop, counting
84
+ * columns from `startColumn`. Lines without tabs return the same
85
+ * string reference (the common case allocates nothing).
86
+ * @param {string} line
87
+ * @param {number} [startColumn]
88
+ * @returns {string}
89
+ */
90
+ export function expandTabs(line, startColumn = 0) {
91
+ if (line.indexOf('\t') === -1) return line;
92
+ let out = '';
93
+ let column = startColumn;
94
+ for (let i = 0; i < line.length; i++) {
95
+ const ch = line[i];
96
+ if (ch === '\t') {
97
+ const width = 4 - (column % 4);
98
+ out += ' '.slice(0, width);
99
+ column += width;
100
+ }
101
+ else {
102
+ out += ch;
103
+ column++;
104
+ }
105
+ }
106
+ return out;
107
+ }
package/styles/md.css ADDED
@@ -0,0 +1,238 @@
1
+ /*
2
+ * @jarenjs/md — the visual component's stylesheet.
3
+ *
4
+ * Scoped under the `.md` article the vnode emitter produces, plus the
5
+ * `tok-*` token classes of the highlight plugin (docs/PLUGINS.md §6.2)
6
+ * and the mermaid placeholder. Colors come in a light set and a dark
7
+ * override activated by a `.dark` class on any ancestor (the
8
+ * convention the jarenjs website uses); everything sizes in em so the
9
+ * component inherits its host's type scale.
10
+ */
11
+
12
+ .md {
13
+ line-height: 1.6;
14
+ overflow-wrap: break-word;
15
+ }
16
+
17
+ .md > :first-child { margin-top: 0; }
18
+ .md > :last-child { margin-bottom: 0; }
19
+
20
+ .md h1, .md h2, .md h3, .md h4, .md h5, .md h6 {
21
+ line-height: 1.25;
22
+ margin: 1.4em 0 0.5em;
23
+ }
24
+ .md h1 { font-size: 1.7em; }
25
+ .md h2 { font-size: 1.4em; }
26
+ .md h3 { font-size: 1.2em; }
27
+
28
+ /* A fragment scrolled into view lands under a sticky header unless the
29
+ target reserves the room itself. That is a layout concern, so it lives
30
+ here and not in whatever scrolls the page: a host with a header of its
31
+ own sets `--md-scroll-margin`, one without keeps a little breathing
32
+ room above the heading. */
33
+ .md :is(h1, h2, h3, h4, h5, h6) { scroll-margin-top: var(--md-scroll-margin, 4.5rem); }
34
+
35
+ /* --- heading anchors (`headingAnchors: true`) ---------------------- */
36
+ /* The affordance is quiet until the heading is hovered or the link is
37
+ focused, so a document does not read as a column of `#`. It stays in
38
+ the DOM either way — hiding it with `display: none` would take it off
39
+ the keyboard, and it is the keyboard path that needs it most. */
40
+ .md .md-anchor {
41
+ margin-left: 0.35em;
42
+ color: var(--md-muted, #5b6472);
43
+ font-weight: 400;
44
+ text-decoration: none;
45
+ opacity: 0;
46
+ transition: opacity 120ms ease;
47
+ }
48
+ .md :is(h1, h2, h3, h4, h5, h6):hover .md-anchor,
49
+ .md .md-anchor:focus-visible { opacity: 1; }
50
+ .md .md-anchor:hover { color: var(--md-link, #2456c4); }
51
+ .md .md-anchor:focus-visible {
52
+ outline: 2px solid var(--md-link, #2456c4);
53
+ outline-offset: 2px;
54
+ }
55
+
56
+ @media (prefers-reduced-motion: reduce) {
57
+ .md .md-anchor { transition: none; }
58
+ }
59
+
60
+ .md p, .md ul, .md ol, .md table { margin: 0.6em 0; }
61
+ .md ul, .md ol { padding-left: 1.5em; }
62
+ .md li > ul, .md li > ol { margin: 0.15em 0; }
63
+ .md li { margin: 0.15em 0; }
64
+
65
+ .md blockquote {
66
+ margin: 0.8em 0;
67
+ padding: 0.1em 1em;
68
+ border-left: 3px solid var(--md-rule, #d0d4dc);
69
+ color: var(--md-muted, #5b6472);
70
+ }
71
+
72
+ .md hr {
73
+ border: 0;
74
+ border-top: 1px solid var(--md-rule, #d0d4dc);
75
+ margin: 1.4em 0;
76
+ }
77
+
78
+ .md a { color: var(--md-link, #2456c4); }
79
+
80
+ .md code {
81
+ font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, monospace;
82
+ font-size: 0.92em;
83
+ background: var(--md-code-bg, #f2f4f8);
84
+ border-radius: 4px;
85
+ padding: 0.1em 0.35em;
86
+ }
87
+
88
+ .md pre {
89
+ margin: 0.8em 0;
90
+ padding: 0.8em 1em;
91
+ overflow-x: auto;
92
+ background: var(--md-code-bg, #f2f4f8);
93
+ border-radius: 8px;
94
+ }
95
+ .md pre code {
96
+ background: none;
97
+ padding: 0;
98
+ font-size: 0.88em;
99
+ }
100
+
101
+ .md table {
102
+ border-collapse: collapse;
103
+ display: block;
104
+ overflow-x: auto;
105
+ }
106
+ .md th, .md td {
107
+ border: 1px solid var(--md-rule, #d0d4dc);
108
+ padding: 0.35em 0.7em;
109
+ }
110
+ .md th { background: var(--md-code-bg, #f2f4f8); }
111
+
112
+ .md img { max-width: 100%; }
113
+
114
+ /* --- GFM footnotes ------------------------------------------------- */
115
+ /* The section is appended after the last block of the document, so it
116
+ needs the rule a reader expects between a document and its notes —
117
+ the emitter writes no `<hr>` of its own, because that would be a
118
+ thematic break the author did not write. Small type, muted, and the
119
+ back-arrow sized so a thumb can hit it. */
120
+ .md .footnotes {
121
+ margin-top: 2em;
122
+ padding-top: 0.8em;
123
+ border-top: 1px solid var(--md-rule, #d0d4dc);
124
+ font-size: 0.9em;
125
+ color: var(--md-muted, #5b6472);
126
+ }
127
+ .md .footnotes > ol { margin: 0; }
128
+ .md .footnotes li { margin: 0.3em 0; }
129
+ .md .footnotes p { margin: 0.3em 0; }
130
+ /* The cited note is highlighted when a reader jumps to it, so a landing
131
+ in the middle of a list of notes says which one it landed on. */
132
+ .md .footnotes li:target { background: var(--md-code-bg, #f2f4f8); }
133
+
134
+ .md .footnote-backref {
135
+ text-decoration: none;
136
+ padding: 0 0.25em;
137
+ font-size: 1.1em;
138
+ line-height: 1;
139
+ }
140
+ .md .footnote-backref sup { font-size: 0.7em; }
141
+
142
+ /* A citation is small and easy to miss; give it a target a pointer can
143
+ land on without shifting the line it sits in. */
144
+ .md sup > a { text-decoration: none; padding: 0 0.15em; }
145
+ .md sup > a:hover { text-decoration: underline; }
146
+
147
+ .md input[type='checkbox'] { margin-right: 0.4em; }
148
+
149
+ /* --- highlight plugin token colors (PLUGINS.md §6.2) --------------- */
150
+
151
+ .md .tok-kw { color: var(--md-tok-kw, #0550ae); }
152
+ .md .tok-str { color: var(--md-tok-str, #50a14f); }
153
+ .md .tok-num { color: var(--md-tok-num, #b76b01); }
154
+ .md .tok-com { color: var(--md-tok-com, #8b919c); font-style: italic; }
155
+ .md .tok-pun { color: var(--md-tok-pun, #526069); }
156
+ .md .tok-op { color: var(--md-tok-op, #0184bc); }
157
+ .md .tok-lit { color: var(--md-tok-lit, #b76b01); }
158
+
159
+ /* --- mermaid placeholder / upgrade (PLUGINS.md §6.1) --------------- */
160
+
161
+ .md-mermaid {
162
+ /* A diagram is a figure, not a run of text: giving it a surface and a
163
+ frame separates it from the prose around it and stops a wide diagram
164
+ from reading as part of the paragraph above. */
165
+ margin: 1.2em 0;
166
+ padding: 1rem;
167
+ text-align: center;
168
+ background: var(--md-figure-bg, #fbfcfe);
169
+ border: 1px solid var(--md-rule, #d0d4dc);
170
+ border-radius: 10px;
171
+ }
172
+ .md-mermaid .md-mermaid-src {
173
+ text-align: left;
174
+ color: var(--md-muted, #5b6472);
175
+ }
176
+ /* The diagram's own sizing (natural size, scroll rather than shrink) lives in
177
+ @jarenjs/mermaid's stylesheet, which owns the element the plugin emits. */
178
+
179
+ /* --- the optional interactive viewer ------------------------------- */
180
+ /* Only present once `mermaidPlugin({ interactive: true })` has hydrated:
181
+ the server-rendered figure carries none of this. */
182
+ .md-mermaid.mm-interactive {
183
+ position: relative;
184
+ cursor: grab;
185
+ }
186
+ .md-mermaid.mm-interactive:focus-visible {
187
+ outline: 2px solid var(--md-link, #2563eb);
188
+ outline-offset: 2px;
189
+ }
190
+ .md-mermaid.mm-interactive:active { cursor: grabbing; }
191
+ .md-mermaid .mm-controls {
192
+ position: absolute;
193
+ right: 0.5rem;
194
+ bottom: 0.5rem;
195
+ display: flex;
196
+ gap: 0.25rem;
197
+ opacity: 0.35;
198
+ transition: opacity 120ms ease;
199
+ }
200
+ .md-mermaid:hover .mm-controls,
201
+ .md-mermaid:focus-within .mm-controls { opacity: 1; }
202
+ .md-mermaid .mm-control {
203
+ /* 32px is below the 44px touch target the site requires elsewhere, so
204
+ these are a convenience: every action they offer is also on the
205
+ keyboard and in the pinch gesture. */
206
+ min-width: 2rem;
207
+ min-height: 2rem;
208
+ padding: 0;
209
+ font-size: 1rem;
210
+ line-height: 1;
211
+ color: var(--md-muted, #5b6472);
212
+ background: var(--md-figure-bg, #fbfcfe);
213
+ border: 1px solid var(--md-rule, #d0d4dc);
214
+ border-radius: 8px;
215
+ cursor: pointer;
216
+ }
217
+ .md-mermaid .mm-control:hover { color: var(--md-link, #2563eb); }
218
+
219
+ @media (prefers-reduced-motion: reduce) {
220
+ .md-mermaid .mm-controls { transition: none; }
221
+ }
222
+
223
+ /* --- dark scheme --------------------------------------------------- */
224
+
225
+ .dark .md, .md.dark {
226
+ --md-figure-bg: #171b22;
227
+ --md-rule: #39414e;
228
+ --md-muted: #9aa4b2;
229
+ --md-link: #7aa2ff;
230
+ --md-code-bg: #1d232c;
231
+ --md-tok-kw: #79b8ff;
232
+ --md-tok-str: #98c379;
233
+ --md-tok-num: #d19a66;
234
+ --md-tok-com: #7f848e;
235
+ --md-tok-pun: #9aa4b2;
236
+ --md-tok-op: #56b6c2;
237
+ --md-tok-lit: #d19a66;
238
+ }