@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/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
+ };