@cube-drone/marquee-html-renderer 0.1.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 +373 -0
- package/README.md +25 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +15 -0
- package/dist/profile.d.ts +43 -0
- package/dist/profile.js +37 -0
- package/dist/render.d.ts +16 -0
- package/dist/render.js +566 -0
- package/package.json +47 -0
- package/src/index.ts +20 -0
- package/src/profile.ts +81 -0
- package/src/render.ts +628 -0
package/src/profile.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Embedder profiles (SPEC.md, "Embedder profiles"): the language defines
|
|
2
|
+
// meaning, the embedder defines policy. Every render-time capability
|
|
3
|
+
// decision routes through here; the renderer itself never fetches, never
|
|
4
|
+
// guesses trust, never widens a scheme allowlist.
|
|
5
|
+
|
|
6
|
+
import type { Attrs } from "@cube-drone/marquee-parser";
|
|
7
|
+
|
|
8
|
+
export interface MediaResolution {
|
|
9
|
+
kind: "image" | "audio" | "video";
|
|
10
|
+
url: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type TurbolinkLevel = "full" | "title" | "bare";
|
|
14
|
+
|
|
15
|
+
/** What an emoji slug becomes: replacement text, or a custom-emoji image. */
|
|
16
|
+
export type EmojiResolution = string | { image: string; alt?: string };
|
|
17
|
+
|
|
18
|
+
export interface Profile {
|
|
19
|
+
/** May this target become a hyperlink? Disallowed links render their
|
|
20
|
+
* children without an anchor (content survives, capability doesn't). */
|
|
21
|
+
linkAllowed(target: string): boolean;
|
|
22
|
+
/** Resolve an embed target to a media kind, or null for the inert
|
|
23
|
+
* fallback. The kind is resolved at render time (SPEC.md, "Media"). */
|
|
24
|
+
media(target: string): MediaResolution | null;
|
|
25
|
+
/** Resolve an emoji slug: replacement text, or an image (the spec's
|
|
26
|
+
* custom-emoji map is named indirection over an inline image), or null →
|
|
27
|
+
* literal `:slug:`. The image URL is embedder-supplied configuration,
|
|
28
|
+
* trusted like `directive` - author bytes only ever supply the slug. */
|
|
29
|
+
emoji(slug: string): EmojiResolution | null;
|
|
30
|
+
/** Rendered turbolink content for a target, or null → the plain-link
|
|
31
|
+
* floor. Embedders compose this from plugins (see marquee-turbolink: an
|
|
32
|
+
* image in a box, play controls, a YouTube embed, a host's native
|
|
33
|
+
* displays). Trusted embedder code, like `directive`: author bytes only
|
|
34
|
+
* enter as the target string. MUST be sync and fetchless - gathering
|
|
35
|
+
* happens in a plugin's resolve() phase, never mid-render. */
|
|
36
|
+
turbolink(target: string, level: TurbolinkLevel): string | null;
|
|
37
|
+
/** The default enrichment level for a target (spec: per-scheme embedder
|
|
38
|
+
* policy); an explicit `level=` on `:::turbolink` wins over this. */
|
|
39
|
+
turbolinkLevel(target: string): TurbolinkLevel;
|
|
40
|
+
/** Embedder directive vocabulary (widgets, includes, computed). Return
|
|
41
|
+
* rendered HTML, or null to fall through to the built-in handling. */
|
|
42
|
+
directive(name: string, attrs: Attrs, renderedChildren: string): string | null;
|
|
43
|
+
/** Embedder span vocabulary. Same contract as `directive`. */
|
|
44
|
+
span(name: string, attrs: Attrs, renderedChildren: string): string | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const MEDIA_KINDS: Record<string, MediaResolution["kind"]> = {
|
|
48
|
+
png: "image", jpg: "image", jpeg: "image", gif: "image", webp: "image",
|
|
49
|
+
avif: "image", svg: "image",
|
|
50
|
+
mp3: "audio", ogg: "audio", wav: "audio", flac: "audio", m4a: "audio",
|
|
51
|
+
mp4: "video", webm: "video",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function scheme(target: string): string | null {
|
|
55
|
+
const m = /^([A-Za-z][A-Za-z0-9+.-]*):/.exec(target);
|
|
56
|
+
return m ? m[1]!.toLowerCase() : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The bare-web default: https links, extension-sniffed media, no widgets,
|
|
60
|
+
* no emoji table, no enrichment. Everything an embedder doesn't decide
|
|
61
|
+
* degrades inert. */
|
|
62
|
+
export const bareWebProfile: Profile = {
|
|
63
|
+
linkAllowed(target) {
|
|
64
|
+
const s = scheme(target);
|
|
65
|
+
return s === null || s === "http" || s === "https" || s === "mailto";
|
|
66
|
+
},
|
|
67
|
+
media(target) {
|
|
68
|
+
if (!this.linkAllowed(target)) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const path = target.split(/[?#]/, 1)[0]!;
|
|
72
|
+
const ext = path.includes(".") ? path.slice(path.lastIndexOf(".") + 1).toLowerCase() : "";
|
|
73
|
+
const kind = MEDIA_KINDS[ext];
|
|
74
|
+
return kind === undefined ? null : { kind, url: target };
|
|
75
|
+
},
|
|
76
|
+
emoji: () => null,
|
|
77
|
+
turbolink: () => null,
|
|
78
|
+
turbolinkLevel: () => "full",
|
|
79
|
+
directive: () => null,
|
|
80
|
+
span: () => null,
|
|
81
|
+
};
|