@cube-drone/marquee-html-renderer 0.7.1 → 0.8.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/README.md CHANGED
@@ -13,7 +13,11 @@ const html = render(parse(source)); // a <div class="mq-doc"> fragment
13
13
 
14
14
  Embedder policy — which schemes link, how media resolves, what turbolinks become, custom
15
15
  directive/span vocabulary — is injected via the `Profile` interface; `bareWebProfile` is the
16
- conservative default. Pair the output with
16
+ conservative default. The `directive`/`span` hooks (a span is the inline twin of a directive)
17
+ are also called by the React renderer and the CodeMirror live preview, so they carry a
18
+ contract, documented on the interface and in the spec: claim on name and attrs alone (a probe
19
+ with empty children answers the same), stay pure and fetchless, and always place the rendered
20
+ children. Pair the output with
17
21
  [`@cube-drone/marquee-css`](https://www.npmjs.com/package/@cube-drone/marquee-css) (the `mq-*`
18
22
  class contract this renderer targets) and optionally
19
23
  [`@cube-drone/marquee-fonts`](https://www.npmjs.com/package/@cube-drone/marquee-fonts).
package/dist/profile.d.ts CHANGED
@@ -2,6 +2,10 @@ import type { Attrs } from "@cube-drone/marquee-parser";
2
2
  export interface MediaResolution {
3
3
  kind: "image" | "audio" | "video";
4
4
  url: string;
5
+ /** A video that is really a silent animation (a gif that became a video):
6
+ * drawn looping, muted, autoplaying, with no controls. The embedder decides -
7
+ * the language only carries the choice. Ignored for images and audio. */
8
+ loop?: boolean;
5
9
  }
6
10
  export type TurbolinkLevel = "full" | "title" | "bare";
7
11
  /** What an emoji slug becomes: replacement text, or a custom-emoji image. */
@@ -32,9 +36,24 @@ export interface Profile {
32
36
  * policy); an explicit `level=` on `:::turbolink` wins over this. */
33
37
  turbolinkLevel(target: string): TurbolinkLevel;
34
38
  /** Embedder directive vocabulary (widgets, includes, computed). Return
35
- * rendered HTML, or null to fall through to the built-in handling. */
39
+ * rendered HTML, or null to fall through to the built-in handling.
40
+ *
41
+ * The contract (spec: "The vocabulary hooks' contract"), because editors
42
+ * and the React renderer call this too, not just the page render:
43
+ * - Claim on `name` and `attrs` alone: whether this returns null MUST NOT
44
+ * depend on `renderedChildren`. Callers may probe with "" to ask "is
45
+ * this yours?" and render for real afterwards.
46
+ * - Pure, sync, fetchless, cheap: it may run per keystroke, and twice in
47
+ * one render, and must answer the same each time.
48
+ * - Place the children: a claiming answer MUST include `renderedChildren`
49
+ * (already-escaped HTML) or a known substitute for them - the shrug's
50
+ * never-eat-content rule binds embedder vocabulary too.
51
+ * Embedder-first: a claim on a name the language defines wins. */
36
52
  directive(name: string, attrs: Attrs, renderedChildren: string): string | null;
37
- /** Embedder span vocabulary. Same contract as `directive`. */
53
+ /** Embedder span vocabulary - the inline twin of `directive`
54
+ * (`[user id=x]Name[/user]` is to `:::user id=x:::` what `[spoiler]` is
55
+ * to `:::spoiler`). Same contract as `directive`, including the probe:
56
+ * the live editor asks with "" to decide whether to draw the span whole. */
38
57
  span(name: string, attrs: Attrs, renderedChildren: string): string | null;
39
58
  }
