@cube-drone/marquee-turbolink 0.6.2 → 0.7.1
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 +46 -6
- package/dist/plugins.js +3 -2
- package/package.json +2 -2
- package/src/opengraph.ts +48 -6
- package/src/plugins.ts +3 -2
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
|
+
* ('), hex (' - what React/Helmet-rendered heads like CBC's emit),
|
|
37
|
+
* and the common named set. One pass, so `&#x27;` decodes to the literal
|
|
38
|
+
* text `'` rather than an apostrophe.
|
|
39
|
+
*/
|
|
6
40
|
function decodeEntities(s) {
|
|
7
|
-
return s
|
|
8
|
-
.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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/dist/plugins.js
CHANGED
|
@@ -7,8 +7,9 @@ function extension(target) {
|
|
|
7
7
|
const path = target.split(/[?#]/, 1)[0];
|
|
8
8
|
return path.includes(".") ? path.slice(path.lastIndexOf(".") + 1).toLowerCase() : "";
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
const
|
|
10
|
+
// Mirror of rust/markup/src/turbolink.rs; `apng` and `opus` joined 2026-09-03.
|
|
11
|
+
const IMAGE_EXT = new Set(["png", "jpg", "jpeg", "gif", "webp", "avif", "apng", "svg"]);
|
|
12
|
+
const AUDIO_EXT = new Set(["mp3", "ogg", "opus", "wav", "flac", "m4a"]);
|
|
12
13
|
const VIDEO_EXT = new Set(["mp4", "webm"]);
|
|
13
14
|
// Shared style chunks: plugins that emit the same classes reference the
|
|
14
15
|
// same constant, and turbolinkStyles() dedupes by content.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cube-drone/marquee-turbolink",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
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.
|
|
37
|
+
"@cube-drone/marquee-parser": "^0.7.1"
|
|
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
|
+
* ('), hex (' - what React/Helmet-rendered heads like CBC's emit),
|
|
44
|
+
* and the common named set. One pass, so `&#x27;` decodes to the literal
|
|
45
|
+
* text `'` rather than an apostrophe.
|
|
46
|
+
*/
|
|
11
47
|
function decodeEntities(s: string): string {
|
|
12
|
-
return s
|
|
13
|
-
.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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. */
|
package/src/plugins.ts
CHANGED
|
@@ -11,8 +11,9 @@ function extension(target: string): string {
|
|
|
11
11
|
return path.includes(".") ? path.slice(path.lastIndexOf(".") + 1).toLowerCase() : "";
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
const
|
|
14
|
+
// Mirror of rust/markup/src/turbolink.rs; `apng` and `opus` joined 2026-09-03.
|
|
15
|
+
const IMAGE_EXT = new Set(["png", "jpg", "jpeg", "gif", "webp", "avif", "apng", "svg"]);
|
|
16
|
+
const AUDIO_EXT = new Set(["mp3", "ogg", "opus", "wav", "flac", "m4a"]);
|
|
16
17
|
const VIDEO_EXT = new Set(["mp4", "webm"]);
|
|
17
18
|
|
|
18
19
|
// Shared style chunks: plugins that emit the same classes reference the
|