@markii/html 0.16.0 → 0.17.0-next.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/LICENSE +21 -0
- package/dist/failure-presentation.d.ts +21 -0
- package/dist/failure-presentation.js +28 -0
- package/dist/render.d.ts +9 -1
- package/dist/render.js +49 -17
- package/package.json +4 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sadigaxund
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -34,3 +34,24 @@ export declare const NOTICE_ATTRIBUTE = "data-mk-notice";
|
|
|
34
34
|
export declare function invalidAttributeValueTitle(directive: string, attribute: string, value: string): string;
|
|
35
35
|
/** The `title` tooltip for `NOTICE_ATTRIBUTE` on a `figure` with a refused `src`. Ported verbatim from `@markii/react`. */
|
|
36
36
|
export declare function unsafeImageSrcTitle(directive: string): string;
|
|
37
|
+
/** The diagnostic text for a registered block component written as an inline directive, which renders as the inline fallback instead. */
|
|
38
|
+
export declare function formMismatchTitle(directive: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* The words of a fallback label for a directive that names a component of
|
|
41
|
+
* an installed pack with no view in this engine. Followed by the directive
|
|
42
|
+
* name in a `<code>` element. A static HTML file is what this engine makes,
|
|
43
|
+
* so that is what the label calls the missing view.
|
|
44
|
+
*/
|
|
45
|
+
export declare const NO_ENGINE_VIEW_LABEL = "no HTML view for";
|
|
46
|
+
/**
|
|
47
|
+
* The diagnostic text for the same case (also the fallback box's tooltip):
|
|
48
|
+
* the pack, and the engines the component does ship a view for.
|
|
49
|
+
*/
|
|
50
|
+
export declare function noEngineViewTitle(directive: string, pack: string, engines: readonly string[]): string;
|
|
51
|
+
/**
|
|
52
|
+
* The visible separator between an inline fallback's label and its content,
|
|
53
|
+
* emitted as real text (`mk-unknown__separator`) so a copy or a plain-text
|
|
54
|
+
* export reads the same as `@markii/ansi`'s output. An inline fallback with
|
|
55
|
+
* no content shows the bare label and no separator.
|
|
56
|
+
*/
|
|
57
|
+
export declare const INLINE_FALLBACK_SEPARATOR = ": ";
|
|
@@ -82,3 +82,31 @@ export function invalidAttributeValueTitle(directive, attribute, value) {
|
|
|
82
82
|
export function unsafeImageSrcTitle(directive) {
|
|
83
83
|
return `${directive}: image source was refused as unsafe and was not shown`;
|
|
84
84
|
}
|
|
85
|
+
/** The diagnostic text for a registered block component written as an inline directive, which renders as the inline fallback instead. */
|
|
86
|
+
export function formMismatchTitle(directive) {
|
|
87
|
+
return `${directive}: block component was written inline and was not rendered`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The words of a fallback label for a directive that names a component of
|
|
91
|
+
* an installed pack with no view in this engine. Followed by the directive
|
|
92
|
+
* name in a `<code>` element. A static HTML file is what this engine makes,
|
|
93
|
+
* so that is what the label calls the missing view.
|
|
94
|
+
*/
|
|
95
|
+
export const NO_ENGINE_VIEW_LABEL = 'no HTML view for';
|
|
96
|
+
/**
|
|
97
|
+
* The diagnostic text for the same case (also the fallback box's tooltip):
|
|
98
|
+
* the pack, and the engines the component does ship a view for.
|
|
99
|
+
*/
|
|
100
|
+
export function noEngineViewTitle(directive, pack, engines) {
|
|
101
|
+
const has = engines.length > 0
|
|
102
|
+
? `it has views for: ${engines.join(', ')}`
|
|
103
|
+
: 'it has no views for any engine';
|
|
104
|
+
return `The ${pack} pack has no HTML view for this component; ${has}`;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The visible separator between an inline fallback's label and its content,
|
|
108
|
+
* emitted as real text (`mk-unknown__separator`) so a copy or a plain-text
|
|
109
|
+
* export reads the same as `@markii/ansi`'s output. An inline fallback with
|
|
110
|
+
* no content shows the bare label and no separator.
|
|
111
|
+
*/
|
|
112
|
+
export const INLINE_FALLBACK_SEPARATOR = ': ';
|
package/dist/render.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { parse } from '@markii/core';
|
|
|
2
2
|
import type { MarkNode } from '@markii/core';
|
|
3
3
|
import type { ValueStore, VaultStore } from '@markii/runtime';
|
|
4
4
|
import type { HtmlRegistry } from './registry.js';
|
|
5
|
-
import type { OnDiagnostic } from '@markii/stdlib';
|
|
5
|
+
import type { OnDiagnostic, UnavailableComponent } from '@markii/stdlib';
|
|
6
6
|
import type { ResolveImageSrc } from './image-resolve.js';
|
|
7
7
|
import type { ResolveHref } from './href-resolve.js';
|
|
8
8
|
/**
|
|
@@ -68,6 +68,14 @@ export interface RenderMarkOptions {
|
|
|
68
68
|
readonly resolveImageSrc?: ResolveImageSrc;
|
|
69
69
|
readonly resolveHref?: ResolveHref;
|
|
70
70
|
readonly onDiagnostic?: OnDiagnostic;
|
|
71
|
+
/**
|
|
72
|
+
* Components of installed packs that this render cannot draw, as plain
|
|
73
|
+
* data from the host. A directive named here renders the usual fallback
|
|
74
|
+
* box labeled "no HTML view for" instead of "unknown component", and
|
|
75
|
+
* `onDiagnostic` gets a `'no-engine-view'` event. Omitted, the render is
|
|
76
|
+
* unchanged.
|
|
77
|
+
*/
|
|
78
|
+
readonly unavailableComponents?: readonly UnavailableComponent[];
|
|
71
79
|
/**
|
|
72
80
|
* The note's own last-run values, the same thing the third positional
|
|
73
81
|
* parameter takes. Offered here as well so a caller that already builds
|
package/dist/render.js
CHANGED
|
@@ -4,8 +4,8 @@ import { readRegistryComponent, registryLayoutAxis, resolveDirectiveAlias, } fro
|
|
|
4
4
|
import { resolveLayoutAttributes } from './layout.js';
|
|
5
5
|
import { escapeHtml } from './escape.js';
|
|
6
6
|
import { resolveScopedPath } from './resolve.js';
|
|
7
|
-
import { failureKindClass, failureTitle, EMPTY_INLINE_MARKER_CLASS, emptyInlineTitle, invalidAttributeValueTitle, } from './failure-presentation.js';
|
|
8
|
-
import { formatValue, getContract, reportDiagnostic, INTERACTIVE_ATTRIBUTE, } from '@markii/stdlib';
|
|
7
|
+
import { failureKindClass, failureTitle, EMPTY_INLINE_MARKER_CLASS, emptyInlineTitle, formMismatchTitle, INLINE_FALLBACK_SEPARATOR, invalidAttributeValueTitle, NO_ENGINE_VIEW_LABEL, noEngineViewTitle, } from './failure-presentation.js';
|
|
8
|
+
import { createUnavailableComponentLookup, formatValue, getContract, reportDiagnostic, INTERACTIVE_ATTRIBUTE, } from '@markii/stdlib';
|
|
9
9
|
import { resolveImageAttribute } from './image-resolve.js';
|
|
10
10
|
import { resolveHrefAttribute } from './href-resolve.js';
|
|
11
11
|
function isMarkRoot(node) {
|
|
@@ -213,6 +213,8 @@ function isRegisteredInline(entry) {
|
|
|
213
213
|
/** The fallback label line, worded for the reason and the form the directive was written in. Returns HTML. */
|
|
214
214
|
function fallbackLabel(name, inline, reason) {
|
|
215
215
|
const code = `<code>${escapeHtml(name)}</code>`;
|
|
216
|
+
if (reason === 'no-engine-view')
|
|
217
|
+
return `${NO_ENGINE_VIEW_LABEL} ${code}`;
|
|
216
218
|
if (reason === 'form-mismatch') {
|
|
217
219
|
return inline
|
|
218
220
|
? `block component ${code} written inline`
|
|
@@ -220,24 +222,39 @@ function fallbackLabel(name, inline, reason) {
|
|
|
220
222
|
}
|
|
221
223
|
return `unknown component ${code}`;
|
|
222
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* The separator an inline fallback's label ends with when the fallback has
|
|
227
|
+
* content to show after it, matching `@markii/ansi`'s `label: content`.
|
|
228
|
+
* Nested inside the label span so it takes the label's styling.
|
|
229
|
+
*/
|
|
230
|
+
function inlineSeparator(childrenHtml) {
|
|
231
|
+
return childrenHtml.trim() === ''
|
|
232
|
+
? ''
|
|
233
|
+
: `<span class="mk-unknown__separator">${INLINE_FALLBACK_SEPARATOR}</span>`;
|
|
234
|
+
}
|
|
223
235
|
/**
|
|
224
236
|
* The unknown-directive fallback, matching `@markii/react`'s `UnknownDirective`
|
|
225
237
|
* markup and class names byte-for-byte so one stylesheet covers both renderers.
|
|
226
238
|
* An inline directive is built from `<span>`s (it lives inside a paragraph); a
|
|
227
239
|
* block directive gets the dashed `<div>` box with its inner content preserved.
|
|
228
240
|
*/
|
|
229
|
-
function unknownDirective(name, inline, childrenHtml, reason) {
|
|
230
|
-
const reasonClass = reason === 'form-mismatch'
|
|
241
|
+
function unknownDirective(name, inline, childrenHtml, reason, title) {
|
|
242
|
+
const reasonClass = reason === 'form-mismatch'
|
|
243
|
+
? ' mk-unknown--mismatch'
|
|
244
|
+
: reason === 'no-engine-view'
|
|
245
|
+
? ' mk-unknown--no-view'
|
|
246
|
+
: '';
|
|
247
|
+
const titleAttr = title === undefined ? '' : ` title="${escapeHtml(title)}"`;
|
|
231
248
|
const label = fallbackLabel(name, inline, reason);
|
|
232
249
|
if (inline) {
|
|
233
|
-
return (`<span class="mk-unknown mk-unknown--inline${reasonClass}">` +
|
|
234
|
-
`<span class="mk-unknown__label">${label}</span>` +
|
|
250
|
+
return (`<span class="mk-unknown mk-unknown--inline${reasonClass}"${titleAttr}>` +
|
|
251
|
+
`<span class="mk-unknown__label">${label}${inlineSeparator(childrenHtml)}</span>` +
|
|
235
252
|
`${childrenHtml}</span>`);
|
|
236
253
|
}
|
|
237
254
|
const content = childrenHtml
|
|
238
255
|
? `<div class="mk-unknown__content">${childrenHtml}</div>`
|
|
239
256
|
: '';
|
|
240
|
-
return (`<div class="mk-unknown mk-unknown--block${reasonClass}">` +
|
|
257
|
+
return (`<div class="mk-unknown mk-unknown--block${reasonClass}"${titleAttr}>` +
|
|
241
258
|
`<p class="mk-unknown__label">${label}</p>${content}</div>`);
|
|
242
259
|
}
|
|
243
260
|
/**
|
|
@@ -251,7 +268,7 @@ function componentError(name, inline, childrenHtml) {
|
|
|
251
268
|
const code = `<code>${escapeHtml(name)}</code>`;
|
|
252
269
|
if (inline) {
|
|
253
270
|
return (`<span class="mk-unknown mk-unknown--inline mk-unknown--error">` +
|
|
254
|
-
`<span class="mk-unknown__label">component ${code} failed to render</span>` +
|
|
271
|
+
`<span class="mk-unknown__label">component ${code} failed to render${inlineSeparator(childrenHtml)}</span>` +
|
|
255
272
|
`${childrenHtml}</span>`);
|
|
256
273
|
}
|
|
257
274
|
const content = childrenHtml
|
|
@@ -365,7 +382,7 @@ function invalidEnumAttribute(name, attributes) {
|
|
|
365
382
|
return undefined;
|
|
366
383
|
}
|
|
367
384
|
/** Resolves one directive (registry component, `:value[...]`, or the fallback) given its layout-stripped attributes. Never throws. */
|
|
368
|
-
function renderDirectiveContent(name, kind, attributes, childrenHtml, plainLabel, registry, ctx, scope, layoutClassName) {
|
|
385
|
+
function renderDirectiveContent(name, kind, attributes, childrenHtml, plainLabel, registry, ctx, scope, layoutClassName, unavailable) {
|
|
369
386
|
if (name === VALUE_DIRECTIVE_NAME) {
|
|
370
387
|
return ctx.valueMarker(plainLabel, attributes.format ?? undefined, attributes.decimals ?? undefined);
|
|
371
388
|
}
|
|
@@ -373,9 +390,24 @@ function renderDirectiveContent(name, kind, attributes, childrenHtml, plainLabel
|
|
|
373
390
|
const entry = Object.hasOwn(registry, name) ? registry[name] : undefined;
|
|
374
391
|
const component = readRegistryComponent(entry);
|
|
375
392
|
if (!entry || component == null) {
|
|
393
|
+
const missingView = unavailable?.(name);
|
|
394
|
+
if (missingView !== undefined) {
|
|
395
|
+
const title = noEngineViewTitle(name, missingView.pack, missingView.engines);
|
|
396
|
+
reportDiagnostic(ctx.onDiagnostic, {
|
|
397
|
+
kind: 'no-engine-view',
|
|
398
|
+
directive: name,
|
|
399
|
+
message: title,
|
|
400
|
+
});
|
|
401
|
+
return unknownDirective(name, inline, childrenHtml, 'no-engine-view', title);
|
|
402
|
+
}
|
|
376
403
|
return unknownDirective(name || '(unnamed)', inline, childrenHtml, 'unregistered');
|
|
377
404
|
}
|
|
378
405
|
if (isFormMismatch(entry, kind)) {
|
|
406
|
+
reportDiagnostic(ctx.onDiagnostic, {
|
|
407
|
+
kind: 'form-mismatch',
|
|
408
|
+
directive: name,
|
|
409
|
+
message: formMismatchTitle(name),
|
|
410
|
+
});
|
|
379
411
|
return unknownDirective(name || '(unnamed)', inline, childrenHtml, 'form-mismatch');
|
|
380
412
|
}
|
|
381
413
|
const binding = resolveDataAttribute(attributes, scope);
|
|
@@ -417,7 +449,7 @@ function renderDirectiveContent(name, kind, attributes, childrenHtml, plainLabel
|
|
|
417
449
|
return rendered;
|
|
418
450
|
}
|
|
419
451
|
/** Turns one `<mk-directive>` element (children already transformed) into its HTML string, including the layout wrapper for block directives. */
|
|
420
|
-
function renderDirective(element, registry, ctx, scope) {
|
|
452
|
+
function renderDirective(element, registry, ctx, scope, unavailable) {
|
|
421
453
|
const written = stringProperty(element, 'data-mk-name') ?? '';
|
|
422
454
|
const kind = stringProperty(element, 'data-mk-kind');
|
|
423
455
|
const rawAttributes = parseAttributes(stringProperty(element, 'data-mk-attrs'));
|
|
@@ -436,7 +468,7 @@ function renderDirective(element, registry, ctx, scope) {
|
|
|
436
468
|
const ownedAxis = registryLayoutAxis(registry, name);
|
|
437
469
|
const { attributes, className } = resolveLayoutAttributes(aliased, ownedAxis);
|
|
438
470
|
const isLayoutScope = ownedAxis !== undefined && isBlock;
|
|
439
|
-
const content = renderDirectiveContent(name, kind, attributes, childrenHtml, plainLabel, registry, ctx, scope, isLayoutScope ? className : undefined);
|
|
471
|
+
const content = renderDirectiveContent(name, kind, attributes, childrenHtml, plainLabel, registry, ctx, scope, isLayoutScope ? className : undefined, unavailable);
|
|
440
472
|
return isBlock && className && !isLayoutScope
|
|
441
473
|
? `<div class="${escapeHtml(className)}">${content}</div>`
|
|
442
474
|
: content;
|
|
@@ -475,13 +507,13 @@ function applyHrefResolver(node, resolveHref) {
|
|
|
475
507
|
node.properties.href = resolveHrefAttribute(href, resolveHref);
|
|
476
508
|
}
|
|
477
509
|
}
|
|
478
|
-
function makeTransform(registry, ctx, scope, resolveHref) {
|
|
510
|
+
function makeTransform(registry, ctx, scope, resolveHref, unavailable) {
|
|
479
511
|
function transform(node) {
|
|
480
512
|
if (node.type !== 'element')
|
|
481
513
|
return node;
|
|
482
514
|
node.children = node.children.map((child) => transform(child));
|
|
483
515
|
if (node.tagName === DIRECTIVE_TAG)
|
|
484
|
-
return raw(renderDirective(node, registry, ctx, scope));
|
|
516
|
+
return raw(renderDirective(node, registry, ctx, scope, unavailable));
|
|
485
517
|
if (node.tagName === 'pre') {
|
|
486
518
|
const marker = renderScriptMarker(node);
|
|
487
519
|
if (marker !== undefined)
|
|
@@ -504,15 +536,15 @@ function renderFailureFallback(error) {
|
|
|
504
536
|
`<p class="mk-unknown__label">failed to render document</p>` +
|
|
505
537
|
`<pre class="mk-unknown__content">${escapeHtml(message)}</pre></div>`);
|
|
506
538
|
}
|
|
507
|
-
function renderRoot(root, registry, scope, resolveImageSrc, resolveHref, onDiagnostic) {
|
|
539
|
+
function renderRoot(root, registry, scope, resolveImageSrc, resolveHref, onDiagnostic, unavailable) {
|
|
508
540
|
const ctx = createBaseContext(scope, resolveImageSrc, onDiagnostic);
|
|
509
|
-
const transform = makeTransform(registry, ctx, scope, resolveHref);
|
|
541
|
+
const transform = makeTransform(registry, ctx, scope, resolveHref, unavailable);
|
|
510
542
|
root.children = root.children.map(transform);
|
|
511
543
|
return serialize(root.children);
|
|
512
544
|
}
|
|
513
545
|
export function renderMarkToHtml(text, registry, store, vault, options) {
|
|
514
546
|
try {
|
|
515
|
-
return renderRoot(toHast(text), registry, { store: options?.store ?? store, vault: options?.vault ?? vault }, options?.resolveImageSrc, options?.resolveHref, options?.onDiagnostic);
|
|
547
|
+
return renderRoot(toHast(text), registry, { store: options?.store ?? store, vault: options?.vault ?? vault }, options?.resolveImageSrc, options?.resolveHref, options?.onDiagnostic, createUnavailableComponentLookup(options?.unavailableComponents));
|
|
516
548
|
}
|
|
517
549
|
catch (error) {
|
|
518
550
|
return renderFailureFallback(error);
|
|
@@ -533,7 +565,7 @@ export function renderMarkToHtml(text, registry, store, vault, options) {
|
|
|
533
565
|
*/
|
|
534
566
|
export function renderMarkNodeToHtml(node, registry, store, vault, options) {
|
|
535
567
|
try {
|
|
536
|
-
return renderRoot(nodeOrRootToHast(node), registry, { store: options?.store ?? store, vault: options?.vault ?? vault }, options?.resolveImageSrc, options?.resolveHref, options?.onDiagnostic);
|
|
568
|
+
return renderRoot(nodeOrRootToHast(node), registry, { store: options?.store ?? store, vault: options?.vault ?? vault }, options?.resolveImageSrc, options?.resolveHref, options?.onDiagnostic, createUnavailableComponentLookup(options?.unavailableComponents));
|
|
537
569
|
}
|
|
538
570
|
catch (error) {
|
|
539
571
|
return renderFailureFallback(error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markii/html",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0-next.0",
|
|
4
4
|
"description": "A framework-free static HTML renderer for Markii (.mk.md): a registry-driven hast-to-HTML string engine. Zero React; for stopped-changing documents (publish, CI, email, archive).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"hast-util-to-html": "^9.0.0",
|
|
57
|
-
"@markii/core": "^0.
|
|
58
|
-
"@markii/runtime": "^0.
|
|
59
|
-
"@markii/stdlib": "^0.
|
|
57
|
+
"@markii/core": "^0.17.0-next.0",
|
|
58
|
+
"@markii/runtime": "^0.17.0-next.0",
|
|
59
|
+
"@markii/stdlib": "^0.17.0-next.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/hast": "^3.0.4"
|