40
59
  /** The bare-web default: https links, extension-sniffed media, no widgets,
package/dist/render.js CHANGED
@@ -205,7 +205,9 @@ function embed(target, alt, profile) {
205
205
  case "audio":
206
206
  return `<audio class="mq-embed" controls src="${url}" aria-label="${escapeAttr(alt)}"></audio>`;
207
207
  case "video":
208
- return `<video class="mq-embed" controls src="${url}" aria-label="${escapeAttr(alt)}"></video>`;
208
+ return media.loop === true
209
+ ? `<video class="mq-embed" autoplay loop muted playsinline src="${url}" aria-label="${escapeAttr(alt)}"></video>`
210
+ : `<video class="mq-embed" controls src="${url}" aria-label="${escapeAttr(alt)}"></video>`;
209
211
  }
210
212
  }
211
213
  // The contractual shrug applied to media: degrade to a labeled link, or
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cube-drone/marquee-html-renderer",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "Reference static HTML renderer for the Marquee markup language",
5
5
  "license": "MPL-2.0",
6
6
  "repository": {
@@ -35,12 +35,12 @@
35
35
  "prepack": "npm run build"
36
36
  },
37
37
  "dependencies": {
38
- "@cube-drone/marquee-parser": "^0.7.1"
38
+ "@cube-drone/marquee-parser": "^0.8.0"
39
39
  },
40
40
  "devDependencies": {
41
- "@cube-drone/marquee-css": "^0.7.1",
42
- "@cube-drone/marquee-fonts": "^0.7.1",
43
- "@cube-drone/marquee-turbolink": "^0.7.1",
41
+ "@cube-drone/marquee-css": "^0.8.0",
42
+ "@cube-drone/marquee-fonts": "^0.8.0",
43
+ "@cube-drone/marquee-turbolink": "^0.8.0",
44
44
  "@types/node": "^24.0.0",
45
45
  "typescript": "^5.9.0"
46
46
  }
package/src/profile.ts CHANGED
@@ -8,6 +8,10 @@ import type { Attrs } from "@cube-drone/marquee-parser";
8
8
  export interface MediaResolution {
9
9
  kind: "image" | "audio" | "video";
10
10
  url: string;
11
+ /** A video that is really a silent animation (a gif that became a video):
12
+ * drawn looping, muted, autoplaying, with no controls. The embedder decides -
13
+ * the language only carries the choice. Ignored for images and audio. */
14
+ loop?: boolean;
11
15
  }
12
16
 
13
17
  export type TurbolinkLevel = "full" | "title" | "bare";
@@ -38,9 +42,24 @@ export interface Profile {
38
42
  * policy); an explicit `level=` on `:::turbolink` wins over this. */
39
43
  turbolinkLevel(target: string): TurbolinkLevel;
40
44
  /** Embedder directive vocabulary (widgets, includes, computed). Return
41
- * rendered HTML, or null to fall through to the built-in handling. */
45
+ * rendered HTML, or null to fall through to the built-in handling.
46
+ *
47
+ * The contract (spec: "The vocabulary hooks' contract"), because editors
48
+ * and the React renderer call this too, not just the page render:
49
+ * - Claim on `name` and `attrs` alone: whether this returns null MUST NOT
50
+ * depend on `renderedChildren`. Callers may probe with "" to ask "is
51
+ * this yours?" and render for real afterwards.
52
+ * - Pure, sync, fetchless, cheap: it may run per keystroke, and twice in
53
+ * one render, and must answer the same each time.
54
+ * - Place the children: a claiming answer MUST include `renderedChildren`
55
+ * (already-escaped HTML) or a known substitute for them - the shrug's
56
+ * never-eat-content rule binds embedder vocabulary too.
57
+ * Embedder-first: a claim on a name the language defines wins. */
42
58
  directive(name: string, attrs: Attrs, renderedChildren: string): string | null;
43
- /** Embedder span vocabulary. Same contract as `directive`. */
59
+ /** Embedder span vocabulary - the inline twin of `directive`
60
+ * (`[user id=x]Name[/user]` is to `:::user id=x:::` what `[spoiler]` is
61
+ * to `:::spoiler`). Same contract as `directive`, including the probe:
62
+ * the live editor asks with "" to decide whether to draw the span whole. */
44
63
  span(name: string, attrs: Attrs, renderedChildren: string): string | null;
45
64
  }
46
65
 
package/src/render.ts CHANGED
@@ -232,7 +232,9 @@ function embed(target: string, alt: string, profile: Profile): string {
232
232
  case "audio":
233
233
  return `<audio class="mq-embed" controls src="${url}" aria-label="${escapeAttr(alt)}"></audio>`;
234
234
  case "video":
235
- return `<video class="mq-embed" controls src="${url}" aria-label="${escapeAttr(alt)}"></video>`;
235
+ return media.loop === true
236
+ ? `<video class="mq-embed" autoplay loop muted playsinline src="${url}" aria-label="${escapeAttr(alt)}"></video>`
237
+ : `<video class="mq-embed" controls src="${url}" aria-label="${escapeAttr(alt)}"></video>`;
236
238
  }
237
239
  }
238
240
  // The contractual shrug applied to media: degrade to a labeled link, or