vantage-md 0.1.7 → 0.5.4
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 +24 -9
- package/dist/index.cjs +862 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +440 -14
- package/dist/index.d.ts +440 -14
- package/dist/index.js +840 -47
- package/dist/index.js.map +1 -1
- package/dist/prose.css +2 -1
- package/dist/react.cjs +1063 -117
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +260 -14
- package/dist/react.d.ts +260 -14
- package/dist/react.js +1056 -118
- package/dist/react.js.map +1 -1
- package/dist/styles.css +240 -0
- package/package.json +18 -8
package/dist/react.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { RefObject } from 'react';
|
|
2
2
|
import { Root } from 'hast';
|
|
3
|
-
import { Plugin } from 'unified';
|
|
3
|
+
import { Plugin, PluggableList } from 'unified';
|
|
4
4
|
import { defaultSchema } from 'rehype-sanitize';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -56,6 +56,107 @@ interface FrontmatterDisplayProps {
|
|
|
56
56
|
}
|
|
57
57
|
declare const FrontmatterDisplay: React.NamedExoticComponent<FrontmatterDisplayProps>;
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* The `tone` vocabulary: GitHub's alert words plus `muted`.
|
|
61
|
+
*
|
|
62
|
+
* Semantic, never chromatic (P2, Ledger OQ-3). A document says what a section
|
|
63
|
+
* *is*; the theme decides what that looks like, which is what lets one document
|
|
64
|
+
* render correctly in light, in dark, and in themes that do not exist yet.
|
|
65
|
+
*/
|
|
66
|
+
declare const VANTAGE_TONES: readonly ["note", "tip", "important", "warning", "caution", "muted"];
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The `vantage:` frontmatter key — file-scoped chrome (`docs/reference/inline-markup.md`, "File-scoped chrome").
|
|
70
|
+
*
|
|
71
|
+
* One reserved key at the top level of a document's frontmatter, holding the
|
|
72
|
+
* chrome that belongs to the *file* rather than to a section. Today that is one
|
|
73
|
+
* thing: whether the document's lifecycle `status:` is shown as a chip above the
|
|
74
|
+
* metadata card, instead of being buried as one row inside it.
|
|
75
|
+
*
|
|
76
|
+
* Read only at the top level, and **inert on every failure** (P3): an unknown
|
|
77
|
+
* key, a value outside the closed set, or a `vantage:` that is not a table
|
|
78
|
+
* produces no chrome, no throw and no console output. The reasons are returned
|
|
79
|
+
* as data in `issues`, for anything that wants to report them — `vantage-check`
|
|
80
|
+
* does, and it is the only signal an author gets. That split is exactly the one
|
|
81
|
+
* `FrontmatterProblem` already uses in `frontmatter.ts`: the viewer reads the
|
|
82
|
+
* value, the checker reads the reasons.
|
|
83
|
+
*
|
|
84
|
+
* Like `vantageDirectives.ts`, this module is imported by the CLI checker **by
|
|
85
|
+
* relative path**, so it must stay a pure function of already-parsed data: no
|
|
86
|
+
* hast, no React, no filesystem.
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The document lifecycle vocabulary. Closed; extending it is a code change.
|
|
91
|
+
*
|
|
92
|
+
* This is the repo's own existing set, not a new one — `styleGuide.ts` tells
|
|
93
|
+
* every agent to write `status: in-review # draft | in-review | accepted |
|
|
94
|
+
* deprecated`, and every document under `docs/` follows it. It is deliberately
|
|
95
|
+
* *not* the `badge` set (`draft stale blocked done wip`): `badge` is
|
|
96
|
+
* section-scoped workflow state, `status` is document lifecycle state, and
|
|
97
|
+
* `in-review` — the value the design doc's own only example renders — is not a
|
|
98
|
+
* badge word at all. Only `draft` is a member of both, and a token set is per key.
|
|
99
|
+
*/
|
|
100
|
+
declare const DOC_STATUSES: readonly ["draft", "in-review", "accepted", "deprecated"];
|
|
101
|
+
type DocStatus = (typeof DOC_STATUSES)[number];
|
|
102
|
+
/** Every key this build knows under `vantage:`. Closed. */
|
|
103
|
+
declare const VANTAGE_FRONTMATTER_KEYS: readonly ["status-chip"];
|
|
104
|
+
/**
|
|
105
|
+
* Which tone each status borrows its colours from.
|
|
106
|
+
*
|
|
107
|
+
* The chip has no palette of its own: it reuses the tone chips
|
|
108
|
+
* (`.vantage-chip--<tone>` in `styles/directives.css`), which is also what makes
|
|
109
|
+
* a `draft` chip and a `badge=draft` chip the same visual object. A map rather
|
|
110
|
+
* than a computed class name, so the whole status→tone relation is one readable
|
|
111
|
+
* table and a test can assert it covers the vocabulary.
|
|
112
|
+
*/
|
|
113
|
+
declare const DOC_STATUS_TONES: Readonly<Record<DocStatus, (typeof VANTAGE_TONES)[number]>>;
|
|
114
|
+
/**
|
|
115
|
+
* Why something under `vantage:` produced no chrome.
|
|
116
|
+
*
|
|
117
|
+
* `status-chip-orphan` and `status-chip-disagrees` are not vocabulary errors —
|
|
118
|
+
* both values are legal — but both are the markup rot R3 is about: a chip that
|
|
119
|
+
* says something the document's own `status:` does not.
|
|
120
|
+
*/
|
|
121
|
+
type VantageFrontmatterIssue = {
|
|
122
|
+
kind: "not-a-table";
|
|
123
|
+
value: unknown;
|
|
124
|
+
} | {
|
|
125
|
+
kind: "unknown-key";
|
|
126
|
+
key: string;
|
|
127
|
+
} | {
|
|
128
|
+
kind: "bad-value";
|
|
129
|
+
key: string;
|
|
130
|
+
value: unknown;
|
|
131
|
+
legal: readonly string[];
|
|
132
|
+
} | {
|
|
133
|
+
kind: "status-chip-orphan";
|
|
134
|
+
status: unknown;
|
|
135
|
+
} | {
|
|
136
|
+
kind: "status-chip-disagrees";
|
|
137
|
+
chip: DocStatus;
|
|
138
|
+
status: unknown;
|
|
139
|
+
};
|
|
140
|
+
interface VantageFrontmatter {
|
|
141
|
+
/** The chip's text, or `undefined` for no chip. */
|
|
142
|
+
statusChip?: DocStatus;
|
|
143
|
+
/** Why something was dropped. A viewer must never read this (P3). */
|
|
144
|
+
issues: VantageFrontmatterIssue[];
|
|
145
|
+
}
|
|
146
|
+
/** Narrowing helper the chip and the checker both use. */
|
|
147
|
+
declare function isDocStatus(value: unknown): value is DocStatus;
|
|
148
|
+
/**
|
|
149
|
+
* Read the `vantage:` key out of parsed frontmatter.
|
|
150
|
+
*
|
|
151
|
+
* Pure: no module state, no mutation of the input, no logging. The same object
|
|
152
|
+
* in twice gives equal results out.
|
|
153
|
+
*/
|
|
154
|
+
declare function readVantageFrontmatter(frontmatter: Record<string, unknown>): VantageFrontmatter;
|
|
155
|
+
|
|
156
|
+
declare const DocumentStatusChip: React.NamedExoticComponent<{
|
|
157
|
+
status: DocStatus;
|
|
158
|
+
}>;
|
|
159
|
+
|
|
59
160
|
/**
|
|
60
161
|
* Framework-agnostic markdown -> HTML rendering pipeline.
|
|
61
162
|
* Uses the same remark/rehype chain as the Vantage viewer.
|
|
@@ -87,7 +188,7 @@ interface RenderResult {
|
|
|
87
188
|
*
|
|
88
189
|
* Features (all enabled by default):
|
|
89
190
|
* - GitHub Flavored Markdown (tables, strikethrough, task lists)
|
|
90
|
-
* - KaTeX math rendering ($$...$$
|
|
191
|
+
* - KaTeX math rendering, inline and block ($$...$$ only; single $ is not a delimiter)
|
|
91
192
|
* - Syntax highlighting via highlight.js
|
|
92
193
|
* - `data-source-line` attributes for line anchors
|
|
93
194
|
* - XSS sanitization
|
|
@@ -108,22 +209,98 @@ declare function renderMarkdown(content: string, options?: RenderOptions): Promi
|
|
|
108
209
|
* each rendered block a traceable line number from the source.
|
|
109
210
|
*/
|
|
110
211
|
|
|
111
|
-
|
|
212
|
+
interface RehypeSourceLinesOptions {
|
|
213
|
+
/**
|
|
214
|
+
* Lines stripped off the front of the file before parsing — frontmatter,
|
|
215
|
+
* essentially. Added to every emitted line number so `data-source-line`
|
|
216
|
+
* names a line in the *file* rather than in the parsed body, which is what
|
|
217
|
+
* a `#L42` link written against the file means. Defaults to 0.
|
|
218
|
+
*/
|
|
219
|
+
offset?: number;
|
|
220
|
+
}
|
|
221
|
+
declare const rehypeSourceLines: Plugin<[RehypeSourceLinesOptions?], Root>;
|
|
112
222
|
|
|
113
223
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
224
|
+
* The one definition of the Vantage remark/rehype chain.
|
|
225
|
+
*
|
|
226
|
+
* Three call sites render Markdown — `renderMarkdown` (string in, HTML out,
|
|
227
|
+
* which is what the CLI checker runs), the app's `<MarkdownViewer>`, and this
|
|
228
|
+
* package's exported `<MarkdownViewer>` — and each one used to hand-write the
|
|
229
|
+
* same plugin list in the same order. Three copies kept in sync by hand is how
|
|
230
|
+
* a plugin lands in the viewer and not in the checker: a document that styles
|
|
231
|
+
* in the app and renders bare through the tool that is supposed to validate it,
|
|
232
|
+
* with no error anywhere.
|
|
233
|
+
*
|
|
234
|
+
* The order is load-bearing, not incidental:
|
|
235
|
+
*
|
|
236
|
+
* - `rehypeRaw` first: `remark-rehype` runs with `allowDangerousHtml: true`,
|
|
237
|
+
* so raw HTML is still a string until this plugin parses it.
|
|
238
|
+
* - `rehypeSourceLines` before `rehypeSanitize`: `data-source-line` has to be
|
|
239
|
+
* an allowlisted attribute on an element the sanitiser keeps.
|
|
240
|
+
* - `rehypeSlug`, `rehypeHighlight` and `rehypeKatex` after `rehypeSanitize`.
|
|
241
|
+
* For `rehypeSlug` this is not a preference: the sanitiser's default schema
|
|
242
|
+
* clobbers `id` with the prefix `user-content-`, so slugging before it turns
|
|
243
|
+
* every `#heading` link in every document into a dead anchor. For the other
|
|
244
|
+
* two it means their output is trusted rather than filtered — KaTeX emits
|
|
245
|
+
* inline `style` on nearly every glyph.
|
|
246
|
+
*
|
|
247
|
+
* Anything that reads HTML comments must sit between `rehypeRaw` and
|
|
248
|
+
* `rehypeSanitize`: before `rehypeRaw` there are no comment nodes, and
|
|
249
|
+
* `rehypeSanitize` deletes them. `rehypeVantageDirectives` is what occupies
|
|
250
|
+
* that slot, and it is registered unconditionally — a renderer that skipped it
|
|
251
|
+
* would disagree with the others about what a document means.
|
|
252
|
+
*/
|
|
253
|
+
|
|
254
|
+
interface PipelineOptions {
|
|
255
|
+
/** GFM tables, strikethrough, task lists (default: true) */
|
|
256
|
+
gfm?: boolean;
|
|
257
|
+
/** KaTeX math, `$$…$$` only (default: true) */
|
|
258
|
+
math?: boolean;
|
|
259
|
+
/** Syntax highlighting via highlight.js (default: true) */
|
|
260
|
+
highlight?: boolean;
|
|
261
|
+
/** `data-source-line` attributes for line anchors (default: true) */
|
|
262
|
+
sourceLines?: boolean;
|
|
263
|
+
/** XSS sanitisation (default: true) */
|
|
264
|
+
sanitize?: boolean;
|
|
265
|
+
/**
|
|
266
|
+
* Lines the frontmatter consumed, added to every emitted line number so
|
|
267
|
+
* `data-source-line` names a line in the *file* rather than in the parsed
|
|
268
|
+
* body — which is what a `#L42` link written against the file means.
|
|
269
|
+
* Defaults to 0. Ignored when `sourceLines` is false.
|
|
270
|
+
*/
|
|
271
|
+
bodyLineOffset?: number;
|
|
272
|
+
}
|
|
273
|
+
interface Pipeline {
|
|
274
|
+
remarkPlugins: PluggableList;
|
|
275
|
+
rehypePlugins: PluggableList;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* The mdast half of the chain. Exported on its own because there is a real
|
|
279
|
+
* mdast-only consumer: the CLI checker parses documents without ever running
|
|
280
|
+
* rehype (`packages/vantage-check/src/core/document.ts`), and it has to parse
|
|
281
|
+
* them exactly the way the viewer does.
|
|
117
282
|
*/
|
|
283
|
+
declare function buildRemarkPlugins(options?: PipelineOptions): PluggableList;
|
|
118
284
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
285
|
+
* Both halves from one options object.
|
|
286
|
+
*
|
|
287
|
+
* This is what every renderer calls. It takes one object rather than exposing
|
|
288
|
+
* the two builders because `math` spans both halves — `remark-math` parses the
|
|
289
|
+
* delimiters, `rehype-katex` renders the result — and two calls are two places
|
|
290
|
+
* to forget the second one.
|
|
291
|
+
*
|
|
292
|
+
* Returns fresh arrays on every call and reads no module-level state; keep it
|
|
293
|
+
* that way, so a plugin in the chain cannot become a function of how many times
|
|
294
|
+
* the chain has been built.
|
|
295
|
+
*/
|
|
296
|
+
declare function buildPipeline(options?: PipelineOptions): Pipeline;
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Framework-agnostic line anchor utilities.
|
|
300
|
+
* Scroll to and highlight the elements a GitHub-style line anchor
|
|
301
|
+
* (#L42, #L42-L50) names. The parsing half lives in lineAnchor.ts, which has
|
|
302
|
+
* no DOM dependency.
|
|
122
303
|
*/
|
|
123
|
-
declare function parseLineAnchor(hash: string): {
|
|
124
|
-
start: number;
|
|
125
|
-
end: number;
|
|
126
|
-
} | null;
|
|
127
304
|
/**
|
|
128
305
|
* Clear all line anchor highlights from a container.
|
|
129
306
|
*/
|
|
@@ -137,15 +314,72 @@ declare function clearLineAnchorHighlights(container: HTMLElement): void;
|
|
|
137
314
|
*/
|
|
138
315
|
declare function scrollToLineAnchor(container: HTMLElement, hash: string): (() => void) | null;
|
|
139
316
|
|
|
317
|
+
/**
|
|
318
|
+
* Parsing for GitHub-style line anchors, with no DOM in sight.
|
|
319
|
+
*
|
|
320
|
+
* Split out from scrollToLineAnchor.ts so that non-browser consumers — the
|
|
321
|
+
* `vantage-check` CLI, which validates `#L42` links against the file on disk —
|
|
322
|
+
* can share the *same* syntax the viewer honours instead of reimplementing it
|
|
323
|
+
* and drifting.
|
|
324
|
+
*/
|
|
325
|
+
/**
|
|
326
|
+
* Parse a GitHub-style line anchor hash.
|
|
327
|
+
* Supports: #L42, #L42-L50, #L42-50
|
|
328
|
+
* Returns null if the hash is not a line anchor.
|
|
329
|
+
*/
|
|
330
|
+
declare function parseLineAnchor(hash: string): {
|
|
331
|
+
start: number;
|
|
332
|
+
end: number;
|
|
333
|
+
} | null;
|
|
334
|
+
|
|
140
335
|
/**
|
|
141
336
|
* Frontmatter parser for YAML (---) and TOML (+++) delimited content.
|
|
142
337
|
* Works in both browser and server environments.
|
|
143
338
|
*/
|
|
144
339
|
type FrontmatterFormat = "yaml" | "toml" | "none";
|
|
340
|
+
/**
|
|
341
|
+
* Why a document that *looks* like it has frontmatter ended up without any.
|
|
342
|
+
*
|
|
343
|
+
* The parser deliberately never throws: a document whose frontmatter is broken
|
|
344
|
+
* still renders, with the block treated as body text. That is the right
|
|
345
|
+
* behaviour for a viewer and the wrong one for an author, who gets no signal
|
|
346
|
+
* at all — so the reason is recorded here for anything that wants to report it
|
|
347
|
+
* (`vantage-check` does; see its frontmatter rules).
|
|
348
|
+
*
|
|
349
|
+
* - `unterminated` — an opening delimiter with no closing one.
|
|
350
|
+
* - `invalid` — the block did not parse; `message` is the parser's own words,
|
|
351
|
+
* and `line`/`column` are 1-based *within the block* when it said.
|
|
352
|
+
* - `not-a-mapping` — it parsed, but to a string or a list rather than a table
|
|
353
|
+
* of fields, which is not something a metadata card can render.
|
|
354
|
+
*/
|
|
355
|
+
interface FrontmatterProblem {
|
|
356
|
+
kind: "unterminated" | "invalid" | "not-a-mapping";
|
|
357
|
+
/** The delimiter the document opened with. */
|
|
358
|
+
delimiter: string;
|
|
359
|
+
message?: string;
|
|
360
|
+
line?: number;
|
|
361
|
+
column?: number;
|
|
362
|
+
}
|
|
145
363
|
interface ParsedFrontmatter {
|
|
146
364
|
frontmatter: Record<string, unknown>;
|
|
147
365
|
body: string;
|
|
148
366
|
format: FrontmatterFormat;
|
|
367
|
+
/**
|
|
368
|
+
* How many source lines the frontmatter block consumed — the shift between a
|
|
369
|
+
* line number in `body` and the same line in the original file:
|
|
370
|
+
* `fileLine = bodyLine + bodyLineOffset`.
|
|
371
|
+
*
|
|
372
|
+
* Anything that renders `body` and reports line numbers (line anchors, review
|
|
373
|
+
* comment anchors) has to add this back, or every number it produces points
|
|
374
|
+
* `bodyLineOffset` lines short of the text it names.
|
|
375
|
+
*/
|
|
376
|
+
bodyLineOffset: number;
|
|
377
|
+
/**
|
|
378
|
+
* Set when the document opens with a frontmatter delimiter that did not
|
|
379
|
+
* yield a metadata table. Everything else in this result is unchanged —
|
|
380
|
+
* this records *why*, it does not change what rendering does.
|
|
381
|
+
*/
|
|
382
|
+
problem?: FrontmatterProblem;
|
|
149
383
|
}
|
|
150
384
|
/**
|
|
151
385
|
* Parse frontmatter from markdown content.
|
|
@@ -160,6 +394,18 @@ declare function parseFrontmatter(content: string): ParsedFrontmatter;
|
|
|
160
394
|
*/
|
|
161
395
|
|
|
162
396
|
type Schema = typeof defaultSchema;
|
|
397
|
+
/**
|
|
398
|
+
* Never set `allowComments` here.
|
|
399
|
+
*
|
|
400
|
+
* `hast-util-sanitize` drops comment nodes because that boolean defaults to
|
|
401
|
+
* `false` — comments are not elements, so `tagNames` has nothing to do with it.
|
|
402
|
+
* `rehypeVantageDirectives` relies on that deletion: it consumes a
|
|
403
|
+
* `<!-- vantage: … -->` comment into attributes and deliberately leaves the node
|
|
404
|
+
* for the sanitiser. Turning the switch on readmits every directive comment —
|
|
405
|
+
* valid and malformed alike — into the rendered HTML, which breaks the carrier's
|
|
406
|
+
* whole premise. `vantageDirectives.test.ts` ("leaves no comment in the rendered
|
|
407
|
+
* markup") is the guard.
|
|
408
|
+
*/
|
|
163
409
|
declare const sanitizeSchema: Schema;
|
|
164
410
|
|
|
165
411
|
/**
|
|
@@ -241,4 +487,4 @@ interface ResolveLinkOptions {
|
|
|
241
487
|
*/
|
|
242
488
|
declare function resolveLinks(html: string, options?: ResolveLinkOptions): string;
|
|
243
489
|
|
|
244
|
-
export { FrontmatterDisplay, type FrontmatterFormat, MarkdownViewer, type MarkdownViewerProps, MermaidDiagram, type ParsedFrontmatter, type RenderMermaidOptions, type RenderOptions, type RenderResult, type ResolveLinkOptions, clearLineAnchorHighlights, parseFrontmatter, parseLineAnchor, rehypeSourceLines, renderMarkdown, renderMermaidBlocks, resolveLinks, sanitizeSchema, scrollToLineAnchor, useLineAnchor };
|
|
490
|
+
export { DOC_STATUSES, DOC_STATUS_TONES, type DocStatus, DocumentStatusChip, FrontmatterDisplay, type FrontmatterFormat, MarkdownViewer, type MarkdownViewerProps, MermaidDiagram, type ParsedFrontmatter, type Pipeline, type PipelineOptions, type RenderMermaidOptions, type RenderOptions, type RenderResult, type ResolveLinkOptions, VANTAGE_FRONTMATTER_KEYS, type VantageFrontmatter, type VantageFrontmatterIssue, buildPipeline, buildRemarkPlugins, clearLineAnchorHighlights, isDocStatus, parseFrontmatter, parseLineAnchor, readVantageFrontmatter, rehypeSourceLines, renderMarkdown, renderMermaidBlocks, resolveLinks, sanitizeSchema, scrollToLineAnchor, useLineAnchor };
|
package/dist/react.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { RefObject } from 'react';
|
|
2
2
|
import { Root } from 'hast';
|
|
3
|
-
import { Plugin } from 'unified';
|
|
3
|
+
import { Plugin, PluggableList } from 'unified';
|
|
4
4
|
import { defaultSchema } from 'rehype-sanitize';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -56,6 +56,107 @@ interface FrontmatterDisplayProps {
|
|
|
56
56
|
}
|
|
57
57
|
declare const FrontmatterDisplay: React.NamedExoticComponent<FrontmatterDisplayProps>;
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* The `tone` vocabulary: GitHub's alert words plus `muted`.
|
|
61
|
+
*
|
|
62
|
+
* Semantic, never chromatic (P2, Ledger OQ-3). A document says what a section
|
|
63
|
+
* *is*; the theme decides what that looks like, which is what lets one document
|
|
64
|
+
* render correctly in light, in dark, and in themes that do not exist yet.
|
|
65
|
+
*/
|
|
66
|
+
declare const VANTAGE_TONES: readonly ["note", "tip", "important", "warning", "caution", "muted"];
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The `vantage:` frontmatter key — file-scoped chrome (`docs/reference/inline-markup.md`, "File-scoped chrome").
|
|
70
|
+
*
|
|
71
|
+
* One reserved key at the top level of a document's frontmatter, holding the
|
|
72
|
+
* chrome that belongs to the *file* rather than to a section. Today that is one
|
|
73
|
+
* thing: whether the document's lifecycle `status:` is shown as a chip above the
|
|
74
|
+
* metadata card, instead of being buried as one row inside it.
|
|
75
|
+
*
|
|
76
|
+
* Read only at the top level, and **inert on every failure** (P3): an unknown
|
|
77
|
+
* key, a value outside the closed set, or a `vantage:` that is not a table
|
|
78
|
+
* produces no chrome, no throw and no console output. The reasons are returned
|
|
79
|
+
* as data in `issues`, for anything that wants to report them — `vantage-check`
|
|
80
|
+
* does, and it is the only signal an author gets. That split is exactly the one
|
|
81
|
+
* `FrontmatterProblem` already uses in `frontmatter.ts`: the viewer reads the
|
|
82
|
+
* value, the checker reads the reasons.
|
|
83
|
+
*
|
|
84
|
+
* Like `vantageDirectives.ts`, this module is imported by the CLI checker **by
|
|
85
|
+
* relative path**, so it must stay a pure function of already-parsed data: no
|
|
86
|
+
* hast, no React, no filesystem.
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The document lifecycle vocabulary. Closed; extending it is a code change.
|
|
91
|
+
*
|
|
92
|
+
* This is the repo's own existing set, not a new one — `styleGuide.ts` tells
|
|
93
|
+
* every agent to write `status: in-review # draft | in-review | accepted |
|
|
94
|
+
* deprecated`, and every document under `docs/` follows it. It is deliberately
|
|
95
|
+
* *not* the `badge` set (`draft stale blocked done wip`): `badge` is
|
|
96
|
+
* section-scoped workflow state, `status` is document lifecycle state, and
|
|
97
|
+
* `in-review` — the value the design doc's own only example renders — is not a
|
|
98
|
+
* badge word at all. Only `draft` is a member of both, and a token set is per key.
|
|
99
|
+
*/
|
|
100
|
+
declare const DOC_STATUSES: readonly ["draft", "in-review", "accepted", "deprecated"];
|
|
101
|
+
type DocStatus = (typeof DOC_STATUSES)[number];
|
|
102
|
+
/** Every key this build knows under `vantage:`. Closed. */
|
|
103
|
+
declare const VANTAGE_FRONTMATTER_KEYS: readonly ["status-chip"];
|
|
104
|
+
/**
|
|
105
|
+
* Which tone each status borrows its colours from.
|
|
106
|
+
*
|
|
107
|
+
* The chip has no palette of its own: it reuses the tone chips
|
|
108
|
+
* (`.vantage-chip--<tone>` in `styles/directives.css`), which is also what makes
|
|
109
|
+
* a `draft` chip and a `badge=draft` chip the same visual object. A map rather
|
|
110
|
+
* than a computed class name, so the whole status→tone relation is one readable
|
|
111
|
+
* table and a test can assert it covers the vocabulary.
|
|
112
|
+
*/
|
|
113
|
+
declare const DOC_STATUS_TONES: Readonly<Record<DocStatus, (typeof VANTAGE_TONES)[number]>>;
|
|
114
|
+
/**
|
|
115
|
+
* Why something under `vantage:` produced no chrome.
|
|
116
|
+
*
|
|
117
|
+
* `status-chip-orphan` and `status-chip-disagrees` are not vocabulary errors —
|
|
118
|
+
* both values are legal — but both are the markup rot R3 is about: a chip that
|
|
119
|
+
* says something the document's own `status:` does not.
|
|
120
|
+
*/
|
|
121
|
+
type VantageFrontmatterIssue = {
|
|
122
|
+
kind: "not-a-table";
|
|
123
|
+
value: unknown;
|
|
124
|
+
} | {
|
|
125
|
+
kind: "unknown-key";
|
|
126
|
+
key: string;
|
|
127
|
+
} | {
|
|
128
|
+
kind: "bad-value";
|
|
129
|
+
key: string;
|
|
130
|
+
value: unknown;
|
|
131
|
+
legal: readonly string[];
|
|
132
|
+
} | {
|
|
133
|
+
kind: "status-chip-orphan";
|
|
134
|
+
status: unknown;
|
|
135
|
+
} | {
|
|
136
|
+
kind: "status-chip-disagrees";
|
|
137
|
+
chip: DocStatus;
|
|
138
|
+
status: unknown;
|
|
139
|
+
};
|
|
140
|
+
interface VantageFrontmatter {
|
|
141
|
+
/** The chip's text, or `undefined` for no chip. */
|
|
142
|
+
statusChip?: DocStatus;
|
|
143
|
+
/** Why something was dropped. A viewer must never read this (P3). */
|
|
144
|
+
issues: VantageFrontmatterIssue[];
|
|
145
|
+
}
|
|
146
|
+
/** Narrowing helper the chip and the checker both use. */
|
|
147
|
+
declare function isDocStatus(value: unknown): value is DocStatus;
|
|
148
|
+
/**
|
|
149
|
+
* Read the `vantage:` key out of parsed frontmatter.
|
|
150
|
+
*
|
|
151
|
+
* Pure: no module state, no mutation of the input, no logging. The same object
|
|
152
|
+
* in twice gives equal results out.
|
|
153
|
+
*/
|
|
154
|
+
declare function readVantageFrontmatter(frontmatter: Record<string, unknown>): VantageFrontmatter;
|
|
155
|
+
|
|
156
|
+
declare const DocumentStatusChip: React.NamedExoticComponent<{
|
|
157
|
+
status: DocStatus;
|
|
158
|
+
}>;
|
|
159
|
+
|
|
59
160
|
/**
|
|
60
161
|
* Framework-agnostic markdown -> HTML rendering pipeline.
|
|
61
162
|
* Uses the same remark/rehype chain as the Vantage viewer.
|
|
@@ -87,7 +188,7 @@ interface RenderResult {
|
|
|
87
188
|
*
|
|
88
189
|
* Features (all enabled by default):
|
|
89
190
|
* - GitHub Flavored Markdown (tables, strikethrough, task lists)
|
|
90
|
-
* - KaTeX math rendering ($$...$$
|
|
191
|
+
* - KaTeX math rendering, inline and block ($$...$$ only; single $ is not a delimiter)
|
|
91
192
|
* - Syntax highlighting via highlight.js
|
|
92
193
|
* - `data-source-line` attributes for line anchors
|
|
93
194
|
* - XSS sanitization
|
|
@@ -108,22 +209,98 @@ declare function renderMarkdown(content: string, options?: RenderOptions): Promi
|
|
|
108
209
|
* each rendered block a traceable line number from the source.
|
|
109
210
|
*/
|
|
110
211
|
|
|
111
|
-
|
|
212
|
+
interface RehypeSourceLinesOptions {
|
|
213
|
+
/**
|
|
214
|
+
* Lines stripped off the front of the file before parsing — frontmatter,
|
|
215
|
+
* essentially. Added to every emitted line number so `data-source-line`
|
|
216
|
+
* names a line in the *file* rather than in the parsed body, which is what
|
|
217
|
+
* a `#L42` link written against the file means. Defaults to 0.
|
|
218
|
+
*/
|
|
219
|
+
offset?: number;
|
|
220
|
+
}
|
|
221
|
+
declare const rehypeSourceLines: Plugin<[RehypeSourceLinesOptions?], Root>;
|
|
112
222
|
|
|
113
223
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
224
|
+
* The one definition of the Vantage remark/rehype chain.
|
|
225
|
+
*
|
|
226
|
+
* Three call sites render Markdown — `renderMarkdown` (string in, HTML out,
|
|
227
|
+
* which is what the CLI checker runs), the app's `<MarkdownViewer>`, and this
|
|
228
|
+
* package's exported `<MarkdownViewer>` — and each one used to hand-write the
|
|
229
|
+
* same plugin list in the same order. Three copies kept in sync by hand is how
|
|
230
|
+
* a plugin lands in the viewer and not in the checker: a document that styles
|
|
231
|
+
* in the app and renders bare through the tool that is supposed to validate it,
|
|
232
|
+
* with no error anywhere.
|
|
233
|
+
*
|
|
234
|
+
* The order is load-bearing, not incidental:
|
|
235
|
+
*
|
|
236
|
+
* - `rehypeRaw` first: `remark-rehype` runs with `allowDangerousHtml: true`,
|
|
237
|
+
* so raw HTML is still a string until this plugin parses it.
|
|
238
|
+
* - `rehypeSourceLines` before `rehypeSanitize`: `data-source-line` has to be
|
|
239
|
+
* an allowlisted attribute on an element the sanitiser keeps.
|
|
240
|
+
* - `rehypeSlug`, `rehypeHighlight` and `rehypeKatex` after `rehypeSanitize`.
|
|
241
|
+
* For `rehypeSlug` this is not a preference: the sanitiser's default schema
|
|
242
|
+
* clobbers `id` with the prefix `user-content-`, so slugging before it turns
|
|
243
|
+
* every `#heading` link in every document into a dead anchor. For the other
|
|
244
|
+
* two it means their output is trusted rather than filtered — KaTeX emits
|
|
245
|
+
* inline `style` on nearly every glyph.
|
|
246
|
+
*
|
|
247
|
+
* Anything that reads HTML comments must sit between `rehypeRaw` and
|
|
248
|
+
* `rehypeSanitize`: before `rehypeRaw` there are no comment nodes, and
|
|
249
|
+
* `rehypeSanitize` deletes them. `rehypeVantageDirectives` is what occupies
|
|
250
|
+
* that slot, and it is registered unconditionally — a renderer that skipped it
|
|
251
|
+
* would disagree with the others about what a document means.
|
|
252
|
+
*/
|
|
253
|
+
|
|
254
|
+
interface PipelineOptions {
|
|
255
|
+
/** GFM tables, strikethrough, task lists (default: true) */
|
|
256
|
+
gfm?: boolean;
|
|
257
|
+
/** KaTeX math, `$$…$$` only (default: true) */
|
|
258
|
+
math?: boolean;
|
|
259
|
+
/** Syntax highlighting via highlight.js (default: true) */
|
|
260
|
+
highlight?: boolean;
|
|
261
|
+
/** `data-source-line` attributes for line anchors (default: true) */
|
|
262
|
+
sourceLines?: boolean;
|
|
263
|
+
/** XSS sanitisation (default: true) */
|
|
264
|
+
sanitize?: boolean;
|
|
265
|
+
/**
|
|
266
|
+
* Lines the frontmatter consumed, added to every emitted line number so
|
|
267
|
+
* `data-source-line` names a line in the *file* rather than in the parsed
|
|
268
|
+
* body — which is what a `#L42` link written against the file means.
|
|
269
|
+
* Defaults to 0. Ignored when `sourceLines` is false.
|
|
270
|
+
*/
|
|
271
|
+
bodyLineOffset?: number;
|
|
272
|
+
}
|
|
273
|
+
interface Pipeline {
|
|
274
|
+
remarkPlugins: PluggableList;
|
|
275
|
+
rehypePlugins: PluggableList;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* The mdast half of the chain. Exported on its own because there is a real
|
|
279
|
+
* mdast-only consumer: the CLI checker parses documents without ever running
|
|
280
|
+
* rehype (`packages/vantage-check/src/core/document.ts`), and it has to parse
|
|
281
|
+
* them exactly the way the viewer does.
|
|
117
282
|
*/
|
|
283
|
+
declare function buildRemarkPlugins(options?: PipelineOptions): PluggableList;
|
|
118
284
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
285
|
+
* Both halves from one options object.
|
|
286
|
+
*
|
|
287
|
+
* This is what every renderer calls. It takes one object rather than exposing
|
|
288
|
+
* the two builders because `math` spans both halves — `remark-math` parses the
|
|
289
|
+
* delimiters, `rehype-katex` renders the result — and two calls are two places
|
|
290
|
+
* to forget the second one.
|
|
291
|
+
*
|
|
292
|
+
* Returns fresh arrays on every call and reads no module-level state; keep it
|
|
293
|
+
* that way, so a plugin in the chain cannot become a function of how many times
|
|
294
|
+
* the chain has been built.
|
|
295
|
+
*/
|
|
296
|
+
declare function buildPipeline(options?: PipelineOptions): Pipeline;
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Framework-agnostic line anchor utilities.
|
|
300
|
+
* Scroll to and highlight the elements a GitHub-style line anchor
|
|
301
|
+
* (#L42, #L42-L50) names. The parsing half lives in lineAnchor.ts, which has
|
|
302
|
+
* no DOM dependency.
|
|
122
303
|
*/
|
|
123
|
-
declare function parseLineAnchor(hash: string): {
|
|
124
|
-
start: number;
|
|
125
|
-
end: number;
|
|
126
|
-
} | null;
|
|
127
304
|
/**
|
|
128
305
|
* Clear all line anchor highlights from a container.
|
|
129
306
|
*/
|
|
@@ -137,15 +314,72 @@ declare function clearLineAnchorHighlights(container: HTMLElement): void;
|
|
|
137
314
|
*/
|
|
138
315
|
declare function scrollToLineAnchor(container: HTMLElement, hash: string): (() => void) | null;
|
|
139
316
|
|
|
317
|
+
/**
|
|
318
|
+
* Parsing for GitHub-style line anchors, with no DOM in sight.
|
|
319
|
+
*
|
|
320
|
+
* Split out from scrollToLineAnchor.ts so that non-browser consumers — the
|
|
321
|
+
* `vantage-check` CLI, which validates `#L42` links against the file on disk —
|
|
322
|
+
* can share the *same* syntax the viewer honours instead of reimplementing it
|
|
323
|
+
* and drifting.
|
|
324
|
+
*/
|
|
325
|
+
/**
|
|
326
|
+
* Parse a GitHub-style line anchor hash.
|
|
327
|
+
* Supports: #L42, #L42-L50, #L42-50
|
|
328
|
+
* Returns null if the hash is not a line anchor.
|
|
329
|
+
*/
|
|
330
|
+
declare function parseLineAnchor(hash: string): {
|
|
331
|
+
start: number;
|
|
332
|
+
end: number;
|
|
333
|
+
} | null;
|
|
334
|
+
|
|
140
335
|
/**
|
|
141
336
|
* Frontmatter parser for YAML (---) and TOML (+++) delimited content.
|
|
142
337
|
* Works in both browser and server environments.
|
|
143
338
|
*/
|
|
144
339
|
type FrontmatterFormat = "yaml" | "toml" | "none";
|
|
340
|
+
/**
|
|
341
|
+
* Why a document that *looks* like it has frontmatter ended up without any.
|
|
342
|
+
*
|
|
343
|
+
* The parser deliberately never throws: a document whose frontmatter is broken
|
|
344
|
+
* still renders, with the block treated as body text. That is the right
|
|
345
|
+
* behaviour for a viewer and the wrong one for an author, who gets no signal
|
|
346
|
+
* at all — so the reason is recorded here for anything that wants to report it
|
|
347
|
+
* (`vantage-check` does; see its frontmatter rules).
|
|
348
|
+
*
|
|
349
|
+
* - `unterminated` — an opening delimiter with no closing one.
|
|
350
|
+
* - `invalid` — the block did not parse; `message` is the parser's own words,
|
|
351
|
+
* and `line`/`column` are 1-based *within the block* when it said.
|
|
352
|
+
* - `not-a-mapping` — it parsed, but to a string or a list rather than a table
|
|
353
|
+
* of fields, which is not something a metadata card can render.
|
|
354
|
+
*/
|
|
355
|
+
interface FrontmatterProblem {
|
|
356
|
+
kind: "unterminated" | "invalid" | "not-a-mapping";
|
|
357
|
+
/** The delimiter the document opened with. */
|
|
358
|
+
delimiter: string;
|
|
359
|
+
message?: string;
|
|
360
|
+
line?: number;
|
|
361
|
+
column?: number;
|
|
362
|
+
}
|
|
145
363
|
interface ParsedFrontmatter {
|
|
146
364
|
frontmatter: Record<string, unknown>;
|
|
147
365
|
body: string;
|
|
148
366
|
format: FrontmatterFormat;
|
|
367
|
+
/**
|
|
368
|
+
* How many source lines the frontmatter block consumed — the shift between a
|
|
369
|
+
* line number in `body` and the same line in the original file:
|
|
370
|
+
* `fileLine = bodyLine + bodyLineOffset`.
|
|
371
|
+
*
|
|
372
|
+
* Anything that renders `body` and reports line numbers (line anchors, review
|
|
373
|
+
* comment anchors) has to add this back, or every number it produces points
|
|
374
|
+
* `bodyLineOffset` lines short of the text it names.
|
|
375
|
+
*/
|
|
376
|
+
bodyLineOffset: number;
|
|
377
|
+
/**
|
|
378
|
+
* Set when the document opens with a frontmatter delimiter that did not
|
|
379
|
+
* yield a metadata table. Everything else in this result is unchanged —
|
|
380
|
+
* this records *why*, it does not change what rendering does.
|
|
381
|
+
*/
|
|
382
|
+
problem?: FrontmatterProblem;
|
|
149
383
|
}
|
|
150
384
|
/**
|
|
151
385
|
* Parse frontmatter from markdown content.
|
|
@@ -160,6 +394,18 @@ declare function parseFrontmatter(content: string): ParsedFrontmatter;
|
|
|
160
394
|
*/
|
|
161
395
|
|
|
162
396
|
type Schema = typeof defaultSchema;
|
|
397
|
+
/**
|
|
398
|
+
* Never set `allowComments` here.
|
|
399
|
+
*
|
|
400
|
+
* `hast-util-sanitize` drops comment nodes because that boolean defaults to
|
|
401
|
+
* `false` — comments are not elements, so `tagNames` has nothing to do with it.
|
|
402
|
+
* `rehypeVantageDirectives` relies on that deletion: it consumes a
|
|
403
|
+
* `<!-- vantage: … -->` comment into attributes and deliberately leaves the node
|
|
404
|
+
* for the sanitiser. Turning the switch on readmits every directive comment —
|
|
405
|
+
* valid and malformed alike — into the rendered HTML, which breaks the carrier's
|
|
406
|
+
* whole premise. `vantageDirectives.test.ts` ("leaves no comment in the rendered
|
|
407
|
+
* markup") is the guard.
|
|
408
|
+
*/
|
|
163
409
|
declare const sanitizeSchema: Schema;
|
|
164
410
|
|
|
165
411
|
/**
|
|
@@ -241,4 +487,4 @@ interface ResolveLinkOptions {
|
|
|
241
487
|
*/
|
|
242
488
|
declare function resolveLinks(html: string, options?: ResolveLinkOptions): string;
|
|
243
489
|
|
|
244
|
-
export { FrontmatterDisplay, type FrontmatterFormat, MarkdownViewer, type MarkdownViewerProps, MermaidDiagram, type ParsedFrontmatter, type RenderMermaidOptions, type RenderOptions, type RenderResult, type ResolveLinkOptions, clearLineAnchorHighlights, parseFrontmatter, parseLineAnchor, rehypeSourceLines, renderMarkdown, renderMermaidBlocks, resolveLinks, sanitizeSchema, scrollToLineAnchor, useLineAnchor };
|
|
490
|
+
export { DOC_STATUSES, DOC_STATUS_TONES, type DocStatus, DocumentStatusChip, FrontmatterDisplay, type FrontmatterFormat, MarkdownViewer, type MarkdownViewerProps, MermaidDiagram, type ParsedFrontmatter, type Pipeline, type PipelineOptions, type RenderMermaidOptions, type RenderOptions, type RenderResult, type ResolveLinkOptions, VANTAGE_FRONTMATTER_KEYS, type VantageFrontmatter, type VantageFrontmatterIssue, buildPipeline, buildRemarkPlugins, clearLineAnchorHighlights, isDocStatus, parseFrontmatter, parseLineAnchor, readVantageFrontmatter, rehypeSourceLines, renderMarkdown, renderMermaidBlocks, resolveLinks, sanitizeSchema, scrollToLineAnchor, useLineAnchor };
|