@cube-drone/marquee-turbolink 0.6.1 → 0.7.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/opengraph.js CHANGED
@@ -3,13 +3,53 @@
3
3
  // LAST so the shaped plugins (YouTube, media) win their own kinds.
4
4
  import { renderCard } from "./card.js";
5
5
  const UA = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36";
6
+ /**
7
+ * The named entities a meta tag is realistically written with. Not the full
8
+ * HTML5 table (2,000+ names); an unknown name is left as-is rather than
9
+ * guessed. Mirror of the Rust crate's NAMED_ENTITIES.
10
+ */
11
+ const NAMED_ENTITIES = {
12
+ lt: "<",
13
+ gt: ">",
14
+ quot: '"',
15
+ apos: "'",
16
+ amp: "&",
17
+ nbsp: "\u00a0",
18
+ ndash: "\u2013",
19
+ mdash: "\u2014",
20
+ lsquo: "\u2018",
21
+ rsquo: "\u2019",
22
+ ldquo: "\u201c",
23
+ rdquo: "\u201d",
24
+ hellip: "\u2026",
25
+ copy: "\u00a9",
26
+ reg: "\u00ae",
27
+ trade: "\u2122",
28
+ laquo: "\u00ab",
29
+ raquo: "\u00bb",
30
+ bull: "\u2022",
31
+ middot: "\u00b7",
32
+ };
33
+ const ENTITY_RE = /&(#[xX][0-9a-fA-F]{1,6}|#[0-9]{1,7}|[a-zA-Z][a-zA-Z0-9]{1,31});/g;
34
+ /**
35
+ * Decode the character references a meta content= can carry: decimal
36
+ * (&#39;), hex (&#x27; - what React/Helmet-rendered heads like CBC's emit),
37
+ * and the common named set. One pass, so `&amp;#x27;` decodes to the literal
38
+ * text `&#x27;` rather than an apostrophe.
39
+ */
6
40
  function decodeEntities(s) {
7
- return s
8
- .replaceAll("&lt;", "<")
9
- .replaceAll("&gt;", ">")
10
- .replaceAll("&quot;", '"')
11
- .replaceAll("&#39;", "'")
12
- .replaceAll("&amp;", "&");
41
+ return s.replace(ENTITY_RE, (whole, body) => {
42
+ if (body.startsWith("#")) {
43
+ const isHex = body[1] === "x" || body[1] === "X";
44
+ const n = Number.parseInt(body.slice(isHex ? 2 : 1), isHex ? 16 : 10);
45
+ // NUL, a lone surrogate, or beyond U+10FFFF has no character to become.
46
+ if (n === 0 || n > 0x10ffff || (n >= 0xd800 && n <= 0xdfff)) {
47
+ return whole;
48
+ }
49
+ return String.fromCodePoint(n);
50
+ }
51
+ return NAMED_ENTITIES[body] ?? whole;
52
+ });
13
53
  }
14
54
  /** Pure and separately testable: html text in, summary out. */
15
55
  export function parseOpenGraph(html) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cube-drone/marquee-turbolink",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Pluggable turbolink rendering for Marquee: link expanders as plugins, plain link as the floor",
5
5
  "license": "MPL-2.0",
6
6
  "repository": {
@@ -34,7 +34,7 @@
34
34
  "prepack": "npm run build"
35
35
  },
36
36
  "dependencies": {
37
- "@cube-drone/marquee-parser": "^0.6.1"
37
+ "@cube-drone/marquee-parser": "^0.7.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^24.0.0",
package/src/opengraph.ts CHANGED
@@ -8,13 +8,55 @@ import type { TurbolinkPlugin } from "./index.ts";
8
8
  const UA =
9
9
  "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36";
10
10
 
11
+ /**
12
+ * The named entities a meta tag is realistically written with. Not the full
13
+ * HTML5 table (2,000+ names); an unknown name is left as-is rather than
14
+ * guessed. Mirror of the Rust crate's NAMED_ENTITIES.
15
+ */
16
+ const NAMED_ENTITIES: Record<string, string> = {
17
+ lt: "<",
18
+ gt: ">",
19
+ quot: '"',
20
+ apos: "'",
21
+ amp: "&",
22
+ nbsp: "\u00a0",
23
+ ndash: "\u2013",
24
+ mdash: "\u2014",
25
+ lsquo: "\u2018",
26
+ rsquo: "\u2019",
27
+ ldquo: "\u201c",
28
+ rdquo: "\u201d",
29
+ hellip: "\u2026",
30
+ copy: "\u00a9",
31
+ reg: "\u00ae",
32
+ trade: "\u2122",
33
+ laquo: "\u00ab",
34
+ raquo: "\u00bb",
35
+ bull: "\u2022",
36
+ middot: "\u00b7",
37
+ };
38
+
39
+ const ENTITY_RE = /&(#[xX][0-9a-fA-F]{1,6}|#[0-9]{1,7}|[a-zA-Z][a-zA-Z0-9]{1,31});/g;
40
+
41
+ /**
42
+ * Decode the character references a meta content= can carry: decimal
43
+ * (&#39;), hex (&#x27; - what React/Helmet-rendered heads like CBC's emit),
44
+ * and the common named set. One pass, so `&amp;#x27;` decodes to the literal
45
+ * text `&#x27;` rather than an apostrophe.
46
+ */
11
47
  function decodeEntities(s: string): string {
12
- return s
13
- .replaceAll("&lt;", "<")
14
- .replaceAll("&gt;", ">")
15
- .replaceAll("&quot;", '"')
16
- .replaceAll("&#39;", "'")
17
- .replaceAll("&amp;", "&");
48
+ return s.replace(ENTITY_RE, (whole: string, body: string): string => {
49
+ if (body.startsWith("#")) {
50
+ const isHex = body[1] === "x" || body[1] === "X";
51
+ const n = Number.parseInt(body.slice(isHex ? 2 : 1), isHex ? 16 : 10);
52
+ // NUL, a lone surrogate, or beyond U+10FFFF has no character to become.
53
+ if (n === 0 || n > 0x10ffff || (n >= 0xd800 && n <= 0xdfff)) {
54
+ return whole;
55
+ }
56
+ return String.fromCodePoint(n);
57
+ }
58
+ return NAMED_ENTITIES[body] ?? whole;
59
+ });
18
60
  }
19
61
 
20
62
  /** Pure and separately testable: html text in, summary out. */