@bendyline/squisq 2.11.16 → 2.11.17
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/{Doc-Bd2kdzuy.d.ts → Doc-CXx1WL8G.d.ts} +27 -2
- package/dist/{ImageEditDoc-C3HOltpW.d.ts → ImageEditDoc-BVLXI-Zd.d.ts} +1 -1
- package/dist/{chunk-NC6LD6D3.js → chunk-54RU7TBZ.js} +34 -7
- package/dist/{chunk-C3NAY5YP.js → chunk-A5QJJZ5S.js} +8 -5
- package/dist/{chunk-HVJ6VFDX.js → chunk-GB7OFYUK.js} +4 -0
- package/dist/{chunk-IFJPPI63.js → chunk-KNC4ZBQC.js} +5 -5
- package/dist/{chunk-WMXQDP2C.js → chunk-L7IIFWWW.js} +109 -14
- package/dist/{chunk-ANH2OZ2M.js → chunk-LJZHC6GN.js} +146 -11
- package/dist/{chunk-IRVXZUVU.js → chunk-MY46W5O5.js} +2 -2
- package/dist/{chunk-GDHXT5Q7.js → chunk-PGWYC3S2.js} +2 -2
- package/dist/{chunk-5W7F46OO.js → chunk-PNXBDMNS.js} +1 -1
- package/dist/{chunk-X7BNDUNI.js → chunk-Q6ELOAOQ.js} +383 -1
- package/dist/{chunk-TMOHAFYE.js → chunk-WSFXV5BT.js} +17 -0
- package/dist/doc/index.d.ts +35 -7
- package/dist/doc/index.js +11 -7
- package/dist/fence/index.d.ts +1 -1
- package/dist/generate/index.d.ts +1 -1
- package/dist/imageEdit/index.d.ts +3 -3
- package/dist/index.d.ts +6 -6
- package/dist/index.js +35 -11
- package/dist/jsonForm/index.d.ts +1 -1
- package/dist/jsonForm/index.js +1 -1
- package/dist/markdown/index.d.ts +170 -4
- package/dist/markdown/index.js +24 -4
- package/dist/{materializePageSection-FRv9whYy.d.ts → materializePageSection-BX1uOAlN.d.ts} +6 -1
- package/dist/mediaEdit/index.d.ts +2 -2
- package/dist/narration/index.d.ts +1 -1
- package/dist/narration/index.js +6 -6
- package/dist/recommend/index.js +2 -2
- package/dist/schemas/index.d.ts +4 -4
- package/dist/schemas/index.js +1 -1
- package/dist/{themeLibrary-BbF3EKWM.d.ts → themeLibrary-Bnq_x-db.d.ts} +1 -1
- package/dist/timing/index.d.ts +1 -1
- package/dist/transform/index.d.ts +2 -2
- package/dist/transform/index.js +6 -6
- package/package.json +1 -1
|
@@ -2,14 +2,14 @@ import {
|
|
|
2
2
|
assertMarkdownDocumentWithinLimits,
|
|
3
3
|
parseMarkdown,
|
|
4
4
|
toMdast
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-PGWYC3S2.js";
|
|
6
6
|
import {
|
|
7
7
|
formatFrontmatterYaml,
|
|
8
8
|
getChildren,
|
|
9
9
|
splitFrontmatterBlock,
|
|
10
10
|
stringifyHtmlNodes,
|
|
11
11
|
walkMarkdownTree
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-Q6ELOAOQ.js";
|
|
13
13
|
|
|
14
14
|
// src/markdown/stringify.ts
|
|
15
15
|
import { unified } from "unified";
|
|
@@ -21,17 +21,41 @@ var defaultProcessor;
|
|
|
21
21
|
var DQ_RUN = `"(?:[^"\\\\]|\\\\.)*"`;
|
|
22
22
|
var SQ_RUN = `'(?:[^'\\\\]|\\\\.)*'`;
|
|
23
23
|
var ESCAPED_TEMPLATE_SPAN_RE = new RegExp(
|
|
24
|
-
`\\{\\\\\\[((?:${DQ_RUN}|${SQ_RUN}|\\\\.|[^\\]\\\\])*)\\]\\}`,
|
|
24
|
+
`\\{\\\\\\[((?:${DQ_RUN}|${SQ_RUN}|\\\\.|[^\\]\\\\"'])*)\\]\\}`,
|
|
25
25
|
"g"
|
|
26
26
|
);
|
|
27
27
|
var HEADING_LINE_RE = /^#{1,6} .*$/gm;
|
|
28
28
|
var TRAILING_ESCAPED_PANDOC_SPAN_RE = new RegExp(
|
|
29
|
-
`\\{(?!\\\\?\\[)((?:${DQ_RUN}|${SQ_RUN}|\\\\.|[^}\\\\])*)\\}(?=\\s*(?:\\{\\[.*)?$)`
|
|
29
|
+
`\\{(?!\\\\?\\[)((?:${DQ_RUN}|${SQ_RUN}|\\\\.|[^}\\\\"'])*)\\}(?=\\s*(?:\\{\\[.*)?$)`
|
|
30
30
|
);
|
|
31
31
|
var UNESCAPE_PUNCT_RE = /\\([\\[\]:#.,+=/?;@%$(){}'"-])/g;
|
|
32
32
|
function unescapeMarkdownPunct(text) {
|
|
33
33
|
return text.replace(UNESCAPE_PUNCT_RE, "$1");
|
|
34
34
|
}
|
|
35
|
+
function literalTextUnsafe(options) {
|
|
36
|
+
return [
|
|
37
|
+
// GFM's writer only guards lowercase protocol endings, while its parser
|
|
38
|
+
// recognizes uppercase ones. Escape the colon before the domain dot can
|
|
39
|
+
// become a literal backslash inside an automatically recognized URL.
|
|
40
|
+
...options?.gfm === false ? [] : [
|
|
41
|
+
{
|
|
42
|
+
character: ":",
|
|
43
|
+
before: "[PS]",
|
|
44
|
+
after: "\\/",
|
|
45
|
+
inConstruct: "phrasing",
|
|
46
|
+
notInConstruct: [
|
|
47
|
+
"autolink",
|
|
48
|
+
"link",
|
|
49
|
+
"image",
|
|
50
|
+
"label"
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
{ character: "\\", after: "[ ]", inConstruct: "phrasing" },
|
|
55
|
+
...options?.math === false ? [] : [{ character: "$", inConstruct: "phrasing" }],
|
|
56
|
+
...options?.directive === false ? [] : [{ character: ":", after: "[A-Za-z]", inConstruct: "phrasing" }]
|
|
57
|
+
];
|
|
58
|
+
}
|
|
35
59
|
var PREFERRED_MASK_SENTINEL = "\0";
|
|
36
60
|
function pickMaskSentinel(text) {
|
|
37
61
|
const present = new Set(text);
|
|
@@ -154,7 +178,8 @@ function stringifyMarkdown(doc, options) {
|
|
|
154
178
|
strong: "*",
|
|
155
179
|
rule: "-",
|
|
156
180
|
fence: "`",
|
|
157
|
-
setext: false
|
|
181
|
+
setext: false,
|
|
182
|
+
unsafe: literalTextUnsafe()
|
|
158
183
|
});
|
|
159
184
|
}
|
|
160
185
|
processor = defaultProcessor;
|
|
@@ -176,7 +201,8 @@ function stringifyMarkdown(doc, options) {
|
|
|
176
201
|
strong: options?.strong ?? "*",
|
|
177
202
|
rule: options?.rule ?? "-",
|
|
178
203
|
fence: options?.fence ?? "`",
|
|
179
|
-
setext: options?.setext ?? false
|
|
204
|
+
setext: options?.setext ?? false,
|
|
205
|
+
unsafe: literalTextUnsafe(options)
|
|
180
206
|
});
|
|
181
207
|
}
|
|
182
208
|
const result = processor.stringify(mdastTree);
|
|
@@ -1204,11 +1230,22 @@ function rewriteMarkdownReferences(source, options = {}) {
|
|
|
1204
1230
|
|
|
1205
1231
|
// src/markdown/docfxMarkdown.ts
|
|
1206
1232
|
function normalizeDocfxMarkdown(source, options = {}) {
|
|
1207
|
-
|
|
1233
|
+
let output = normalizeDocfxMarkdownPass(source, options);
|
|
1234
|
+
if (options.unknownDirectives === "preserve") {
|
|
1235
|
+
for (let pass = 0; pass < 2 && output !== source && /:::/u.test(output); pass++) {
|
|
1236
|
+
source = output;
|
|
1237
|
+
output = normalizeDocfxMarkdownPass(source, options);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
return output;
|
|
1241
|
+
}
|
|
1242
|
+
function normalizeDocfxMarkdownPass(source, options = {}) {
|
|
1243
|
+
const { frontmatter, body: originalBody } = splitFrontmatterBlock(source);
|
|
1244
|
+
const body = flattenDocfxLayout(originalBody);
|
|
1208
1245
|
const protectedSpans = [];
|
|
1209
|
-
for (const comment of body.matchAll(/<!--[\s\S]
|
|
1246
|
+
for (const comment of body.matchAll(/<!--[\s\S]*?(?:-->|$)/g))
|
|
1210
1247
|
protectedSpans.push([comment.index, comment.index + comment[0].length]);
|
|
1211
|
-
walkMarkdownTree(parseMarkdown(body, { frontmatter: false }), (node) => {
|
|
1248
|
+
walkMarkdownTree(parseMarkdown(body, { frontmatter: false, directive: false }), (node) => {
|
|
1212
1249
|
if ((node.type === "code" || node.type === "inlineCode") && node.position?.start.offset !== void 0 && node.position.end.offset !== void 0)
|
|
1213
1250
|
protectedSpans.push([node.position.start.offset, node.position.end.offset]);
|
|
1214
1251
|
});
|
|
@@ -1216,8 +1253,8 @@ function normalizeDocfxMarkdown(source, options = {}) {
|
|
|
1216
1253
|
type: "document",
|
|
1217
1254
|
children: [{ type: "paragraph", children: [{ type: "text", value: text }] }]
|
|
1218
1255
|
}).trimEnd();
|
|
1219
|
-
|
|
1220
|
-
/(^[^\S\n]*(?:>\s*)?):::\s*([\w-]+)([^\n]*?)(?=\r?$)|(^[^\S\n]*>\s*)\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION|div)\b([^\]]*)\]|:::\s*(image)\b([^\n]*?):::/gim,
|
|
1256
|
+
let output = body.replace(
|
|
1257
|
+
/(^[^\S\n]*(?:>\s*)?):::\s*([\w-]+)([^\n]*?)(?=\r?$)|(^[^\S\n]*>\s*)\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION|div)\b([^\]]*)\]|:::\s*(image|no-loc)\b([^\n]*?):::/gim,
|
|
1221
1258
|
(match, prefix, name, raw, quote, admonition, _extra, inlineName, inlineRaw, offset) => {
|
|
1222
1259
|
if (protectedSpans.some(([start, end]) => offset >= start && offset < end)) return match;
|
|
1223
1260
|
if (admonition)
|
|
@@ -1239,6 +1276,16 @@ function normalizeDocfxMarkdown(source, options = {}) {
|
|
|
1239
1276
|
}).trimEnd();
|
|
1240
1277
|
return `${prefix ?? ""}${image}`;
|
|
1241
1278
|
}
|
|
1279
|
+
case "code":
|
|
1280
|
+
if (options.reference && attributes.has("source"))
|
|
1281
|
+
return (prefix ?? "") + options.reference("code", attributes.get("source"), raw?.trim() ?? "Code example");
|
|
1282
|
+
throw new Error("Unsupported DocFX code without a source reference resolver");
|
|
1283
|
+
case "no-loc":
|
|
1284
|
+
return (prefix ?? "") + escape(attributes.get("text") ?? "");
|
|
1285
|
+
case "moniker":
|
|
1286
|
+
return (prefix ?? "") + "**Applies to: " + escape(attributes.get("range") ?? raw?.trim() ?? "") + "**";
|
|
1287
|
+
case "moniker-end":
|
|
1288
|
+
return "";
|
|
1242
1289
|
case "zone":
|
|
1243
1290
|
return attributes.has("pivot") ? `${prefix ?? ""}**Applies to: ${escape(attributes.get("pivot"))}**
|
|
1244
1291
|
` : "";
|
|
@@ -1250,12 +1297,100 @@ function normalizeDocfxMarkdown(source, options = {}) {
|
|
|
1250
1297
|
case "column-end":
|
|
1251
1298
|
return "";
|
|
1252
1299
|
default:
|
|
1300
|
+
if (options.unknownDirectives === "preserve") {
|
|
1301
|
+
const fence = "`".repeat(
|
|
1302
|
+
Math.max(1, ...Array.from(match.matchAll(/`+/g), (m) => m[0].length + 1))
|
|
1303
|
+
);
|
|
1304
|
+
return `${prefix ?? ""}${fence}${match.trim()}${fence}`;
|
|
1305
|
+
}
|
|
1253
1306
|
throw new Error(`Unsupported DocFX directive: ${name}`);
|
|
1254
1307
|
}
|
|
1255
1308
|
}
|
|
1256
1309
|
);
|
|
1310
|
+
if (options.reference) {
|
|
1311
|
+
const spans = [];
|
|
1312
|
+
for (const comment of output.matchAll(/<!--[\s\S]*?(?:-->|$)/g))
|
|
1313
|
+
spans.push([comment.index, comment.index + comment[0].length]);
|
|
1314
|
+
walkMarkdownTree(parseMarkdown(output, { frontmatter: false, directive: false }), (node) => {
|
|
1315
|
+
if ((node.type === "code" || node.type === "inlineCode") && node.position)
|
|
1316
|
+
spans.push([node.position.start.offset, node.position.end.offset]);
|
|
1317
|
+
});
|
|
1318
|
+
output = output.replace(
|
|
1319
|
+
/\[!code-[\w-]+\s*\[([^\]]*)\]\(([^)\n]+)\)\]|<xref:([^>\n]+)>/gi,
|
|
1320
|
+
(match, label, target, uid, offset) => {
|
|
1321
|
+
if (spans.some(([start, end]) => offset >= start && offset < end)) return match;
|
|
1322
|
+
return uid ? options.reference("xref", uid, uid.split("?")[0]) : options.reference("code", target, label || "Code example");
|
|
1323
|
+
}
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
if (options.unknownDirectives === "preserve" && /\[!code-|\[!INCLUDE\b|<xref:/i.test(output)) {
|
|
1327
|
+
const spans = [];
|
|
1328
|
+
for (const comment of output.matchAll(/<!--[\s\S]*?(?:-->|$)/g))
|
|
1329
|
+
spans.push([comment.index, comment.index + comment[0].length]);
|
|
1330
|
+
walkMarkdownTree(parseMarkdown(output, { frontmatter: false, directive: false }), (node) => {
|
|
1331
|
+
if ((node.type === "code" || node.type === "inlineCode") && node.position)
|
|
1332
|
+
spans.push([node.position.start.offset, node.position.end.offset]);
|
|
1333
|
+
});
|
|
1334
|
+
output = output.replace(
|
|
1335
|
+
/\[!code-[^\n]*|\[!INCLUDE[^\n]*|<xref:[^\n]*/gi,
|
|
1336
|
+
(match, offset) => {
|
|
1337
|
+
if (spans.some(([start, end]) => offset >= start && offset < end)) return match;
|
|
1338
|
+
options.onPreservedReference?.(match);
|
|
1339
|
+
const fence = String.fromCharCode(96).repeat(
|
|
1340
|
+
Math.max(1, ...Array.from(match.matchAll(/\x60+/g), (m) => m[0].length + 1))
|
|
1341
|
+
);
|
|
1342
|
+
return fence + " " + match + " " + fence;
|
|
1343
|
+
}
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
if (options.frontmatterAsCode && frontmatter) {
|
|
1347
|
+
const fence = "`".repeat(
|
|
1348
|
+
Math.max(3, ...Array.from(frontmatter.matchAll(/`+/g), (m) => m[0].length + 1))
|
|
1349
|
+
);
|
|
1350
|
+
return output + "\n\n## Original source metadata\n\n" + fence + "text\n" + frontmatter.trimEnd() + "\n" + fence + "\n";
|
|
1351
|
+
}
|
|
1257
1352
|
return (frontmatter ?? "") + output;
|
|
1258
1353
|
}
|
|
1354
|
+
function flattenDocfxLayout(source) {
|
|
1355
|
+
const stack = [];
|
|
1356
|
+
let fence;
|
|
1357
|
+
let comment = false;
|
|
1358
|
+
return source.split("\n").map((line) => {
|
|
1359
|
+
const column = [...stack].reverse().find((entry) => entry.kind === "column");
|
|
1360
|
+
const margin = column ? column.indent + 4 : 0;
|
|
1361
|
+
const indent = /^ */.exec(line)[0].length;
|
|
1362
|
+
const content = line.slice(Math.min(indent, margin));
|
|
1363
|
+
const boundary = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(content);
|
|
1364
|
+
if (fence) {
|
|
1365
|
+
if (boundary && boundary[1][0] === fence.character && boundary[1].length >= fence.length && !boundary[2].trim())
|
|
1366
|
+
fence = void 0;
|
|
1367
|
+
return content;
|
|
1368
|
+
}
|
|
1369
|
+
const commented = comment || line.includes("<!--");
|
|
1370
|
+
const opening = line.lastIndexOf("<!--");
|
|
1371
|
+
const closing = line.lastIndexOf("-->");
|
|
1372
|
+
if (opening > closing) comment = true;
|
|
1373
|
+
else if (closing >= 0) comment = false;
|
|
1374
|
+
if (commented) return content;
|
|
1375
|
+
if (boundary) {
|
|
1376
|
+
fence = { character: boundary[1][0], length: boundary[1].length };
|
|
1377
|
+
return content;
|
|
1378
|
+
}
|
|
1379
|
+
const marker = /^ *:::(row|column)(-end)?(?=\s|:|$)[^\n]*$/i.exec(line);
|
|
1380
|
+
if (!marker || !stack.length && indent > 3) return content;
|
|
1381
|
+
const kind = marker[1].toLowerCase();
|
|
1382
|
+
if (marker[2]) {
|
|
1383
|
+
const index = stack.map((entry) => entry.kind).lastIndexOf(kind);
|
|
1384
|
+
if (index < 0) return content;
|
|
1385
|
+
stack.splice(index);
|
|
1386
|
+
} else {
|
|
1387
|
+
if (stack.length >= 128)
|
|
1388
|
+
throw new Error("DocFX layout exceeds the 128-level nesting safety limit");
|
|
1389
|
+
stack.push({ kind, indent });
|
|
1390
|
+
}
|
|
1391
|
+
return "";
|
|
1392
|
+
}).join("\n");
|
|
1393
|
+
}
|
|
1259
1394
|
|
|
1260
1395
|
export {
|
|
1261
1396
|
stringifyMarkdown,
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
buildNarrationScript,
|
|
8
8
|
expectedSyllablesAt,
|
|
9
9
|
wordPosAtExpectedSyllables
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-PNXBDMNS.js";
|
|
11
11
|
import {
|
|
12
12
|
flattenRenderableBlocks
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-L7IIFWWW.js";
|
|
14
14
|
|
|
15
15
|
// src/narration/types.ts
|
|
16
16
|
var DEFAULT_FEATURE_CONFIG = Object.freeze({
|
|
@@ -6,11 +6,11 @@ import {
|
|
|
6
6
|
quoteAttrValue,
|
|
7
7
|
splitKeyValueToken,
|
|
8
8
|
tokenizeAttrTokens
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-WSFXV5BT.js";
|
|
10
10
|
import {
|
|
11
11
|
parseFrontmatter,
|
|
12
12
|
parseHtmlToNodes
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-Q6ELOAOQ.js";
|
|
14
14
|
import {
|
|
15
15
|
resolveIcon
|
|
16
16
|
} from "./chunk-7N4G32LG.js";
|
|
@@ -685,6 +685,378 @@ function plainTextFromInlineHtml(html) {
|
|
|
685
685
|
return walk(parseHtmlToNodes(html)).replace(/\u00A0/g, " ").replace(/\n{3,}/g, "\n\n").trim();
|
|
686
686
|
}
|
|
687
687
|
|
|
688
|
+
// src/markdown/mediaReference.ts
|
|
689
|
+
var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set(["mp4", "m4v", "mov", "webm", "ogv"]);
|
|
690
|
+
var AUDIO_EXTENSIONS = /* @__PURE__ */ new Set(["mp3", "m4a", "aac", "wav", "ogg", "oga", "opus", "flac"]);
|
|
691
|
+
var CONTAINER_EXTENSIONS = /* @__PURE__ */ new Set(["webm", "mp4"]);
|
|
692
|
+
function mediaKindForUrl(url) {
|
|
693
|
+
const path = url.split(/[?#]/, 1)[0] ?? "";
|
|
694
|
+
const name = path.slice(path.lastIndexOf("/") + 1);
|
|
695
|
+
const dot = name.lastIndexOf(".");
|
|
696
|
+
if (dot <= 0) return null;
|
|
697
|
+
const ext = name.slice(dot + 1).toLowerCase();
|
|
698
|
+
if (AUDIO_EXTENSIONS.has(ext)) return "audio";
|
|
699
|
+
if (CONTAINER_EXTENSIONS.has(ext) && /^(?:\.\/)?audio\//i.test(path)) return "audio";
|
|
700
|
+
if (VIDEO_EXTENSIONS.has(ext)) return "video";
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// src/markdown/videoEmbed.ts
|
|
705
|
+
var VIDEO_EMBED_PROVIDERS = [
|
|
706
|
+
{ id: "youtube", name: "YouTube", example: "https://www.youtube.com/watch?v=dQw4w9WgXcQ" },
|
|
707
|
+
{ id: "vimeo", name: "Vimeo", example: "https://vimeo.com/76979871" },
|
|
708
|
+
{
|
|
709
|
+
id: "loom",
|
|
710
|
+
name: "Loom",
|
|
711
|
+
example: "https://www.loom.com/share/0281766fa2d04bb788eaf19e65135184"
|
|
712
|
+
},
|
|
713
|
+
{ id: "dailymotion", name: "Dailymotion", example: "https://www.dailymotion.com/video/x7tgad0" },
|
|
714
|
+
{ id: "wistia", name: "Wistia", example: "https://home.wistia.com/medias/e4a27b971d" }
|
|
715
|
+
];
|
|
716
|
+
var PROVIDER_NAMES = Object.fromEntries(
|
|
717
|
+
VIDEO_EMBED_PROVIDERS.map((p) => [p.id, p.name])
|
|
718
|
+
);
|
|
719
|
+
var LANDSCAPE = 16 / 9;
|
|
720
|
+
var PORTRAIT = 9 / 16;
|
|
721
|
+
function unescapeMarkdown(value) {
|
|
722
|
+
return value.replace(/\\([!-/:-@[-`{-~])/g, "$1");
|
|
723
|
+
}
|
|
724
|
+
function toUrl(input) {
|
|
725
|
+
let text = unescapeMarkdown(input.trim());
|
|
726
|
+
if (text.startsWith("<") && text.endsWith(">")) text = text.slice(1, -1).trim();
|
|
727
|
+
if (!text || /\s/.test(text)) return null;
|
|
728
|
+
if (text.startsWith("//")) text = `https:${text}`;
|
|
729
|
+
else if (!/^[a-z][a-z0-9+.-]*:/i.test(text)) text = `https://${text}`;
|
|
730
|
+
let url;
|
|
731
|
+
try {
|
|
732
|
+
url = new URL(text);
|
|
733
|
+
} catch {
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return null;
|
|
737
|
+
if (url.username || url.password || url.port) return null;
|
|
738
|
+
return url;
|
|
739
|
+
}
|
|
740
|
+
function baseHost(url) {
|
|
741
|
+
return url.hostname.toLowerCase().replace(/^(?:www|m)\./, "");
|
|
742
|
+
}
|
|
743
|
+
function pathSegments(url) {
|
|
744
|
+
return url.pathname.split("/").filter(Boolean).map((segment) => {
|
|
745
|
+
try {
|
|
746
|
+
return decodeURIComponent(segment);
|
|
747
|
+
} catch {
|
|
748
|
+
return segment;
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
function fragmentParam(url, key) {
|
|
753
|
+
const hash = url.hash.replace(/^#/, "");
|
|
754
|
+
if (!hash) return null;
|
|
755
|
+
return new URLSearchParams(hash).get(key);
|
|
756
|
+
}
|
|
757
|
+
var HMS_RE = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?$/;
|
|
758
|
+
var CLOCK_RE = /^(?:(\d+):)?(\d{1,2}):(\d{2})$/;
|
|
759
|
+
function parseVideoStartTime(raw) {
|
|
760
|
+
if (!raw) return void 0;
|
|
761
|
+
const value = raw.trim().toLowerCase();
|
|
762
|
+
if (!value) return void 0;
|
|
763
|
+
let total = null;
|
|
764
|
+
const hms = HMS_RE.exec(value);
|
|
765
|
+
if (hms && (hms[1] || hms[2] || hms[3])) {
|
|
766
|
+
total = Number(hms[1] ?? 0) * 3600 + Number(hms[2] ?? 0) * 60 + Number(hms[3] ?? 0);
|
|
767
|
+
} else {
|
|
768
|
+
const clock = CLOCK_RE.exec(value);
|
|
769
|
+
const minutes = Number(clock?.[2]);
|
|
770
|
+
const seconds = Number(clock?.[3]);
|
|
771
|
+
if (clock && seconds < 60 && (clock[1] === void 0 || minutes < 60)) {
|
|
772
|
+
total = Number(clock[1] ?? 0) * 3600 + minutes * 60 + seconds;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
if (total === null || !Number.isFinite(total) || total <= 0) return void 0;
|
|
776
|
+
return Math.floor(total);
|
|
777
|
+
}
|
|
778
|
+
function withQuery(base, params) {
|
|
779
|
+
const query = params.filter((entry) => entry[1] !== void 0).map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&");
|
|
780
|
+
return query ? `${base}?${query}` : base;
|
|
781
|
+
}
|
|
782
|
+
function build(provider, fields) {
|
|
783
|
+
const embed = {
|
|
784
|
+
provider,
|
|
785
|
+
providerName: PROVIDER_NAMES[provider],
|
|
786
|
+
id: fields.id,
|
|
787
|
+
embedUrl: fields.embedUrl,
|
|
788
|
+
watchUrl: fields.watchUrl,
|
|
789
|
+
aspectRatio: fields.aspectRatio ?? LANDSCAPE
|
|
790
|
+
};
|
|
791
|
+
if (fields.playlistId) embed.playlistId = fields.playlistId;
|
|
792
|
+
if (fields.startSeconds) embed.startSeconds = fields.startSeconds;
|
|
793
|
+
if (fields.thumbnailUrl) embed.thumbnailUrl = fields.thumbnailUrl;
|
|
794
|
+
return embed;
|
|
795
|
+
}
|
|
796
|
+
var YOUTUBE_HOSTS = /* @__PURE__ */ new Set(["youtube.com", "music.youtube.com", "youtube-nocookie.com"]);
|
|
797
|
+
var YOUTUBE_ID_RE = /^[A-Za-z0-9_-]{11}$/;
|
|
798
|
+
var YOUTUBE_LIST_RE = /^[A-Za-z0-9_-]{2,80}$/;
|
|
799
|
+
var YOUTUBE_ID_PATHS = /* @__PURE__ */ new Set(["embed", "v", "e", "live", "shorts"]);
|
|
800
|
+
var YOUTUBE_EMBED_ORIGIN = "https://www.youtube-nocookie.com";
|
|
801
|
+
function parseYouTube(url) {
|
|
802
|
+
const host = baseHost(url);
|
|
803
|
+
const segments = pathSegments(url);
|
|
804
|
+
const params = url.searchParams;
|
|
805
|
+
let id = null;
|
|
806
|
+
let shorts = false;
|
|
807
|
+
if (host === "youtu.be") {
|
|
808
|
+
id = segments[0] ?? null;
|
|
809
|
+
} else if (YOUTUBE_HOSTS.has(host)) {
|
|
810
|
+
const [head, second] = segments;
|
|
811
|
+
if (head === "watch") id = params.get("v");
|
|
812
|
+
else if (head && YOUTUBE_ID_PATHS.has(head) && second !== "videoseries") {
|
|
813
|
+
id = second ?? null;
|
|
814
|
+
shorts = head === "shorts";
|
|
815
|
+
} else if (head !== "playlist" && !(head === "embed" && second === "videoseries")) {
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
} else {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
if (id !== null && !YOUTUBE_ID_RE.test(id)) return null;
|
|
822
|
+
const list = params.get("list");
|
|
823
|
+
const playlistId = list && YOUTUBE_LIST_RE.test(list) ? list : void 0;
|
|
824
|
+
if (!id && !playlistId) return null;
|
|
825
|
+
const startSeconds = parseVideoStartTime(
|
|
826
|
+
params.get("t") ?? params.get("start") ?? fragmentParam(url, "t")
|
|
827
|
+
);
|
|
828
|
+
const start = startSeconds ? String(startSeconds) : void 0;
|
|
829
|
+
if (!id) {
|
|
830
|
+
return build("youtube", {
|
|
831
|
+
id: playlistId,
|
|
832
|
+
playlistId,
|
|
833
|
+
embedUrl: withQuery(`${YOUTUBE_EMBED_ORIGIN}/embed/videoseries`, [["list", playlistId]]),
|
|
834
|
+
watchUrl: withQuery("https://www.youtube.com/playlist", [["list", playlistId]])
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
return build("youtube", {
|
|
838
|
+
id,
|
|
839
|
+
playlistId,
|
|
840
|
+
startSeconds,
|
|
841
|
+
embedUrl: withQuery(`${YOUTUBE_EMBED_ORIGIN}/embed/${id}`, [
|
|
842
|
+
["list", playlistId],
|
|
843
|
+
["start", start]
|
|
844
|
+
]),
|
|
845
|
+
watchUrl: shorts ? `https://www.youtube.com/shorts/${id}` : withQuery("https://www.youtube.com/watch", [
|
|
846
|
+
["v", id],
|
|
847
|
+
["list", playlistId],
|
|
848
|
+
["t", start && `${start}s`]
|
|
849
|
+
]),
|
|
850
|
+
thumbnailUrl: `https://i.ytimg.com/vi/${id}/hqdefault.jpg`,
|
|
851
|
+
aspectRatio: shorts ? PORTRAIT : LANDSCAPE
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
var VIMEO_ID_RE = /^\d{1,12}$/;
|
|
855
|
+
var VIMEO_HASH_RE = /^[0-9a-f]{6,24}$/i;
|
|
856
|
+
function vimeoIdFromSegments(segments) {
|
|
857
|
+
const [a, b, c, d] = segments;
|
|
858
|
+
if (a && VIMEO_ID_RE.test(a)) {
|
|
859
|
+
return { id: a, ...b && VIMEO_HASH_RE.test(b) ? { hash: b } : {} };
|
|
860
|
+
}
|
|
861
|
+
if (a === "video" && b && VIMEO_ID_RE.test(b)) return { id: b };
|
|
862
|
+
if (a === "channels" && c && VIMEO_ID_RE.test(c)) return { id: c };
|
|
863
|
+
if (a === "groups" && c === "videos" && d && VIMEO_ID_RE.test(d)) return { id: d };
|
|
864
|
+
if ((a === "album" || a === "showcase") && c === "video" && d && VIMEO_ID_RE.test(d)) {
|
|
865
|
+
return { id: d };
|
|
866
|
+
}
|
|
867
|
+
return null;
|
|
868
|
+
}
|
|
869
|
+
function parseVimeo(url) {
|
|
870
|
+
const host = baseHost(url);
|
|
871
|
+
if (host !== "vimeo.com" && host !== "player.vimeo.com") return null;
|
|
872
|
+
const found = vimeoIdFromSegments(pathSegments(url));
|
|
873
|
+
if (!found) return null;
|
|
874
|
+
const queryHash = url.searchParams.get("h");
|
|
875
|
+
const hash = found.hash ?? (queryHash && VIMEO_HASH_RE.test(queryHash) ? queryHash : void 0);
|
|
876
|
+
const startSeconds = parseVideoStartTime(
|
|
877
|
+
fragmentParam(url, "t") ?? url.searchParams.get("t") ?? url.searchParams.get("start")
|
|
878
|
+
);
|
|
879
|
+
const timeFragment = startSeconds ? `#t=${startSeconds}s` : "";
|
|
880
|
+
return build("vimeo", {
|
|
881
|
+
id: found.id,
|
|
882
|
+
startSeconds,
|
|
883
|
+
embedUrl: withQuery(`https://player.vimeo.com/video/${found.id}`, [["h", hash]]) + timeFragment,
|
|
884
|
+
watchUrl: `https://vimeo.com/${found.id}${hash ? `/${hash}` : ""}${timeFragment}`
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
var LOOM_ID_RE = /(?:^|-)([0-9a-f]{32})$/i;
|
|
888
|
+
function parseLoom(url) {
|
|
889
|
+
if (baseHost(url) !== "loom.com") return null;
|
|
890
|
+
const [head, second] = pathSegments(url);
|
|
891
|
+
if (head !== "share" && head !== "embed" || !second) return null;
|
|
892
|
+
const match = LOOM_ID_RE.exec(second);
|
|
893
|
+
if (!match) return null;
|
|
894
|
+
const id = match[1].toLowerCase();
|
|
895
|
+
return build("loom", {
|
|
896
|
+
id,
|
|
897
|
+
embedUrl: `https://www.loom.com/embed/${id}`,
|
|
898
|
+
watchUrl: `https://www.loom.com/share/${id}`
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
var DAILYMOTION_ID_RE = /^[A-Za-z0-9]{5,20}$/;
|
|
902
|
+
function parseDailymotion(url) {
|
|
903
|
+
const host = baseHost(url);
|
|
904
|
+
const segments = pathSegments(url);
|
|
905
|
+
let raw = null;
|
|
906
|
+
if (host === "dai.ly") raw = segments[0] ?? null;
|
|
907
|
+
else if (host === "dailymotion.com") {
|
|
908
|
+
if (segments[0] === "video") raw = segments[1] ?? null;
|
|
909
|
+
else if (segments[0] === "embed" && segments[1] === "video") raw = segments[2] ?? null;
|
|
910
|
+
else return null;
|
|
911
|
+
} else if (host === "geo.dailymotion.com") {
|
|
912
|
+
raw = url.searchParams.get("video");
|
|
913
|
+
} else {
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
const id = raw?.split("_")[0];
|
|
917
|
+
if (!id || !DAILYMOTION_ID_RE.test(id)) return null;
|
|
918
|
+
const startSeconds = parseVideoStartTime(url.searchParams.get("start"));
|
|
919
|
+
const start = startSeconds ? String(startSeconds) : void 0;
|
|
920
|
+
return build("dailymotion", {
|
|
921
|
+
id,
|
|
922
|
+
startSeconds,
|
|
923
|
+
embedUrl: withQuery(`https://www.dailymotion.com/embed/video/${id}`, [["start", start]]),
|
|
924
|
+
watchUrl: withQuery(`https://www.dailymotion.com/video/${id}`, [["start", start]]),
|
|
925
|
+
thumbnailUrl: `https://www.dailymotion.com/thumbnail/video/${id}`
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
var WISTIA_ID_RE = /^[a-z0-9]{10}$/;
|
|
929
|
+
var WISTIA_ACCOUNT_HOST_RE = /^[a-z0-9-]{1,63}\.wistia\.com$/;
|
|
930
|
+
function parseWistia(url) {
|
|
931
|
+
const host = url.hostname.toLowerCase();
|
|
932
|
+
const fastHost = host === "fast.wistia.net" || host === "fast.wistia.com";
|
|
933
|
+
if (!fastHost && !WISTIA_ACCOUNT_HOST_RE.test(host)) return null;
|
|
934
|
+
const segments = pathSegments(url);
|
|
935
|
+
let raw;
|
|
936
|
+
if (segments[0] === "medias") raw = segments[1];
|
|
937
|
+
else if (segments[0] === "embed" && (segments[1] === "iframe" || segments[1] === "medias")) {
|
|
938
|
+
raw = segments[2];
|
|
939
|
+
}
|
|
940
|
+
const id = raw?.replace(/\.jsonp$/, "");
|
|
941
|
+
if (!id || !WISTIA_ID_RE.test(id)) return null;
|
|
942
|
+
const embedUrl = `https://fast.wistia.net/embed/iframe/${id}`;
|
|
943
|
+
return build("wistia", {
|
|
944
|
+
id,
|
|
945
|
+
embedUrl,
|
|
946
|
+
// A media page lives on the account's own subdomain; the fast host only
|
|
947
|
+
// serves the player, which doubles as a viewable page.
|
|
948
|
+
watchUrl: fastHost ? embedUrl : `https://${host}/medias/${id}`
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
var PARSERS = [
|
|
952
|
+
parseYouTube,
|
|
953
|
+
parseVimeo,
|
|
954
|
+
parseLoom,
|
|
955
|
+
parseDailymotion,
|
|
956
|
+
parseWistia
|
|
957
|
+
];
|
|
958
|
+
function parseVideoEmbedUrl(input) {
|
|
959
|
+
if (typeof input !== "string") return null;
|
|
960
|
+
const url = toUrl(input);
|
|
961
|
+
if (!url) return null;
|
|
962
|
+
for (const parse of PARSERS) {
|
|
963
|
+
const embed = parse(url);
|
|
964
|
+
if (embed) return embed;
|
|
965
|
+
}
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
function isVideoEmbedUrl(input) {
|
|
969
|
+
return parseVideoEmbedUrl(input) !== null;
|
|
970
|
+
}
|
|
971
|
+
var VIDEO_EMBED_IFRAME_ALLOW = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen";
|
|
972
|
+
function videoEmbedIframeAttributes(embed, title) {
|
|
973
|
+
return {
|
|
974
|
+
src: embed.embedUrl,
|
|
975
|
+
title: title?.trim() || `${embed.providerName} video`,
|
|
976
|
+
allow: VIDEO_EMBED_IFRAME_ALLOW,
|
|
977
|
+
allowfullscreen: "",
|
|
978
|
+
referrerpolicy: "strict-origin-when-cross-origin",
|
|
979
|
+
loading: "lazy"
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
var GENERIC_PLAYER_TITLE_RE = /^(?:[\w .-]+[ -])?(?:video[ -])?player$/i;
|
|
983
|
+
function attributeOf(element, name) {
|
|
984
|
+
for (const [key, value] of Object.entries(element.attributes)) {
|
|
985
|
+
if (key.toLowerCase() === name) return value;
|
|
986
|
+
}
|
|
987
|
+
return void 0;
|
|
988
|
+
}
|
|
989
|
+
function soleIframe(nodes) {
|
|
990
|
+
let found = null;
|
|
991
|
+
let disqualified = false;
|
|
992
|
+
const visit = (list) => {
|
|
993
|
+
for (const node of list) {
|
|
994
|
+
if (disqualified) return;
|
|
995
|
+
if (node.type === "htmlText") {
|
|
996
|
+
if (node.value.trim()) disqualified = true;
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
if (node.type !== "htmlElement") continue;
|
|
1000
|
+
const tag = node.tagName.toLowerCase();
|
|
1001
|
+
if (tag === "iframe") {
|
|
1002
|
+
if (found) disqualified = true;
|
|
1003
|
+
found = node;
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
if (tag === "script" || tag === "style" || tag === "noscript") continue;
|
|
1007
|
+
visit(node.children);
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
visit(nodes);
|
|
1011
|
+
return disqualified ? null : found;
|
|
1012
|
+
}
|
|
1013
|
+
function iframeEmbed(element) {
|
|
1014
|
+
const embed = parseVideoEmbedUrl(attributeOf(element, "src"));
|
|
1015
|
+
if (!embed) return null;
|
|
1016
|
+
const title = attributeOf(element, "title")?.trim() ?? "";
|
|
1017
|
+
const generic = GENERIC_PLAYER_TITLE_RE.test(title) || title.toLowerCase() === `${embed.providerName} video`.toLowerCase();
|
|
1018
|
+
return { embed, title: title && !generic ? title : null };
|
|
1019
|
+
}
|
|
1020
|
+
function videoEmbedFromIframe(element) {
|
|
1021
|
+
return element.tagName.toLowerCase() === "iframe" ? iframeEmbed(element) : null;
|
|
1022
|
+
}
|
|
1023
|
+
function parseVideoEmbedInput(input) {
|
|
1024
|
+
if (typeof input !== "string") return null;
|
|
1025
|
+
const text = input.trim();
|
|
1026
|
+
if (!text) return null;
|
|
1027
|
+
if (/^<(?!https?:)/i.test(text)) {
|
|
1028
|
+
if (!/<iframe\b/i.test(text)) return null;
|
|
1029
|
+
const iframe = soleIframe(parseHtmlToNodes(text));
|
|
1030
|
+
return iframe ? iframeEmbed(iframe) : null;
|
|
1031
|
+
}
|
|
1032
|
+
const embed = parseVideoEmbedUrl(text);
|
|
1033
|
+
return embed ? { embed, title: null } : null;
|
|
1034
|
+
}
|
|
1035
|
+
function isBlankInline(node) {
|
|
1036
|
+
return node.type === "text" && !node.value.trim() || node.type === "htmlInline" && node.htmlChildren.length === 0;
|
|
1037
|
+
}
|
|
1038
|
+
function findBlockVideoEmbed(node) {
|
|
1039
|
+
if (node.type === "htmlBlock") {
|
|
1040
|
+
const iframe = soleIframe(node.htmlChildren);
|
|
1041
|
+
const found = iframe ? iframeEmbed(iframe) : null;
|
|
1042
|
+
return found ? { ...found, form: "iframe" } : null;
|
|
1043
|
+
}
|
|
1044
|
+
if (node.type !== "paragraph") return null;
|
|
1045
|
+
const text = extractPlainText(node).trim();
|
|
1046
|
+
if (text && !/\s/.test(text)) {
|
|
1047
|
+
const bare = parseVideoEmbedUrl(text);
|
|
1048
|
+
if (bare) return { embed: bare, title: null, form: "url" };
|
|
1049
|
+
}
|
|
1050
|
+
const significant = node.children.filter((child) => !isBlankInline(child));
|
|
1051
|
+
if (significant.length !== 1 || significant[0].type !== "link") return null;
|
|
1052
|
+
const link = significant[0];
|
|
1053
|
+
const embed = parseVideoEmbedUrl(link.url);
|
|
1054
|
+
if (!embed) return null;
|
|
1055
|
+
const label = extractPlainText(link).trim();
|
|
1056
|
+
const title = label && !isVideoEmbedUrl(label) && label !== link.url ? label : null;
|
|
1057
|
+
return { embed, title, form: title ? "link" : "url" };
|
|
1058
|
+
}
|
|
1059
|
+
|
|
688
1060
|
export {
|
|
689
1061
|
parseHtmlToNodes,
|
|
690
1062
|
stringifyHtmlNodes,
|
|
@@ -702,5 +1074,15 @@ export {
|
|
|
702
1074
|
createDocument,
|
|
703
1075
|
inferDocumentTitle,
|
|
704
1076
|
readFrontmatterThemeId,
|
|
705
|
-
plainTextFromInlineHtml
|
|
1077
|
+
plainTextFromInlineHtml,
|
|
1078
|
+
mediaKindForUrl,
|
|
1079
|
+
VIDEO_EMBED_PROVIDERS,
|
|
1080
|
+
parseVideoStartTime,
|
|
1081
|
+
parseVideoEmbedUrl,
|
|
1082
|
+
isVideoEmbedUrl,
|
|
1083
|
+
VIDEO_EMBED_IFRAME_ALLOW,
|
|
1084
|
+
videoEmbedIframeAttributes,
|
|
1085
|
+
videoEmbedFromIframe,
|
|
1086
|
+
parseVideoEmbedInput,
|
|
1087
|
+
findBlockVideoEmbed
|
|
706
1088
|
};
|