@markii/ansi 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ansi.d.ts +79 -0
- package/dist/ansi.js +136 -0
- package/dist/box.d.ts +47 -0
- package/dist/box.js +209 -0
- package/dist/components/badge.d.ts +10 -0
- package/dist/components/badge.js +44 -0
- package/dist/components/callout.d.ts +20 -0
- package/dist/components/callout.js +71 -0
- package/dist/components/card.d.ts +12 -0
- package/dist/components/card.js +44 -0
- package/dist/components/cell.d.ts +18 -0
- package/dist/components/cell.js +31 -0
- package/dist/components/chart.d.ts +15 -0
- package/dist/components/chart.js +129 -0
- package/dist/components/details.d.ts +12 -0
- package/dist/components/details.js +25 -0
- package/dist/components/divider.d.ts +4 -0
- package/dist/components/divider.js +80 -0
- package/dist/components/figure.d.ts +20 -0
- package/dist/components/figure.js +53 -0
- package/dist/components/index.d.ts +34 -0
- package/dist/components/index.js +109 -0
- package/dist/components/kbd.d.ts +7 -0
- package/dist/components/kbd.js +8 -0
- package/dist/components/layout-wrapper.d.ts +35 -0
- package/dist/components/layout-wrapper.js +62 -0
- package/dist/components/progress.d.ts +15 -0
- package/dist/components/progress.js +78 -0
- package/dist/components/rating.d.ts +9 -0
- package/dist/components/rating.js +28 -0
- package/dist/components/row.d.ts +26 -0
- package/dist/components/row.js +67 -0
- package/dist/components/stat.d.ts +13 -0
- package/dist/components/stat.js +79 -0
- package/dist/components/tab.d.ts +13 -0
- package/dist/components/tab.js +17 -0
- package/dist/components/table-grid.d.ts +34 -0
- package/dist/components/table-grid.js +124 -0
- package/dist/components/table.d.ts +16 -0
- package/dist/components/table.js +101 -0
- package/dist/components/tabs.d.ts +18 -0
- package/dist/components/tabs.js +30 -0
- package/dist/failure-presentation.d.ts +58 -0
- package/dist/failure-presentation.js +124 -0
- package/dist/href-resolve.d.ts +21 -0
- package/dist/href-resolve.js +25 -0
- package/dist/image-resolve.d.ts +41 -0
- package/dist/image-resolve.js +28 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +17 -0
- package/dist/layout.d.ts +73 -0
- package/dist/layout.js +144 -0
- package/dist/measure.d.ts +25 -0
- package/dist/measure.js +108 -0
- package/dist/registry.d.ts +227 -0
- package/dist/registry.js +121 -0
- package/dist/render.d.ts +69 -0
- package/dist/render.js +889 -0
- package/dist/resolve.d.ts +60 -0
- package/dist/resolve.js +152 -0
- package/dist/sanitize.d.ts +51 -0
- package/dist/sanitize.js +101 -0
- package/dist/style.d.ts +9 -0
- package/dist/style.js +13 -0
- package/dist/theme.d.ts +36 -0
- package/dist/theme.js +79 -0
- package/dist/url-resolve.d.ts +54 -0
- package/dist/url-resolve.js +82 -0
- package/dist/value-format.d.ts +13 -0
- package/dist/value-format.js +16 -0
- package/dist/value-types.d.ts +37 -0
- package/dist/value-types.js +17 -0
- package/package.json +61 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { deriveTableShape, formatValue, isNumericLike } from '@markii/stdlib';
|
|
2
|
+
import { selfLayoutAlign, selfLayoutWidth } from '../layout.js';
|
|
3
|
+
import { safeRead } from '../resolve.js';
|
|
4
|
+
import { dataStateSuffix, failureToken } from '../failure-presentation.js';
|
|
5
|
+
import { drawTableGrid, measureTableGridWidth } from './table-grid.js';
|
|
6
|
+
function parseColumns(raw) {
|
|
7
|
+
if (!raw)
|
|
8
|
+
return undefined;
|
|
9
|
+
const list = raw
|
|
10
|
+
.split(',')
|
|
11
|
+
.map((entry) => entry.trim())
|
|
12
|
+
.filter((entry) => entry.length > 0);
|
|
13
|
+
return list.length > 0 ? list : undefined;
|
|
14
|
+
}
|
|
15
|
+
function parseLimit(raw) {
|
|
16
|
+
if (!raw)
|
|
17
|
+
return undefined;
|
|
18
|
+
const trimmed = raw.trim();
|
|
19
|
+
if (!/^\d+$/.test(trimmed))
|
|
20
|
+
return undefined;
|
|
21
|
+
const parsed = Number(trimmed);
|
|
22
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
23
|
+
}
|
|
24
|
+
function limitRows(rows, limit) {
|
|
25
|
+
return limit === undefined ? rows : rows.slice(0, limit);
|
|
26
|
+
}
|
|
27
|
+
/** Renders one cell's display text: `format`/`decimals` apply only when the cell's raw value is itself numeric-like, matching `@markii/html`'s `Table` exactly. */
|
|
28
|
+
function renderCell(value, format, decimals) {
|
|
29
|
+
if (format && isNumericLike(value))
|
|
30
|
+
return formatValue(value, format, decimals);
|
|
31
|
+
return formatValue(value);
|
|
32
|
+
}
|
|
33
|
+
const TEXT_ALIGNS = ['left', 'center', 'right'];
|
|
34
|
+
function isTextAlign(value) {
|
|
35
|
+
return TEXT_ALIGNS.includes(value);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* `::table{data=users columns="name,role" limit=10}` — a data-bound table.
|
|
39
|
+
* Data binding (§8) mirrors `@markii/html`'s `Table`: `@markii/stdlib`'s
|
|
40
|
+
* `deriveTableShape` decides the layout from the bound value's own shape.
|
|
41
|
+
* `format`/`decimals` apply to numeric cells only; `text` aligns every
|
|
42
|
+
* cell's content within its column; `caption`, when given, is a bold line
|
|
43
|
+
* above the grid. A missing/stale/failed binding degrades to the same quiet
|
|
44
|
+
* "no data" line `chart`/`progress` use, with the failure suffix appended.
|
|
45
|
+
*
|
|
46
|
+
* Registered `selfLayout` (see `card.ts`'s doc comment): draws real
|
|
47
|
+
* box-drawing glyphs (`./table-grid.ts`), so it sizes its own grid off
|
|
48
|
+
* `ctx.layout` instead of letting a generic post-render narrow/pad corrupt
|
|
49
|
+
* the borders.
|
|
50
|
+
*/
|
|
51
|
+
export const Table = (attributes, _children, ctx) => {
|
|
52
|
+
const { data, dataStatus, dataFailureKind } = ctx;
|
|
53
|
+
const columnsOverride = parseColumns(attributes.columns);
|
|
54
|
+
const limit = parseLimit(attributes.limit);
|
|
55
|
+
const format = attributes.format ?? undefined;
|
|
56
|
+
const decimals = attributes.decimals ?? undefined;
|
|
57
|
+
const caption = attributes.caption ?? null;
|
|
58
|
+
const rawTextAlign = attributes.text;
|
|
59
|
+
const align = rawTextAlign && isTextAlign(rawTextAlign) ? rawTextAlign : 'left';
|
|
60
|
+
const bound = safeRead(() => dataStatus === 'missing' || dataStatus === 'error'
|
|
61
|
+
? { kind: 'empty' }
|
|
62
|
+
: deriveTableShape(data, columnsOverride), () => ({ kind: 'empty' }));
|
|
63
|
+
const shape = bound.fields;
|
|
64
|
+
const suffix = dataStateSuffix(dataStatus, dataFailureKind);
|
|
65
|
+
const token = failureToken(dataFailureKind);
|
|
66
|
+
const styledSuffix = suffix
|
|
67
|
+
? token
|
|
68
|
+
? ctx.style(suffix, token)
|
|
69
|
+
: ctx.dim(suffix)
|
|
70
|
+
: '';
|
|
71
|
+
const captionLine = caption ? ctx.bold(ctx.text(caption)) : undefined;
|
|
72
|
+
if (shape.kind === 'empty') {
|
|
73
|
+
const emptyLine = `${ctx.dim('no data')}${styledSuffix}`;
|
|
74
|
+
return captionLine ? `${captionLine}\n${emptyLine}` : emptyLine;
|
|
75
|
+
}
|
|
76
|
+
const cell = (value) => ctx.text(renderCell(value, format, decimals));
|
|
77
|
+
let header;
|
|
78
|
+
let rows;
|
|
79
|
+
if (shape.kind === 'objects') {
|
|
80
|
+
header = shape.columns.map((column) => ctx.text(column));
|
|
81
|
+
rows = limitRows(shape.rows, limit).map((cells) => cells.map(cell));
|
|
82
|
+
}
|
|
83
|
+
else if (shape.kind === 'arrays') {
|
|
84
|
+
rows = limitRows(shape.rows, limit).map((cells) => cells.map(cell));
|
|
85
|
+
}
|
|
86
|
+
else if (shape.kind === 'primitives') {
|
|
87
|
+
rows = limitRows(shape.rows, limit).map((cells) => [cell(cells[0])]);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
rows = limitRows(shape.rows, limit).map(([key, value]) => [
|
|
91
|
+
ctx.text(key),
|
|
92
|
+
cell(value),
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
const naturalWidth = measureTableGridWidth(header, rows);
|
|
96
|
+
const boxWidth = selfLayoutWidth(ctx.layout, ctx.width, naturalWidth);
|
|
97
|
+
const grid = drawTableGrid(header, rows, boxWidth, (text) => ctx.bold(text), align);
|
|
98
|
+
const body = `${grid}${styledSuffix}`;
|
|
99
|
+
const withCaption = captionLine ? `${captionLine}\n${body}` : body;
|
|
100
|
+
return selfLayoutAlign(withCaption, ctx.layout, ctx.width);
|
|
101
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AnsiComponent } from '../registry.js';
|
|
2
|
+
/**
|
|
3
|
+
* `::::tabs :::tab{label="..."} ... ::: :::tab{label="..."} ... ::: ::::` —
|
|
4
|
+
* a tabbed panel switcher. A terminal has no click-driven tab switching (see
|
|
5
|
+
* `render.ts`'s top comment on interactive components), so every panel is
|
|
6
|
+
* shown, stacked, in document order — matching `@markii/html`'s `Tabs`
|
|
7
|
+
* faithfulness limitation of showing every panel rather than picking one.
|
|
8
|
+
*
|
|
9
|
+
* "The active tab marked": since every panel is always visible here, this
|
|
10
|
+
* engine treats the FIRST tab (document order) as the one a live host would
|
|
11
|
+
* show before any interaction, and marks only its heading line — every
|
|
12
|
+
* `tab` child's block is joined into `childrenText` with a blank line
|
|
13
|
+
* between them (`render.ts`'s `renderBlocks`), the same separator this
|
|
14
|
+
* engine uses between any two sibling blocks, so splitting on the first
|
|
15
|
+
* blank line reliably finds the boundary between the first tab's block and
|
|
16
|
+
* the rest without needing any information `tabs` does not have.
|
|
17
|
+
*/
|
|
18
|
+
export declare const Tabs: AnsiComponent;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** The visible marker appended to whichever tab is treated as "active" (see this module's doc comment for how that is decided). */
|
|
2
|
+
const ACTIVE_MARKER = ' (active)';
|
|
3
|
+
/**
|
|
4
|
+
* `::::tabs :::tab{label="..."} ... ::: :::tab{label="..."} ... ::: ::::` —
|
|
5
|
+
* a tabbed panel switcher. A terminal has no click-driven tab switching (see
|
|
6
|
+
* `render.ts`'s top comment on interactive components), so every panel is
|
|
7
|
+
* shown, stacked, in document order — matching `@markii/html`'s `Tabs`
|
|
8
|
+
* faithfulness limitation of showing every panel rather than picking one.
|
|
9
|
+
*
|
|
10
|
+
* "The active tab marked": since every panel is always visible here, this
|
|
11
|
+
* engine treats the FIRST tab (document order) as the one a live host would
|
|
12
|
+
* show before any interaction, and marks only its heading line — every
|
|
13
|
+
* `tab` child's block is joined into `childrenText` with a blank line
|
|
14
|
+
* between them (`render.ts`'s `renderBlocks`), the same separator this
|
|
15
|
+
* engine uses between any two sibling blocks, so splitting on the first
|
|
16
|
+
* blank line reliably finds the boundary between the first tab's block and
|
|
17
|
+
* the rest without needing any information `tabs` does not have.
|
|
18
|
+
*/
|
|
19
|
+
export const Tabs = (_attributes, children) => {
|
|
20
|
+
const childrenText = children();
|
|
21
|
+
if (!childrenText.trim())
|
|
22
|
+
return '';
|
|
23
|
+
const blocks = childrenText.split('\n\n');
|
|
24
|
+
const [first, ...rest] = blocks;
|
|
25
|
+
if (first === undefined)
|
|
26
|
+
return childrenText;
|
|
27
|
+
const lines = first.split('\n');
|
|
28
|
+
lines[0] = `${lines[0] ?? ''}${ACTIVE_MARKER}`;
|
|
29
|
+
return [lines.join('\n'), ...rest].join('\n\n');
|
|
30
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { FailureKind, ValueStatus } from './value-types.js';
|
|
2
|
+
import type { Tier1Token } from './theme.js';
|
|
3
|
+
/** The short phrase for `kind`, or `undefined` if absent or not one of the four taxonomy members. */
|
|
4
|
+
export declare function failurePhrase(kind: FailureKind | undefined): string | undefined;
|
|
5
|
+
/** The tooltip-equivalent text for a failed/missing binding: the short phrase for `kind`, with `error` appended when there is one. */
|
|
6
|
+
export declare function failureTitle(error: string | undefined, kind: FailureKind | undefined): string | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* The theme token a failed binding's marker is colored with, this engine's
|
|
9
|
+
* terminal equivalent of `@markii/html`'s `failureKindClass`: `script-error`
|
|
10
|
+
* and `capability-denied` both read as `--mk-danger` (the note asked for
|
|
11
|
+
* something and could not get it, one way or another), `tier-blocked` reads
|
|
12
|
+
* as `--mk-warning` (a manual run would fix it, nothing is actually broken),
|
|
13
|
+
* `limit` reads as `--mk-limit`'s dedicated purple. `undefined` for an
|
|
14
|
+
* absent or out-of-taxonomy `kind`, in which case a caller applies no color
|
|
15
|
+
* at all rather than guessing one.
|
|
16
|
+
*/
|
|
17
|
+
export declare function failureToken(kind: FailureKind | undefined): Tier1Token | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* The quiet trailing marker text for a data-bound value's state: ` (stale)`
|
|
20
|
+
* for a stale value, ` (<phrase>)` for a failed one (built from the SAME
|
|
21
|
+
* `FAILURE_PHRASE` table `failureTitle` uses, so the two never say something
|
|
22
|
+
* different about the same failure), `''` for a fresh or plain-missing
|
|
23
|
+
* value (a plain miss already shows as the empty/placeholder marker
|
|
24
|
+
* `render.ts` builds; it needs no additional suffix). `status: 'error'`
|
|
25
|
+
* without a recognized `kind` still gets the generic wording via
|
|
26
|
+
* `failurePhrase`'s `undefined` fallback in the caller.
|
|
27
|
+
*/
|
|
28
|
+
export declare function dataStateSuffix(status: ValueStatus | undefined, kind: FailureKind | undefined): string;
|
|
29
|
+
/**
|
|
30
|
+
* The marker text `render.ts` shows for an INLINE-registered component that
|
|
31
|
+
* receives no content (e.g. `::badge{label="x"}`, text put in an attribute
|
|
32
|
+
* instead of the directive body). Wording identical to `@markii/html`'s
|
|
33
|
+
* `emptyInlineTitle`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function emptyInlineTitle(name: string): string;
|
|
36
|
+
/** The marker text for a known attribute's value outside its closed enum. Wording identical to `@markii/html`'s `invalidAttributeValueTitle`. */
|
|
37
|
+
export declare function invalidAttributeValueTitle(directive: string, attribute: string, value: string): string;
|
|
38
|
+
/** The marker text for a component-built image whose `src` was refused as unsafe. Wording identical to `@markii/html`'s `unsafeImageSrcTitle`. */
|
|
39
|
+
export declare function unsafeImageSrcTitle(directive: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* The INLINE marker text for a known attribute's value outside its closed
|
|
42
|
+
* enum, this engine's own departure from `@markii/html`'s identical wording:
|
|
43
|
+
* a terminal has no tooltip to carry `invalidAttributeValueTitle`'s full
|
|
44
|
+
* sentence out of the text flow, so printing that sentence inline would put
|
|
45
|
+
* the reason back in the page, the exact thing AGENTS.md's "clean is not
|
|
46
|
+
* silent" rule exists to prevent. This short label is what render.ts prints
|
|
47
|
+
* next to the component's own output; the full sentence still reaches a
|
|
48
|
+
* host's diagnostics surface via `onDiagnostic`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function invalidAttributeValueLabel(directive: string, attribute: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* The INLINE marker text for a component-built image whose `src` was
|
|
53
|
+
* refused as unsafe, this engine's short counterpart to
|
|
54
|
+
* `unsafeImageSrcTitle` for the same reason `invalidAttributeValueLabel`
|
|
55
|
+
* exists: no tooltip channel, so the full sentence goes to `onDiagnostic`
|
|
56
|
+
* only.
|
|
57
|
+
*/
|
|
58
|
+
export declare function unsafeImageSrcLabel(directive: string): string;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This engine's port of `@markii/html`'s `failure-presentation.ts` — the ONE
|
|
3
|
+
* place UI wording for `@markii/runtime`'s failure taxonomy lives in this
|
|
4
|
+
* engine. Ported (not imported) for the same reason `./resolve.ts` is
|
|
5
|
+
* ported: each platform renderer independently implements the same
|
|
6
|
+
* presentation contract (docs/scripting.md), kept identical in wording so a
|
|
7
|
+
* failing name reads the same everywhere. `failure-presentation.drift.test.ts`
|
|
8
|
+
* is the executable proof: it reads `@markii/html`'s copy of this module as
|
|
9
|
+
* TEXT and asserts the phrases and title templates match byte for byte.
|
|
10
|
+
*
|
|
11
|
+
* The presentation contract (AGENTS.md's cleanliness principle) carries over
|
|
12
|
+
* unchanged: a failure never becomes body text. In a terminal it surfaces as
|
|
13
|
+
* a quiet trailing marker (`dataStateSuffix`) plus, where the caller wants
|
|
14
|
+
* it, a themed color (`failureToken`) — the terminal's equivalent of the
|
|
15
|
+
* HTML engine's `title` tooltip and modifier class, since neither exists in
|
|
16
|
+
* a terminal.
|
|
17
|
+
*
|
|
18
|
+
* Two notices (an out-of-enum attribute value, a refused image source) carry
|
|
19
|
+
* a full sentence in the browser engines' `title` tooltip, reached out of
|
|
20
|
+
* the text flow. A terminal has no tooltip, so printing that sentence inline
|
|
21
|
+
* would put the reason back in the rendered note. `invalidAttributeValueLabel`
|
|
22
|
+
* and `unsafeImageSrcLabel` are this engine's short inline labels for those
|
|
23
|
+
* two cases; the full sentence (`invalidAttributeValueTitle`/
|
|
24
|
+
* `unsafeImageSrcTitle`, unchanged) still reaches a host only through
|
|
25
|
+
* `onDiagnostic`.
|
|
26
|
+
*/
|
|
27
|
+
/** Human-facing phrase per `FailureKind`. Identical wording to `@markii/html`'s `FAILURE_PHRASE`; kept in sync by `failure-presentation.drift.test.ts`. Null-prototype so an out-of-taxonomy `kind` can never resolve through the prototype chain. */
|
|
28
|
+
const FAILURE_PHRASE = Object.assign(Object.create(null), {
|
|
29
|
+
'script-error': 'script error',
|
|
30
|
+
'capability-denied': 'needs permission',
|
|
31
|
+
'tier-blocked': 'requires manual run',
|
|
32
|
+
limit: 'limit exceeded',
|
|
33
|
+
});
|
|
34
|
+
/** The short phrase for `kind`, or `undefined` if absent or not one of the four taxonomy members. */
|
|
35
|
+
export function failurePhrase(kind) {
|
|
36
|
+
if (kind === undefined)
|
|
37
|
+
return undefined;
|
|
38
|
+
return Object.hasOwn(FAILURE_PHRASE, kind) ? FAILURE_PHRASE[kind] : undefined;
|
|
39
|
+
}
|
|
40
|
+
/** The tooltip-equivalent text for a failed/missing binding: the short phrase for `kind`, with `error` appended when there is one. */
|
|
41
|
+
export function failureTitle(error, kind) {
|
|
42
|
+
const phrase = failurePhrase(kind);
|
|
43
|
+
if (!phrase)
|
|
44
|
+
return error ? error : undefined;
|
|
45
|
+
return error ? `${phrase}: ${error}` : phrase;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The theme token a failed binding's marker is colored with, this engine's
|
|
49
|
+
* terminal equivalent of `@markii/html`'s `failureKindClass`: `script-error`
|
|
50
|
+
* and `capability-denied` both read as `--mk-danger` (the note asked for
|
|
51
|
+
* something and could not get it, one way or another), `tier-blocked` reads
|
|
52
|
+
* as `--mk-warning` (a manual run would fix it, nothing is actually broken),
|
|
53
|
+
* `limit` reads as `--mk-limit`'s dedicated purple. `undefined` for an
|
|
54
|
+
* absent or out-of-taxonomy `kind`, in which case a caller applies no color
|
|
55
|
+
* at all rather than guessing one.
|
|
56
|
+
*/
|
|
57
|
+
export function failureToken(kind) {
|
|
58
|
+
if (kind === 'script-error' || kind === 'capability-denied')
|
|
59
|
+
return '--mk-danger';
|
|
60
|
+
if (kind === 'tier-blocked')
|
|
61
|
+
return '--mk-warning';
|
|
62
|
+
if (kind === 'limit')
|
|
63
|
+
return '--mk-limit';
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The quiet trailing marker text for a data-bound value's state: ` (stale)`
|
|
68
|
+
* for a stale value, ` (<phrase>)` for a failed one (built from the SAME
|
|
69
|
+
* `FAILURE_PHRASE` table `failureTitle` uses, so the two never say something
|
|
70
|
+
* different about the same failure), `''` for a fresh or plain-missing
|
|
71
|
+
* value (a plain miss already shows as the empty/placeholder marker
|
|
72
|
+
* `render.ts` builds; it needs no additional suffix). `status: 'error'`
|
|
73
|
+
* without a recognized `kind` still gets the generic wording via
|
|
74
|
+
* `failurePhrase`'s `undefined` fallback in the caller.
|
|
75
|
+
*/
|
|
76
|
+
export function dataStateSuffix(status, kind) {
|
|
77
|
+
if (status === 'error') {
|
|
78
|
+
const phrase = failurePhrase(kind) ?? 'error';
|
|
79
|
+
return ` (${phrase})`;
|
|
80
|
+
}
|
|
81
|
+
if (status === 'stale')
|
|
82
|
+
return ' (stale)';
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The marker text `render.ts` shows for an INLINE-registered component that
|
|
87
|
+
* receives no content (e.g. `::badge{label="x"}`, text put in an attribute
|
|
88
|
+
* instead of the directive body). Wording identical to `@markii/html`'s
|
|
89
|
+
* `emptyInlineTitle`.
|
|
90
|
+
*/
|
|
91
|
+
export function emptyInlineTitle(name) {
|
|
92
|
+
return `${name}: no content (an attribute may have been used where directive text was expected)`;
|
|
93
|
+
}
|
|
94
|
+
/** The marker text for a known attribute's value outside its closed enum. Wording identical to `@markii/html`'s `invalidAttributeValueTitle`. */
|
|
95
|
+
export function invalidAttributeValueTitle(directive, attribute, value) {
|
|
96
|
+
return `${directive}: "${value}" is not a valid ${attribute} value (ignored)`;
|
|
97
|
+
}
|
|
98
|
+
/** The marker text for a component-built image whose `src` was refused as unsafe. Wording identical to `@markii/html`'s `unsafeImageSrcTitle`. */
|
|
99
|
+
export function unsafeImageSrcTitle(directive) {
|
|
100
|
+
return `${directive}: image source was refused as unsafe and was not shown`;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The INLINE marker text for a known attribute's value outside its closed
|
|
104
|
+
* enum, this engine's own departure from `@markii/html`'s identical wording:
|
|
105
|
+
* a terminal has no tooltip to carry `invalidAttributeValueTitle`'s full
|
|
106
|
+
* sentence out of the text flow, so printing that sentence inline would put
|
|
107
|
+
* the reason back in the page, the exact thing AGENTS.md's "clean is not
|
|
108
|
+
* silent" rule exists to prevent. This short label is what render.ts prints
|
|
109
|
+
* next to the component's own output; the full sentence still reaches a
|
|
110
|
+
* host's diagnostics surface via `onDiagnostic`.
|
|
111
|
+
*/
|
|
112
|
+
export function invalidAttributeValueLabel(directive, attribute) {
|
|
113
|
+
return `${directive}: ${attribute} ignored`;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The INLINE marker text for a component-built image whose `src` was
|
|
117
|
+
* refused as unsafe, this engine's short counterpart to
|
|
118
|
+
* `unsafeImageSrcTitle` for the same reason `invalidAttributeValueLabel`
|
|
119
|
+
* exists: no tooltip channel, so the full sentence goes to `onDiagnostic`
|
|
120
|
+
* only.
|
|
121
|
+
*/
|
|
122
|
+
export function unsafeImageSrcLabel(directive) {
|
|
123
|
+
return `${directive}: image not shown`;
|
|
124
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The link-rewrite twin of `./image-resolve.js`'s `resolveImageSrc`:
|
|
3
|
+
* `renderMarkToAnsi`'s `resolveHref` option (`render.ts`'s
|
|
4
|
+
* `RenderMarkOptions`), applied to every link an ordinary markdown link
|
|
5
|
+
* produces. Ported verbatim from `@markii/html`'s `href-resolve.ts`.
|
|
6
|
+
*
|
|
7
|
+
* Same resolvability rule as images (no scheme, no protocol-relative
|
|
8
|
+
* `//host/...`, no bare `#fragment`, not empty), and the same
|
|
9
|
+
* `javascript:`/`vbscript:` refusal on the resolver's OWN return value,
|
|
10
|
+
* both shared with `./image-resolve.js` via `./url-resolve.js`.
|
|
11
|
+
*/
|
|
12
|
+
export type ResolveHref = (href: string) => string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* The value one link should actually carry: `value` unchanged unless
|
|
15
|
+
* `resolveHref` is present, `value` is worth resolving at all, the resolver
|
|
16
|
+
* returns something, and that something passes `isSafeResolvedUrl`. A
|
|
17
|
+
* resolver that throws is treated exactly like one that returned
|
|
18
|
+
* `undefined`: `value` is kept, and the render is never broken over one
|
|
19
|
+
* link.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveHrefAttribute(value: string, resolveHref: ResolveHref | undefined): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { isResolvableRelativeValue, isSafeResolvedUrl } from './url-resolve.js';
|
|
2
|
+
/**
|
|
3
|
+
* The value one link should actually carry: `value` unchanged unless
|
|
4
|
+
* `resolveHref` is present, `value` is worth resolving at all, the resolver
|
|
5
|
+
* returns something, and that something passes `isSafeResolvedUrl`. A
|
|
6
|
+
* resolver that throws is treated exactly like one that returned
|
|
7
|
+
* `undefined`: `value` is kept, and the render is never broken over one
|
|
8
|
+
* link.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveHrefAttribute(value, resolveHref) {
|
|
11
|
+
if (!resolveHref)
|
|
12
|
+
return value;
|
|
13
|
+
if (!isResolvableRelativeValue(value))
|
|
14
|
+
return value;
|
|
15
|
+
let resolved;
|
|
16
|
+
try {
|
|
17
|
+
resolved = resolveHref(value);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
if (resolved === undefined)
|
|
23
|
+
return value;
|
|
24
|
+
return isSafeResolvedUrl(resolved) ? resolved : value;
|
|
25
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared logic behind `renderMarkToAnsi`'s `resolveImageSrc` option
|
|
3
|
+
* (see `render.ts`'s `RenderMarkOptions`), used everywhere an image
|
|
4
|
+
* reference reaches this engine's output: an ordinary markdown image and
|
|
5
|
+
* any future standard component that builds its own image reference from a
|
|
6
|
+
* directive attribute. Ported verbatim from `@markii/html`'s
|
|
7
|
+
* `image-resolve.ts` (itself mirroring `@markii/react`'s).
|
|
8
|
+
*
|
|
9
|
+
* A host resolver is only ever asked about a source that could plausibly
|
|
10
|
+
* be its own: one with no scheme, no protocol-relative `//host/...` form,
|
|
11
|
+
* no bare `#fragment`, and no empty/whitespace value — `./url-resolve.js`'s
|
|
12
|
+
* `isResolvableRelativeValue`, shared with `resolveHref`
|
|
13
|
+
* (`./href-resolve.js`) rather than duplicated.
|
|
14
|
+
*
|
|
15
|
+
* WHY THE RESULT CHECK IS NOT `isSafeUrl`. `isSafeUrl`'s allowlist
|
|
16
|
+
* (`http`/`https`/`mailto`/`tel`) exists to judge a URL an AUTHOR typed
|
|
17
|
+
* into the document, where any other scheme is suspicious. A resolver's
|
|
18
|
+
* RETURN VALUE is the opposite trust direction: it is the HOST's own
|
|
19
|
+
* answer for where its resolved image actually lives, and the two
|
|
20
|
+
* reference hosts already return values `isSafeUrl` would reject outright
|
|
21
|
+
* (a `data:` URI, an `app://` vault path). What still needs guarding
|
|
22
|
+
* against is a resolver, hostile or merely buggy, echoing a
|
|
23
|
+
* `javascript:`/`vbscript:` value back out — the one class of scheme that
|
|
24
|
+
* turns an image reference into a script-execution vector rather than an
|
|
25
|
+
* image request. `./url-resolve.js`'s `isSafeResolvedUrl` is a narrow
|
|
26
|
+
* denylist for exactly that, not a repeat of the author-facing allowlist.
|
|
27
|
+
*/
|
|
28
|
+
/** The shape `renderMarkToAnsi`/`renderMarkNodeToAnsi` accept, and the one carried on `AnsiRenderContext` for a component that resolves its own image reference. */
|
|
29
|
+
export type ResolveImageSrc = (src: string) => string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* The value one image reference should actually carry: `value` unchanged
|
|
32
|
+
* unless `resolveImageSrc` is present, `value` is worth resolving at all,
|
|
33
|
+
* the resolver returns something, and that something passes
|
|
34
|
+
* `isSafeResolvedUrl` — so a resolver can never smuggle a `javascript:` URL
|
|
35
|
+
* past the sanitizer that already ran on everything else in the document,
|
|
36
|
+
* while a legitimate `data:`/`app:`/host-scheme result still reaches the
|
|
37
|
+
* output. A resolver that throws is treated exactly like one that returned
|
|
38
|
+
* `undefined`: `value` is kept, and the render is never broken over one
|
|
39
|
+
* image.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveImageAttribute(value: string, resolveImageSrc: ResolveImageSrc | undefined): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { isResolvableRelativeValue, isSafeResolvedUrl } from './url-resolve.js';
|
|
2
|
+
/**
|
|
3
|
+
* The value one image reference should actually carry: `value` unchanged
|
|
4
|
+
* unless `resolveImageSrc` is present, `value` is worth resolving at all,
|
|
5
|
+
* the resolver returns something, and that something passes
|
|
6
|
+
* `isSafeResolvedUrl` — so a resolver can never smuggle a `javascript:` URL
|
|
7
|
+
* past the sanitizer that already ran on everything else in the document,
|
|
8
|
+
* while a legitimate `data:`/`app:`/host-scheme result still reaches the
|
|
9
|
+
* output. A resolver that throws is treated exactly like one that returned
|
|
10
|
+
* `undefined`: `value` is kept, and the render is never broken over one
|
|
11
|
+
* image.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveImageAttribute(value, resolveImageSrc) {
|
|
14
|
+
if (!resolveImageSrc)
|
|
15
|
+
return value;
|
|
16
|
+
if (!isResolvableRelativeValue(value))
|
|
17
|
+
return value;
|
|
18
|
+
let resolved;
|
|
19
|
+
try {
|
|
20
|
+
resolved = resolveImageSrc(value);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
if (resolved === undefined)
|
|
26
|
+
return value;
|
|
27
|
+
return isSafeResolvedUrl(resolved) ? resolved : value;
|
|
28
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { renderMarkToAnsi, renderMarkNodeToAnsi, renderMarkInlineToAnsi, type RenderMarkOptions, } from './render.js';
|
|
2
|
+
export { fg, colorize, bold, dim, italic, underline, inverse, hyperlink, detectColorLevel, resolveColorOption, type ColorLevel, type ColorOption, type AnsiColor, } from './ansi.js';
|
|
3
|
+
export { defaultAnsiTheme, type AnsiTheme, type Tier1Token } from './theme.js';
|
|
4
|
+
export { style } from './style.js';
|
|
5
|
+
export { createAnsiRegistry, mergeAnsiRegistries, registryAliases, readRegistryComponent, resolveDirectiveAlias, REGISTRY_ALIASES, type DirectiveAttributes, type AnsiRegistry, type AnsiRegistryEntry, type AnsiComponent, type AnsiChildren, type AnsiChildPart, type AnsiChildrenOptions, type AnsiRenderContext, type RegistryAlias, type RegistryAliases, type ResolvedDirective, type ValueResolution, } from './registry.js';
|
|
6
|
+
export { resolveStorePath, resolveScopedPath, VAULT_NAME_PREFIX, type StorePathResolution, type ValueScope, } from './resolve.js';
|
|
7
|
+
export { failurePhrase, failureTitle, failureToken, dataStateSuffix, emptyInlineTitle, invalidAttributeValueLabel, invalidAttributeValueTitle, unsafeImageSrcLabel, unsafeImageSrcTitle, } from './failure-presentation.js';
|
|
8
|
+
export { stringifyStoredValue } from './value-format.js';
|
|
9
|
+
export { measure, stripAnsi } from './measure.js';
|
|
10
|
+
export { stripControlCharacters, sanitizeBlockText, sanitizeUrlText, BLOCK_TAB_WIDTH, } from './sanitize.js';
|
|
11
|
+
export { wrap, pad, indentBlock, columns, frame, rule, type FrameOptions, } from './box.js';
|
|
12
|
+
export { resolveLayoutAttributes, applyLayout, LAYOUT_ATTRIBUTE_KEYS, type ResolvedLayoutAttributes, type ResolvedLayoutPresets, type WidthPreset, type AlignPreset, } from './layout.js';
|
|
13
|
+
export { type ResolveImageSrc } from './image-resolve.js';
|
|
14
|
+
export { type ResolveHref } from './href-resolve.js';
|
|
15
|
+
export type { AnsiValueStore, AnsiVaultStore, FailureKind, StoredValue, ValueStatus, } from './value-types.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// @markii/ansi: a framework-free terminal renderer for Markii documents. It
|
|
2
|
+
// consumes @markii/core's sanitized hast and emits a plain string (optionally
|
|
3
|
+
// carrying ANSI SGR/OSC 8 escapes) for a terminal, a pipe, or any host that
|
|
4
|
+
// wants text with no HTML and no React runtime. It is a third platform
|
|
5
|
+
// renderer alongside @markii/html and @markii/react.
|
|
6
|
+
export { renderMarkToAnsi, renderMarkNodeToAnsi, renderMarkInlineToAnsi, } from './render.js';
|
|
7
|
+
export { fg, colorize, bold, dim, italic, underline, inverse, hyperlink, detectColorLevel, resolveColorOption, } from './ansi.js';
|
|
8
|
+
export { defaultAnsiTheme } from './theme.js';
|
|
9
|
+
export { style } from './style.js';
|
|
10
|
+
export { createAnsiRegistry, mergeAnsiRegistries, registryAliases, readRegistryComponent, resolveDirectiveAlias, REGISTRY_ALIASES, } from './registry.js';
|
|
11
|
+
export { resolveStorePath, resolveScopedPath, VAULT_NAME_PREFIX, } from './resolve.js';
|
|
12
|
+
export { failurePhrase, failureTitle, failureToken, dataStateSuffix, emptyInlineTitle, invalidAttributeValueLabel, invalidAttributeValueTitle, unsafeImageSrcLabel, unsafeImageSrcTitle, } from './failure-presentation.js';
|
|
13
|
+
export { stringifyStoredValue } from './value-format.js';
|
|
14
|
+
export { measure, stripAnsi } from './measure.js';
|
|
15
|
+
export { stripControlCharacters, sanitizeBlockText, sanitizeUrlText, BLOCK_TAB_WIDTH, } from './sanitize.js';
|
|
16
|
+
export { wrap, pad, indentBlock, columns, frame, rule, } from './box.js';
|
|
17
|
+
export { resolveLayoutAttributes, applyLayout, LAYOUT_ATTRIBUTE_KEYS, } from './layout.js';
|
package/dist/layout.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { ALIGN_PRESETS, LAYOUT_ATTRIBUTE_KEYS, WIDTH_PRESETS } from '@markii/stdlib';
|
|
2
|
+
import type { LayoutAxis } from '@markii/stdlib';
|
|
3
|
+
import type { DirectiveAttributes } from './registry.js';
|
|
4
|
+
/**
|
|
5
|
+
* The terminal counterpart of `@markii/html`'s `layout.ts`: the same closed
|
|
6
|
+
* `width`/`align` vocabulary from `@markii/stdlib`, but resolved to COLUMN
|
|
7
|
+
* ARITHMETIC instead of a CSS class, since there is no stylesheet here. The
|
|
8
|
+
* two reserved keys are still always stripped off a directive's attributes
|
|
9
|
+
* before a component sees them, whether or not their value turns out to be
|
|
10
|
+
* valid, exactly like the other two engines.
|
|
11
|
+
*/
|
|
12
|
+
export { LAYOUT_ATTRIBUTE_KEYS };
|
|
13
|
+
export type WidthPreset = (typeof WIDTH_PRESETS)[number];
|
|
14
|
+
export type AlignPreset = (typeof ALIGN_PRESETS)[number];
|
|
15
|
+
/** The two layout attributes' resolved preset names, present only for a preset that actually applied. */
|
|
16
|
+
export interface ResolvedLayoutPresets {
|
|
17
|
+
width?: WidthPreset;
|
|
18
|
+
align?: AlignPreset;
|
|
19
|
+
}
|
|
20
|
+
export interface ResolvedLayoutAttributes {
|
|
21
|
+
/** `attributes` with every reserved layout key (present, valid or not) removed. */
|
|
22
|
+
attributes: DirectiveAttributes;
|
|
23
|
+
/** The resolved presets, or `undefined` if neither attribute produced one. */
|
|
24
|
+
resolved?: ResolvedLayoutPresets;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Splits `width`/`align` off `attributes`, returning the remaining
|
|
28
|
+
* attributes untouched plus the presets those two attributes resolved to,
|
|
29
|
+
* if any. Same rules as `@markii/html`'s `resolveLayoutAttributes`: both
|
|
30
|
+
* keys are stripped whenever present regardless of validity; an invalid or
|
|
31
|
+
* hostile value never produces a preset; `ownedAxis` (a layout wrapper's own
|
|
32
|
+
* axis) strips its attribute but produces no preset for it, since the
|
|
33
|
+
* directive's NAME already decided that axis. Never throws.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveLayoutAttributes(attributes: DirectiveAttributes, ownedAxis?: LayoutAxis): ResolvedLayoutAttributes;
|
|
36
|
+
/** The minimum column count `narrow` ever shrinks to, regardless of how small `width` is. */
|
|
37
|
+
export declare const NARROW_MINIMUM = 20;
|
|
38
|
+
/**
|
|
39
|
+
* Turns `layout`'s resolved presets into an actual re-wrapped, aligned
|
|
40
|
+
* block, given `width` columns of budget. Mapping (all documented here,
|
|
41
|
+
* the one place it is decided): `narrow` halves `width` (rounded, never
|
|
42
|
+
* below `NARROW_MINIMUM`); `wide` and `full` both use the entire `width`
|
|
43
|
+
* (a terminal has no notion of "wider than the column" to distinguish
|
|
44
|
+
* them); `fit` shrinks to `block`'s own widest existing line, never wider
|
|
45
|
+
* than `width`. `align` then places the (possibly narrowed) block within
|
|
46
|
+
* the full `width` using `./box.js`'s `pad`, which is where `align`'s
|
|
47
|
+
* `left`/`center`/`right` meaning comes from — this function invents none
|
|
48
|
+
* of its own.
|
|
49
|
+
*/
|
|
50
|
+
export declare function applyLayout(block: string, layout: ResolvedLayoutPresets | undefined, width: number): string;
|
|
51
|
+
/**
|
|
52
|
+
* The target width a SELF-DRAWING box component (`card`, `callout`, `table`,
|
|
53
|
+
* `chart`; see `registry.ts`'s `AnsiRegistryEntry.selfLayout`) should draw
|
|
54
|
+
* its own frame at, given the resolved `width` preset. Mirrors the width
|
|
55
|
+
* half of `applyLayout`'s mapping exactly (`narrow` halves `width`, floored
|
|
56
|
+
* at `NARROW_MINIMUM`; `wide`/`full`/absent use the whole budget; `fit`
|
|
57
|
+
* shrinks to the box's own natural content width, capped at `width`), so a
|
|
58
|
+
* component that draws at this width up front needs no further narrowing:
|
|
59
|
+
* `applyLayout`'s `wrap` step becomes a no-op on an already-correctly-sized
|
|
60
|
+
* line. `naturalWidth` is the box's own widest line at its full content size
|
|
61
|
+
* (only consulted for `fit`); a caller that draws lazily may pass a function
|
|
62
|
+
* to defer that measurement until it is known to matter.
|
|
63
|
+
*/
|
|
64
|
+
export declare function selfLayoutWidth(layout: ResolvedLayoutPresets | undefined, width: number, naturalWidth: number | (() => number)): number;
|
|
65
|
+
/**
|
|
66
|
+
* Pads a SELF-DRAWING box's already-drawn frame (every line already
|
|
67
|
+
* `boxWidth` columns wide) within the full `width` per the resolved `align`
|
|
68
|
+
* preset — the align half of `applyLayout`, factored out so a `selfLayout`
|
|
69
|
+
* component can align its own frame without going through the generic
|
|
70
|
+
* narrow/wrap path that would otherwise re-wrap (and corrupt) its
|
|
71
|
+
* box-drawing characters. A no-op when `layout.align` is absent.
|
|
72
|
+
*/
|
|
73
|
+
export declare function selfLayoutAlign(block: string, layout: ResolvedLayoutPresets | undefined, width: number): string;
|