@markii/html 0.6.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.
- package/dist/components/badge.d.ts +9 -0
- package/dist/components/badge.js +24 -0
- package/dist/components/callout.d.ts +10 -0
- package/dist/components/callout.js +29 -0
- package/dist/components/card.d.ts +9 -0
- package/dist/components/card.js +15 -0
- package/dist/components/cell.d.ts +9 -0
- package/dist/components/cell.js +10 -0
- package/dist/components/chart.d.ts +15 -0
- package/dist/components/chart.js +158 -0
- package/dist/components/details.d.ts +10 -0
- package/dist/components/details.js +16 -0
- package/dist/components/figure.d.ts +18 -0
- package/dist/components/figure.js +28 -0
- package/dist/components/index.d.ts +27 -0
- package/dist/components/index.js +91 -0
- package/dist/components/kbd.d.ts +7 -0
- package/dist/components/kbd.js +8 -0
- package/dist/components/layout-wrapper.d.ts +23 -0
- package/dist/components/layout-wrapper.js +45 -0
- package/dist/components/progress.d.ts +13 -0
- package/dist/components/progress.js +79 -0
- package/dist/components/rating.d.ts +9 -0
- package/dist/components/rating.js +33 -0
- package/dist/components/row.d.ts +9 -0
- package/dist/components/row.js +19 -0
- package/dist/components/stat.d.ts +18 -0
- package/dist/components/stat.js +81 -0
- package/dist/components/tab.d.ts +18 -0
- package/dist/components/tab.js +19 -0
- package/dist/components/tabs.d.ts +30 -0
- package/dist/components/tabs.js +33 -0
- package/dist/doc-css.generated.d.ts +2 -0
- package/dist/doc-css.generated.js +5 -0
- package/dist/document.d.ts +42 -0
- package/dist/document.js +44 -0
- package/dist/escape.d.ts +7 -0
- package/dist/escape.js +23 -0
- package/dist/failure-presentation.d.ts +14 -0
- package/dist/failure-presentation.js +56 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +13 -0
- package/dist/layout.d.ts +31 -0
- package/dist/layout.js +75 -0
- package/dist/registry.d.ts +139 -0
- package/dist/registry.js +118 -0
- package/dist/render.d.ts +34 -0
- package/dist/render.js +417 -0
- package/dist/resolve.d.ts +59 -0
- package/dist/resolve.js +154 -0
- package/dist/test/html-context.d.ts +9 -0
- package/dist/test/html-context.js +21 -0
- package/dist/value-format.d.ts +12 -0
- package/dist/value-format.js +41 -0
- package/package.json +64 -0
package/dist/layout.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { DirectiveAttributes } from './registry.js';
|
|
2
|
+
/**
|
|
3
|
+
* The closed set of layout-preset attributes (docs/format.md): a small,
|
|
4
|
+
* non-freeform-CSS vocabulary any block directive can carry regardless of
|
|
5
|
+
* which component renders it. These two keys are reserved: always intercepted
|
|
6
|
+
* before a component sees its `attributes`, whether or not their value turns
|
|
7
|
+
* out to be valid. This mirrors `@markii/react`'s `layout.ts` exactly, so the
|
|
8
|
+
* two renderers strip and classify the same keys the same way.
|
|
9
|
+
*/
|
|
10
|
+
export declare const LAYOUT_ATTRIBUTE_KEYS: readonly ["width", "align"];
|
|
11
|
+
export interface ResolvedLayoutAttributes {
|
|
12
|
+
/** `attributes` with every reserved layout key (present, valid or not) removed. */
|
|
13
|
+
attributes: DirectiveAttributes;
|
|
14
|
+
/** Space-joined layout classes, or `undefined` if none applied: never an empty string. */
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Splits docs/format.md's closed layout-attribute set (`width`, `align`) off
|
|
19
|
+
* `attributes`, returning the remaining attributes untouched plus the combined
|
|
20
|
+
* class string those two attributes resolve to, if any.
|
|
21
|
+
*
|
|
22
|
+
* Both keys are stripped whenever the key is present on the input, regardless
|
|
23
|
+
* of whether its value is valid; they are reserved layout attributes, so
|
|
24
|
+
* interception wins over a same-named attribute a component might otherwise
|
|
25
|
+
* read itself. Presence is checked with `Object.hasOwn`, so a directive
|
|
26
|
+
* attribute literally named `constructor` or `__proto__` is never mistaken for
|
|
27
|
+
* a real key via the prototype chain. An invalid or hostile value never
|
|
28
|
+
* produces a class: it is dropped silently, exactly like an absent attribute.
|
|
29
|
+
* Never throws.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveLayoutAttributes(attributes: DirectiveAttributes): ResolvedLayoutAttributes;
|
package/dist/layout.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The closed set of layout-preset attributes (docs/format.md): a small,
|
|
3
|
+
* non-freeform-CSS vocabulary any block directive can carry regardless of
|
|
4
|
+
* which component renders it. These two keys are reserved: always intercepted
|
|
5
|
+
* before a component sees its `attributes`, whether or not their value turns
|
|
6
|
+
* out to be valid. This mirrors `@markii/react`'s `layout.ts` exactly, so the
|
|
7
|
+
* two renderers strip and classify the same keys the same way.
|
|
8
|
+
*/
|
|
9
|
+
export const LAYOUT_ATTRIBUTE_KEYS = ['width', 'align'];
|
|
10
|
+
/** `width=normal` is the explicit default: it maps to no class, same as an absent `width`. */
|
|
11
|
+
const NORMAL_WIDTH = 'normal';
|
|
12
|
+
/**
|
|
13
|
+
* `width` value -> class. Null-prototype so a hostile value like `__proto__`
|
|
14
|
+
* or `constructor` cannot resolve through the prototype chain to an inherited
|
|
15
|
+
* `Object.prototype` member; it simply misses the lookup, same as any other
|
|
16
|
+
* unrecognized value.
|
|
17
|
+
*/
|
|
18
|
+
const WIDTH_CLASSES = Object.assign(Object.create(null), {
|
|
19
|
+
narrow: 'mk-width-narrow',
|
|
20
|
+
wide: 'mk-width-wide',
|
|
21
|
+
full: 'mk-width-full',
|
|
22
|
+
});
|
|
23
|
+
/** `align` value -> class. Same null-prototype defense as `WIDTH_CLASSES`. */
|
|
24
|
+
const ALIGN_CLASSES = Object.assign(Object.create(null), {
|
|
25
|
+
left: 'mk-align-left',
|
|
26
|
+
center: 'mk-align-center',
|
|
27
|
+
right: 'mk-align-right',
|
|
28
|
+
});
|
|
29
|
+
function widthClassFor(value) {
|
|
30
|
+
if (value === null || value === undefined || value === '')
|
|
31
|
+
return undefined;
|
|
32
|
+
if (value === NORMAL_WIDTH)
|
|
33
|
+
return undefined;
|
|
34
|
+
return WIDTH_CLASSES[value];
|
|
35
|
+
}
|
|
36
|
+
function alignClassFor(value) {
|
|
37
|
+
if (value === null || value === undefined || value === '')
|
|
38
|
+
return undefined;
|
|
39
|
+
return ALIGN_CLASSES[value];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Splits docs/format.md's closed layout-attribute set (`width`, `align`) off
|
|
43
|
+
* `attributes`, returning the remaining attributes untouched plus the combined
|
|
44
|
+
* class string those two attributes resolve to, if any.
|
|
45
|
+
*
|
|
46
|
+
* Both keys are stripped whenever the key is present on the input, regardless
|
|
47
|
+
* of whether its value is valid; they are reserved layout attributes, so
|
|
48
|
+
* interception wins over a same-named attribute a component might otherwise
|
|
49
|
+
* read itself. Presence is checked with `Object.hasOwn`, so a directive
|
|
50
|
+
* attribute literally named `constructor` or `__proto__` is never mistaken for
|
|
51
|
+
* a real key via the prototype chain. An invalid or hostile value never
|
|
52
|
+
* produces a class: it is dropped silently, exactly like an absent attribute.
|
|
53
|
+
* Never throws.
|
|
54
|
+
*/
|
|
55
|
+
export function resolveLayoutAttributes(attributes) {
|
|
56
|
+
let rest = attributes;
|
|
57
|
+
const classes = [];
|
|
58
|
+
if (Object.hasOwn(rest, 'width')) {
|
|
59
|
+
const { width, ...remainder } = rest;
|
|
60
|
+
rest = remainder;
|
|
61
|
+
const widthClass = widthClassFor(width);
|
|
62
|
+
if (widthClass)
|
|
63
|
+
classes.push(widthClass);
|
|
64
|
+
}
|
|
65
|
+
if (Object.hasOwn(rest, 'align')) {
|
|
66
|
+
const { align, ...remainder } = rest;
|
|
67
|
+
rest = remainder;
|
|
68
|
+
const alignClass = alignClassFor(align);
|
|
69
|
+
if (alignClass)
|
|
70
|
+
classes.push(alignClass);
|
|
71
|
+
}
|
|
72
|
+
return classes.length > 0
|
|
73
|
+
? { attributes: rest, className: classes.join(' ') }
|
|
74
|
+
: { attributes: rest };
|
|
75
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The registry contract for the HTML engine: the string-emitting twin of
|
|
3
|
+
* `@markii/react`'s `registry.ts`. Everything here is framework-free by
|
|
4
|
+
* construction (no React types), because a component is a plain function
|
|
5
|
+
* from attributes and already-rendered children to an HTML string. The
|
|
6
|
+
* alias, merge, and hostile-configuration rules are deliberately identical to
|
|
7
|
+
* the React renderer's, so a note resolves the same way in both.
|
|
8
|
+
*/
|
|
9
|
+
import type { FailureKind, ValueStatus } from '@markii/runtime';
|
|
10
|
+
/**
|
|
11
|
+
* Attributes parsed off a directive, e.g. `{type=warning title="Careful"}`. A
|
|
12
|
+
* bare attribute (present but valueless, e.g. `{collapsed}`) arrives as
|
|
13
|
+
* `null`. A key that was never written is simply absent.
|
|
14
|
+
*/
|
|
15
|
+
export type DirectiveAttributes = Record<string, string | null | undefined>;
|
|
16
|
+
/**
|
|
17
|
+
* A `data=`/`:value[...]` name resolved against the render's value store
|
|
18
|
+
* (and, for an `@`-prefixed name, its vault) — the string engine's read-only
|
|
19
|
+
* view of `./resolve.js`'s `StorePathResolution`. Never carries a `Proxy` or
|
|
20
|
+
* any other live handle: `value` is whatever the store returned, but
|
|
21
|
+
* `status`/`error`/`failureKind` are already validated primitives.
|
|
22
|
+
*/
|
|
23
|
+
export interface ValueResolution {
|
|
24
|
+
value: unknown;
|
|
25
|
+
status: ValueStatus;
|
|
26
|
+
error?: string;
|
|
27
|
+
failureKind?: FailureKind;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The render context handed to every component. `esc` is the engine's single
|
|
31
|
+
* HTML-escaping primitive (see `./escape`), so a component never hand-rolls
|
|
32
|
+
* escaping.
|
|
33
|
+
*
|
|
34
|
+
* `resolve` looks up a `data=`-style name (dotted paths, `@`-prefixed vault
|
|
35
|
+
* names) against the store/vault the current render was called with; it
|
|
36
|
+
* degrades to `{ value: undefined, status: 'missing' }` when there is no
|
|
37
|
+
* store/vault, or the name doesn't resolve — it never throws. `valueMarker`
|
|
38
|
+
* is the empty/stale-state presentation for a resolved name, matching
|
|
39
|
+
* `@markii/react`'s `ValueDirective` exactly (the missing-value `{name}`
|
|
40
|
+
* span, the stale underline, the failure-kind tooltip); it is what powers
|
|
41
|
+
* the `:value[...]` built-in and is exposed here so a data-bound component
|
|
42
|
+
* can render the identical marker for a name it resolves itself.
|
|
43
|
+
*
|
|
44
|
+
* `data`/`dataStatus`/`dataError`/`dataFailureKind` mirror `@markii/react`'s
|
|
45
|
+
* `MarkComponentProps` fields, just carried on `ctx` instead of a fourth
|
|
46
|
+
* function parameter (the `HtmlComponent` signature is `(attributes,
|
|
47
|
+
* childrenHtml, ctx)`, with no room for a fifth argument). They are present
|
|
48
|
+
* ONLY when the directive actually had a `data=` attribute — `dataStatus`
|
|
49
|
+
* is always one of the four `ValueStatus` values in that case, even when the
|
|
50
|
+
* name didn't resolve (`'missing'`); all four are `undefined` together when
|
|
51
|
+
* there was no `data=` attribute at all, exactly like the `'data' in
|
|
52
|
+
* binding` distinction `@markii/react`'s `renderDirectiveContent` makes.
|
|
53
|
+
*/
|
|
54
|
+
export interface HtmlRenderContext {
|
|
55
|
+
/** HTML-escapes a string for safe insertion into text or a quoted attribute value. */
|
|
56
|
+
esc(value: string): string;
|
|
57
|
+
/** Resolves a `data=`/`:value[...]` name against the current render's store/vault. Never throws. */
|
|
58
|
+
resolve(name: string): ValueResolution;
|
|
59
|
+
/** The quiet missing/stale/failure-tinted marker for `name`, matching `@markii/react`'s `ValueDirective` markup exactly. Never throws. */
|
|
60
|
+
valueMarker(name: string): string;
|
|
61
|
+
data?: unknown;
|
|
62
|
+
dataStatus?: ValueStatus;
|
|
63
|
+
dataError?: string;
|
|
64
|
+
dataFailureKind?: FailureKind;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* One registry component: receives the directive's raw string attributes
|
|
68
|
+
* (bare attributes as `null`), its inner markdown already rendered to an HTML
|
|
69
|
+
* string, and the render context, and returns the HTML string to emit.
|
|
70
|
+
* Attribute parsing, validation, and defaulting are the component's own job,
|
|
71
|
+
* exactly as in the React contract.
|
|
72
|
+
*/
|
|
73
|
+
export type HtmlComponent = (attributes: DirectiveAttributes, childrenHtml: string, ctx: HtmlRenderContext) => string;
|
|
74
|
+
/**
|
|
75
|
+
* One registry entry: the component plus whether it is meant to be used
|
|
76
|
+
* inline (text directive) vs as a block (leaf/container directive). Only an
|
|
77
|
+
* explicit `inline: false` drives the form/kind mismatch rule; `undefined`
|
|
78
|
+
* says nothing about kind.
|
|
79
|
+
*/
|
|
80
|
+
export interface HtmlRegistryEntry {
|
|
81
|
+
component: HtmlComponent;
|
|
82
|
+
inline?: boolean;
|
|
83
|
+
}
|
|
84
|
+
/** One alias: a second name for an existing component, optionally carrying preset attributes. */
|
|
85
|
+
export interface RegistryAlias {
|
|
86
|
+
name: string;
|
|
87
|
+
attributes?: DirectiveAttributes;
|
|
88
|
+
}
|
|
89
|
+
/** Alias name -> what it stands for. */
|
|
90
|
+
export type RegistryAliases = Record<string, RegistryAlias>;
|
|
91
|
+
/**
|
|
92
|
+
* The symbol an alias table hangs off a registry under: a symbol rather than
|
|
93
|
+
* a string key so it can never collide with a directive name, never show up
|
|
94
|
+
* in `Object.keys`, and still ride across `Object.assign` (so `mergeHtml`
|
|
95
|
+
* `Registries` carries it). Mirrors `@markii/react`'s `REGISTRY_ALIASES`.
|
|
96
|
+
*/
|
|
97
|
+
export declare const REGISTRY_ALIASES: unique symbol;
|
|
98
|
+
/** Directive name -> component registration, plus an optional alias table under `REGISTRY_ALIASES`. */
|
|
99
|
+
export interface HtmlRegistry {
|
|
100
|
+
[name: string]: HtmlRegistryEntry;
|
|
101
|
+
[REGISTRY_ALIASES]?: RegistryAliases;
|
|
102
|
+
}
|
|
103
|
+
/** Reads a registry's alias table, or `undefined` when it has none. Returned as-is; treat as read-only. */
|
|
104
|
+
export declare function registryAliases(registry: HtmlRegistry): RegistryAliases | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* Creates a registry from a plain object of entries plus an optional alias
|
|
107
|
+
* table. The returned map has a `null` prototype so a directive named
|
|
108
|
+
* `constructor`, `toString`, `hasOwnProperty`, etc. cannot resolve to an
|
|
109
|
+
* inherited member; only entries actually registered are ever found.
|
|
110
|
+
*/
|
|
111
|
+
export declare function createHtmlRegistry(entries?: HtmlRegistry, aliases?: RegistryAliases): HtmlRegistry;
|
|
112
|
+
/**
|
|
113
|
+
* Merges any number of registries, later ones taking precedence, into a
|
|
114
|
+
* null-prototype map. Alias tables merge per name, not wholesale, so a later
|
|
115
|
+
* registry that defines any alias does not silently drop earlier ones.
|
|
116
|
+
*/
|
|
117
|
+
export declare function mergeHtmlRegistries(...registries: HtmlRegistry[]): HtmlRegistry;
|
|
118
|
+
/**
|
|
119
|
+
* Reads `entry.component`, or `undefined` if `entry` is nullish or the read
|
|
120
|
+
* itself throws. A hand-built registry can define `component` as a throwing
|
|
121
|
+
* getter (or a trapping `Proxy`); a throwing read degrades to "no component
|
|
122
|
+
* here", identical to a genuinely absent one, never an exception escaping the
|
|
123
|
+
* renderer (docs/spec.md requirement 4).
|
|
124
|
+
*/
|
|
125
|
+
export declare function readRegistryComponent(entry: HtmlRegistryEntry | undefined): HtmlComponent | undefined;
|
|
126
|
+
/** A directive name and attributes after alias resolution. */
|
|
127
|
+
export interface ResolvedDirective {
|
|
128
|
+
name: string;
|
|
129
|
+
attributes: DirectiveAttributes;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Resolves one directive name through the registry's alias table. Four rules,
|
|
133
|
+
* in order: a real component wins over any alias; an unaliased name passes
|
|
134
|
+
* through; an alias is followed exactly one hop (a chain lands on the
|
|
135
|
+
* unknown-directive fallback rather than chaining); author attributes win over
|
|
136
|
+
* the alias's presets. Never throws; a malformed alias degrades to the
|
|
137
|
+
* ordinary unknown-directive path. Identical to `@markii/react`'s rule.
|
|
138
|
+
*/
|
|
139
|
+
export declare function resolveDirectiveAlias(registry: HtmlRegistry, name: string, attributes: DirectiveAttributes): ResolvedDirective;
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The registry contract for the HTML engine: the string-emitting twin of
|
|
3
|
+
* `@markii/react`'s `registry.ts`. Everything here is framework-free by
|
|
4
|
+
* construction (no React types), because a component is a plain function
|
|
5
|
+
* from attributes and already-rendered children to an HTML string. The
|
|
6
|
+
* alias, merge, and hostile-configuration rules are deliberately identical to
|
|
7
|
+
* the React renderer's, so a note resolves the same way in both.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The symbol an alias table hangs off a registry under: a symbol rather than
|
|
11
|
+
* a string key so it can never collide with a directive name, never show up
|
|
12
|
+
* in `Object.keys`, and still ride across `Object.assign` (so `mergeHtml`
|
|
13
|
+
* `Registries` carries it). Mirrors `@markii/react`'s `REGISTRY_ALIASES`.
|
|
14
|
+
*/
|
|
15
|
+
export const REGISTRY_ALIASES = Symbol('markii.html.registry.aliases');
|
|
16
|
+
/** Reads a registry's alias table, or `undefined` when it has none. Returned as-is; treat as read-only. */
|
|
17
|
+
export function registryAliases(registry) {
|
|
18
|
+
return registry[REGISTRY_ALIASES];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Combines alias tables left-to-right into one null-prototype map, later
|
|
22
|
+
* tables winning per name. Returns `undefined` when no input carried aliases,
|
|
23
|
+
* so an alias-free merge stays alias-free.
|
|
24
|
+
*/
|
|
25
|
+
function mergeAliasTables(tables) {
|
|
26
|
+
const present = tables.filter((table) => table !== undefined);
|
|
27
|
+
if (present.length === 0)
|
|
28
|
+
return undefined;
|
|
29
|
+
const merged = Object.create(null);
|
|
30
|
+
for (const table of present) {
|
|
31
|
+
for (const name of Object.keys(table))
|
|
32
|
+
merged[name] = table[name];
|
|
33
|
+
}
|
|
34
|
+
return merged;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Creates a registry from a plain object of entries plus an optional alias
|
|
38
|
+
* table. The returned map has a `null` prototype so a directive named
|
|
39
|
+
* `constructor`, `toString`, `hasOwnProperty`, etc. cannot resolve to an
|
|
40
|
+
* inherited member; only entries actually registered are ever found.
|
|
41
|
+
*/
|
|
42
|
+
export function createHtmlRegistry(entries = {}, aliases) {
|
|
43
|
+
const registry = Object.assign(Object.create(null), entries);
|
|
44
|
+
const merged = mergeAliasTables([registryAliases(entries), aliases]);
|
|
45
|
+
if (merged)
|
|
46
|
+
registry[REGISTRY_ALIASES] = merged;
|
|
47
|
+
return registry;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Merges any number of registries, later ones taking precedence, into a
|
|
51
|
+
* null-prototype map. Alias tables merge per name, not wholesale, so a later
|
|
52
|
+
* registry that defines any alias does not silently drop earlier ones.
|
|
53
|
+
*/
|
|
54
|
+
export function mergeHtmlRegistries(...registries) {
|
|
55
|
+
const merged = Object.assign(Object.create(null), ...registries);
|
|
56
|
+
const aliases = mergeAliasTables(registries.map(registryAliases));
|
|
57
|
+
if (aliases)
|
|
58
|
+
merged[REGISTRY_ALIASES] = aliases;
|
|
59
|
+
else
|
|
60
|
+
delete merged[REGISTRY_ALIASES];
|
|
61
|
+
return merged;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Reads `entry.component`, or `undefined` if `entry` is nullish or the read
|
|
65
|
+
* itself throws. A hand-built registry can define `component` as a throwing
|
|
66
|
+
* getter (or a trapping `Proxy`); a throwing read degrades to "no component
|
|
67
|
+
* here", identical to a genuinely absent one, never an exception escaping the
|
|
68
|
+
* renderer (docs/spec.md requirement 4).
|
|
69
|
+
*/
|
|
70
|
+
export function readRegistryComponent(entry) {
|
|
71
|
+
if (!entry)
|
|
72
|
+
return undefined;
|
|
73
|
+
try {
|
|
74
|
+
return entry.component ?? undefined;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Whether `registry` has a real, usable component under `name` (own property, non-nullish, non-throwing). */
|
|
81
|
+
function hasComponent(registry, name) {
|
|
82
|
+
return (Object.hasOwn(registry, name) &&
|
|
83
|
+
readRegistryComponent(registry[name]) != null);
|
|
84
|
+
}
|
|
85
|
+
/** Merges an alias's preset attributes under the author's own, author winning on collision ("closest to the text wins"). */
|
|
86
|
+
function mergeAliasAttributes(preset, author) {
|
|
87
|
+
if (!preset)
|
|
88
|
+
return author;
|
|
89
|
+
const result = {};
|
|
90
|
+
for (const [key, value] of Object.entries(preset))
|
|
91
|
+
result[key] = value;
|
|
92
|
+
for (const [key, value] of Object.entries(author))
|
|
93
|
+
result[key] = value;
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Resolves one directive name through the registry's alias table. Four rules,
|
|
98
|
+
* in order: a real component wins over any alias; an unaliased name passes
|
|
99
|
+
* through; an alias is followed exactly one hop (a chain lands on the
|
|
100
|
+
* unknown-directive fallback rather than chaining); author attributes win over
|
|
101
|
+
* the alias's presets. Never throws; a malformed alias degrades to the
|
|
102
|
+
* ordinary unknown-directive path. Identical to `@markii/react`'s rule.
|
|
103
|
+
*/
|
|
104
|
+
export function resolveDirectiveAlias(registry, name, attributes) {
|
|
105
|
+
if (hasComponent(registry, name))
|
|
106
|
+
return { name, attributes };
|
|
107
|
+
const aliases = registryAliases(registry);
|
|
108
|
+
if (!aliases || !Object.hasOwn(aliases, name))
|
|
109
|
+
return { name, attributes };
|
|
110
|
+
const alias = aliases[name];
|
|
111
|
+
if (typeof alias?.name !== 'string' || alias.name === '') {
|
|
112
|
+
return { name, attributes };
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
name: alias.name,
|
|
116
|
+
attributes: mergeAliasAttributes(alias.attributes, attributes),
|
|
117
|
+
};
|
|
118
|
+
}
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { MarkNode } from '@markii/core';
|
|
2
|
+
import type { ValueStore, VaultStore } from '@markii/runtime';
|
|
3
|
+
import type { HtmlRegistry } from './registry.js';
|
|
4
|
+
/**
|
|
5
|
+
* Renders Markii text to a static HTML string using `registry` to resolve
|
|
6
|
+
* directive names. Pipeline: `@markii/core`'s `toHast` (parse -> tag directive
|
|
7
|
+
* nodes -> remark-rehype -> sanitize URLs) -> a hast->HTML walk that swaps
|
|
8
|
+
* directive elements for registry components (or the unknown-directive
|
|
9
|
+
* fallback) and folds script fences into markers. Pure and never-throwing:
|
|
10
|
+
* parsing is tolerant, unknown names always render a fallback, and any
|
|
11
|
+
* unexpected internal error degrades to the "failed to render document" box.
|
|
12
|
+
*
|
|
13
|
+
* `store` is the note's value store (`@markii/runtime`, §8's pure read path)
|
|
14
|
+
* — optional, matching how a missing/absent value degrades gracefully: with
|
|
15
|
+
* no store, `:value[name]` renders its missing-value marker and every
|
|
16
|
+
* `data=name` attribute resolves to `dataStatus: 'missing'`, but the
|
|
17
|
+
* document still renders completely.
|
|
18
|
+
*
|
|
19
|
+
* `vault` is the optional app-scoped read seam (`@markii/runtime`'s
|
|
20
|
+
* `VaultStore`) that an `@`-prefixed name (`data=@gh.stars`,
|
|
21
|
+
* `:value[@gh.stars]`) resolves against instead of `store` — "bare name =
|
|
22
|
+
* mine, `@name` = the vault's". With no `vault` supplied, every `@name`
|
|
23
|
+
* degrades to `'missing'` the same way an absent `store` degrades a bare
|
|
24
|
+
* name.
|
|
25
|
+
*/
|
|
26
|
+
export declare function renderMarkToHtml(text: string, registry: HtmlRegistry, store?: ValueStore, vault?: VaultStore): string;
|
|
27
|
+
/**
|
|
28
|
+
* The block-level twin of `renderMarkToHtml`: renders one already-parsed mdast
|
|
29
|
+
* node (`@markii/core`'s `MarkNode`) to HTML instead of a whole document's
|
|
30
|
+
* text, via `nodeToHast`. Same registry resolution, same fallbacks, same
|
|
31
|
+
* purity and never-throw guarantees, and the same optional `store`/`vault`
|
|
32
|
+
* value-binding arguments.
|
|
33
|
+
*/
|
|
34
|
+
export declare function renderMarkNodeToHtml(node: MarkNode, registry: HtmlRegistry, store?: ValueStore, vault?: VaultStore): string;
|