seemore 1.12.13 → 1.12.16

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/cli/index.js CHANGED
@@ -78,7 +78,7 @@ import pc5 from "picocolors";
78
78
  import { mkdirSync as mkdirSync3, mkdtempSync, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync7 } from "fs";
79
79
  import { createRequire as createRequire4 } from "module";
80
80
  import { tmpdir as tmpdir3 } from "os";
81
- import { isAbsolute as isAbsolute2, join as join12, relative as relative3, resolve as resolve5 } from "path";
81
+ import { isAbsolute as isAbsolute2, join as join12, relative as relative4, resolve as resolve6 } from "path";
82
82
  import pc2 from "picocolors";
83
83
  import { build as viteBuild } from "vite";
84
84
 
@@ -961,9 +961,9 @@ function splitHash(value) {
961
961
  if (index === -1) return [value, void 0];
962
962
  return [value.slice(0, index), value.slice(index + 1)];
963
963
  }
964
- function joinPosix(fromDir, relative5) {
964
+ function joinPosix(fromDir, relative6) {
965
965
  const segments = [...fromDir];
966
- for (const part of toPosix(relative5).split("/")) {
966
+ for (const part of toPosix(relative6).split("/")) {
967
967
  if (part === "" || part === ".") continue;
968
968
  if (part === "..") segments.pop();
969
969
  else segments.push(part);
@@ -1469,6 +1469,164 @@ function rehypeSeemoreRawHtml() {
1469
1469
  };
1470
1470
  }
1471
1471
 
1472
+ // src/node/vite/media.ts
1473
+ import { existsSync as existsSync4 } from "fs";
1474
+ import { dirname as dirname7, relative as relative2, resolve as resolve5 } from "path";
1475
+ import { visit as visit3 } from "unist-util-visit";
1476
+ var MEDIA = {
1477
+ video: ["src", "poster"],
1478
+ audio: ["src"],
1479
+ source: ["src"],
1480
+ track: ["src"]
1481
+ };
1482
+ function rehypeSeemoreMedia(options) {
1483
+ return (tree, file) => {
1484
+ const path = file.path ?? "";
1485
+ if (path === "") return;
1486
+ const dir = dirname7(path.split("?")[0] ?? path);
1487
+ const from = toPosix(relative2(options.contentRoot, path.split("?")[0] ?? path));
1488
+ const imports = /* @__PURE__ */ new Map();
1489
+ const importFor = (url) => {
1490
+ if (/^(?:[a-z][a-z0-9+.-]*:|\/\/|\/|#)/i.test(url) || url === "") return void 0;
1491
+ const raw = url.split(/[?#]/)[0] ?? "";
1492
+ let decoded;
1493
+ try {
1494
+ decoded = decodeURIComponent(raw);
1495
+ } catch {
1496
+ decoded = raw;
1497
+ }
1498
+ if (!existsSync4(resolve5(dir, decoded))) {
1499
+ options.onWarning(`Missing asset ${url} referenced by ${from}.`);
1500
+ return void 0;
1501
+ }
1502
+ const specifier = /^\.{1,2}\//.test(decoded) ? decoded : `./${decoded}`;
1503
+ let name = imports.get(specifier);
1504
+ if (name === void 0) {
1505
+ name = `__seemoreMedia${imports.size}`;
1506
+ imports.set(specifier, name);
1507
+ }
1508
+ return name;
1509
+ };
1510
+ visit3(tree, (node, index, parent) => {
1511
+ if (parent === void 0 || index === void 0) return;
1512
+ if (node.type === "element") {
1513
+ const fields = MEDIA[node.tagName];
1514
+ if (fields === void 0) return;
1515
+ const bound = /* @__PURE__ */ new Map();
1516
+ for (const field of fields) {
1517
+ const value = node.properties[field];
1518
+ if (typeof value !== "string") continue;
1519
+ const name = importFor(value);
1520
+ if (name !== void 0) bound.set(field, name);
1521
+ }
1522
+ if (bound.size === 0) return;
1523
+ parent.children[index] = toJsx(node, bound);
1524
+ return;
1525
+ }
1526
+ if (node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") {
1527
+ const element = node;
1528
+ const fields = element.name === null ? void 0 : MEDIA[element.name];
1529
+ if (fields === void 0) return;
1530
+ for (const attribute of element.attributes) {
1531
+ if (attribute.type !== "mdxJsxAttribute" || !fields.includes(attribute.name)) continue;
1532
+ if (typeof attribute.value !== "string") continue;
1533
+ const name = importFor(attribute.value);
1534
+ if (name !== void 0) attribute.value = identifierValue(name);
1535
+ }
1536
+ }
1537
+ });
1538
+ if (imports.size === 0) return;
1539
+ tree.children.unshift(
1540
+ ...[...imports].map(([specifier, name]) => importNode(specifier, name))
1541
+ );
1542
+ };
1543
+ }
1544
+ function toJsx(node, bound) {
1545
+ const attributes = [];
1546
+ for (const [key, value] of Object.entries(node.properties)) {
1547
+ if (value === void 0 || value === null || value === false) continue;
1548
+ const name = attributeName(key);
1549
+ const imported = bound.get(key);
1550
+ if (imported !== void 0) {
1551
+ attributes.push({ type: "mdxJsxAttribute", name, value: identifierValue(imported) });
1552
+ } else if (key === "style" && typeof value === "string") {
1553
+ attributes.push({ type: "mdxJsxAttribute", name, value: styleValue(value) });
1554
+ } else if (value === true) {
1555
+ attributes.push({ type: "mdxJsxAttribute", name, value: null });
1556
+ } else {
1557
+ attributes.push({ type: "mdxJsxAttribute", name, value: Array.isArray(value) ? value.join(" ") : String(value) });
1558
+ }
1559
+ }
1560
+ return {
1561
+ type: node.children.some((child) => child.type === "element" && child.tagName === "p") ? "mdxJsxFlowElement" : "mdxJsxTextElement",
1562
+ name: node.tagName,
1563
+ attributes,
1564
+ children: node.children
1565
+ };
1566
+ }
1567
+ function attributeName(key) {
1568
+ const match = /^(data|aria)([A-Z].*)$/.exec(key);
1569
+ if (match === null) return key;
1570
+ return `${match[1]}${(match[2] ?? "").replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}`;
1571
+ }
1572
+ function identifierValue(name) {
1573
+ return expressionValue(name, { type: "Identifier", name });
1574
+ }
1575
+ function styleValue(css) {
1576
+ const properties = [];
1577
+ for (const declaration of css.split(";")) {
1578
+ const colon = declaration.indexOf(":");
1579
+ if (colon === -1) continue;
1580
+ const property = declaration.slice(0, colon).trim();
1581
+ const value = declaration.slice(colon + 1).trim();
1582
+ if (property === "" || value === "") continue;
1583
+ const key = property.startsWith("--") ? property : property.replace(/^-ms-/, "ms-").replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
1584
+ properties.push({
1585
+ type: "Property",
1586
+ kind: "init",
1587
+ method: false,
1588
+ shorthand: false,
1589
+ computed: false,
1590
+ key: { type: "Literal", value: key },
1591
+ value: { type: "Literal", value }
1592
+ });
1593
+ }
1594
+ return expressionValue(JSON.stringify(css), { type: "ObjectExpression", properties });
1595
+ }
1596
+ function expressionValue(source, expression) {
1597
+ return {
1598
+ type: "mdxJsxAttributeValueExpression",
1599
+ value: source,
1600
+ data: {
1601
+ estree: {
1602
+ type: "Program",
1603
+ sourceType: "module",
1604
+ body: [{ type: "ExpressionStatement", expression }]
1605
+ }
1606
+ }
1607
+ };
1608
+ }
1609
+ function importNode(specifier, name) {
1610
+ return {
1611
+ type: "mdxjsEsm",
1612
+ value: "",
1613
+ data: {
1614
+ estree: {
1615
+ type: "Program",
1616
+ sourceType: "module",
1617
+ body: [
1618
+ {
1619
+ type: "ImportDeclaration",
1620
+ attributes: [],
1621
+ specifiers: [{ type: "ImportDefaultSpecifier", local: { type: "Identifier", name } }],
1622
+ source: { type: "Literal", value: specifier }
1623
+ }
1624
+ ]
1625
+ }
1626
+ }
1627
+ };
1628
+ }
1629
+
1472
1630
  // src/node/vite/mdx.ts
1473
1631
  var MARKDOWN_EXPORT = "_markdown";
1474
1632
  function createRemarkPlugins(options) {
@@ -1483,7 +1641,9 @@ function createRemarkPlugins(options) {
1483
1641
  // frontmatter strip is the one thing that must come first — its keys are metadata, not
1484
1642
  // content. Heading ids stay off: `## Title [#title]` is noise in a paste.
1485
1643
  remarkSeemoreMarkdownSource,
1486
- [remarkLLMs, { as: MARKDOWN_EXPORT, headingIds: false }],
1644
+ function() {
1645
+ return remarkSeemoreLLMs.call(this, options);
1646
+ },
1487
1647
  remarkHeading,
1488
1648
  remarkAdmonition,
1489
1649
  remarkDirectiveAdmonition,
@@ -1510,7 +1670,28 @@ function createRemarkPlugins(options) {
1510
1670
  () => remarkSeemoreLinks(options)
1511
1671
  ];
1512
1672
  }
1513
- function createRehypePlugins(options = {}) {
1673
+ function remarkSeemoreLLMs(options) {
1674
+ const snapshot = remarkLLMs.call(this, { as: MARKDOWN_EXPORT, headingIds: false });
1675
+ return (tree, file) => {
1676
+ try {
1677
+ snapshot(tree, file);
1678
+ } catch (error) {
1679
+ options.onWarning(
1680
+ `Could not convert ${file.path} back to Markdown for copying (${error.message}); the raw file is copied instead.`
1681
+ );
1682
+ const fallback = { type: "root", children: [{ type: "html", value: sourceOf(tree, file) }] };
1683
+ snapshot(fallback, file);
1684
+ tree.children.unshift(...fallback.children.slice(0, -1));
1685
+ }
1686
+ };
1687
+ }
1688
+ function sourceOf(tree, file) {
1689
+ const source = String(file.value);
1690
+ const yaml = tree.children[0]?.type === "yaml" ? tree.children[0] : void 0;
1691
+ const end = yaml?.position?.end.offset;
1692
+ return end === void 0 ? source : source.slice(end).trimStart();
1693
+ }
1694
+ function createRehypePlugins(options) {
1514
1695
  return [
1515
1696
  // A fence in a language Shiki has no grammar for (anything an AI dreamt up) is plain code
1516
1697
  // on the page, not a dead one: `plaintext` is special-cased by Shiki and never needs
@@ -1525,14 +1706,17 @@ function createRehypePlugins(options = {}) {
1525
1706
  // `rehype-code` reads `noCopy` from. Running it here means each plugin above has already
1526
1707
  // taken what it needs, and the elements it creates are not stamped as editable, which is
1527
1708
  // right: they have no source range a text editor could be handed.
1528
- rehypeSeemoreRawHtml
1709
+ rehypeSeemoreRawHtml,
1710
+ // After `rehype-raw`, the first point where a `.md` file's hand-written `<video>` is an
1711
+ // element rather than a string.
1712
+ () => rehypeSeemoreMedia(options)
1529
1713
  ];
1530
1714
  }
1531
1715
 
1532
1716
  // src/node/vite/plugin.ts
1533
1717
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1534
1718
  import { createRequire as createRequire2 } from "module";
1535
- import { dirname as dirname7 } from "path";
1719
+ import { dirname as dirname8 } from "path";
1536
1720
 
1537
1721
  // src/node/search/build.ts
1538
1722
  import { readFileSync as readFileSync5 } from "fs";
@@ -1628,7 +1812,7 @@ var require_2 = createRequire2(import.meta.url);
1628
1812
  function styleImports(ctx) {
1629
1813
  const lines = [`@import 'fumadocs-ui/css/${ctx.config.theme}.css';`];
1630
1814
  try {
1631
- lines.push(`@source '${dirname7(require_2.resolve("fumadocs-ui/package.json"))}/dist';`);
1815
+ lines.push(`@source '${dirname8(require_2.resolve("fumadocs-ui/package.json"))}/dist';`);
1632
1816
  } catch {
1633
1817
  }
1634
1818
  return lines.join("\n");
@@ -1875,7 +2059,7 @@ function readIfExists(path) {
1875
2059
  }
1876
2060
 
1877
2061
  // src/node/vite/watcher.ts
1878
- import { relative as relative2 } from "path";
2062
+ import { relative as relative3 } from "path";
1879
2063
  import chokidar from "chokidar";
1880
2064
  var CONTENT_FILE = /\.(?:mdx?|json)$/i;
1881
2065
  var EXCLUDED_DIR = /(?:^|\/)(?:node_modules|dist|build|out|vendor|target|venv|deps|Pods|bower_components|\.[^/]+)(?:$|\/)/;
@@ -1906,7 +2090,7 @@ function seemoreWatcherPlugin(ctx) {
1906
2090
  ignoreInitial: true,
1907
2091
  ignored: (path, stats) => {
1908
2092
  if (path === ctx.config.configFile) return false;
1909
- return isWatchIgnored(relative2(ctx.contentRoot, path), stats?.isFile() === true, ctx.config.include);
2093
+ return isWatchIgnored(relative3(ctx.contentRoot, path), stats?.isFile() === true, ctx.config.include);
1910
2094
  }
1911
2095
  });
1912
2096
  const onEvent = (event, path) => {
@@ -1987,7 +2171,11 @@ function createViteConfig({ ctx, mode, outDir, ssrOutDir, auth = false }) {
1987
2171
  getResolver: () => ctx.resolver(),
1988
2172
  onWarning: (message) => ctx.warnings.add(message)
1989
2173
  }),
1990
- rehypePlugins: createRehypePlugins({ positions: mode === "dev" && ctx.config.features["content.edit"] }),
2174
+ rehypePlugins: createRehypePlugins({
2175
+ contentRoot: ctx.contentRoot,
2176
+ onWarning: (message) => ctx.warnings.add(message),
2177
+ positions: mode === "dev" && ctx.config.features["content.edit"]
2178
+ }),
1991
2179
  // MDX compiles its own JSX. Vite's builtin transform infers a file's language from its
1992
2180
  // extension and does not know `.md`/`.mdx`, so leaving JSX in the output would fail to
1993
2181
  // parse. The output is plain JS against the automatic runtime, so nothing downstream has
@@ -2131,7 +2319,7 @@ async function loadPrerenderModule(ctx, ssrOutDir) {
2131
2319
 
2132
2320
  // src/node/social/cards.ts
2133
2321
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync6 } from "fs";
2134
- import { dirname as dirname8, join as join11 } from "path";
2322
+ import { dirname as dirname9, join as join11 } from "path";
2135
2323
 
2136
2324
  // src/shared/og.ts
2137
2325
  function ogImagePath(url) {
@@ -2155,7 +2343,7 @@ async function generateSocialCards(ctx, outDir) {
2155
2343
  const png = await renderCard(takumi, ctx.config.title, page.data.title, page.data.description);
2156
2344
  if (png === void 0) continue;
2157
2345
  const target = join11(outDir, ogImagePath(page.url));
2158
- mkdirSync2(dirname8(target), { recursive: true });
2346
+ mkdirSync2(dirname9(target), { recursive: true });
2159
2347
  writeFileSync6(target, png);
2160
2348
  written++;
2161
2349
  }
@@ -2196,7 +2384,7 @@ async function runBuild(options) {
2196
2384
  const config = { ...loaded.config, base: options.base === void 0 ? loaded.config.base : normaliseBase(options.base) };
2197
2385
  warnAboutMissingBase(config.base, loaded.file);
2198
2386
  const ctx = createContext({ config, contentRoot });
2199
- const outDir = resolve5(options.cwd, options.outDir ?? "dist");
2387
+ const outDir = resolve6(options.cwd, options.outDir ?? "dist");
2200
2388
  assertSafeOutDir(outDir, options.cwd, contentRoot);
2201
2389
  const password = config.auth === void 0 ? void 0 : readPassword("npx seemore build");
2202
2390
  const scan2 = ctx.source.current();
@@ -2205,7 +2393,7 @@ async function runBuild(options) {
2205
2393
  throw new Error(`No Markdown files found under ${contentRoot}. Point seemore at a folder that has some, or check \`exclude\`.`);
2206
2394
  }
2207
2395
  for (const warning of scan2.warnings) ctx.warnings.add(warning);
2208
- console.log(pc2.dim(`seemore ${scan2.pages.length} pages from ${relative3(options.cwd, contentRoot) || "."}`));
2396
+ console.log(pc2.dim(`seemore ${scan2.pages.length} pages from ${relative4(options.cwd, contentRoot) || "."}`));
2209
2397
  await viteBuild(createViteConfig({ ctx, mode: "build", outDir, auth: password !== void 0 }));
2210
2398
  const template = readFileSync7(join12(outDir, "index.html"), "utf8");
2211
2399
  const routes = password === void 0 ? await prerenderPages(ctx, outDir, template) : countRoutes(ctx);
@@ -2224,7 +2412,7 @@ async function runBuild(options) {
2224
2412
  console.log(pc2.dim(`seemore ${encrypted} files encrypted; visitors unlock them with the password`));
2225
2413
  }
2226
2414
  ctx.warnings.flush();
2227
- console.log(pc2.green(`seemore ${routes} pages written to ${relative3(options.cwd, outDir) || outDir}`));
2415
+ console.log(pc2.green(`seemore ${routes} pages written to ${relative4(options.cwd, outDir) || outDir}`));
2228
2416
  return { outDir, routes };
2229
2417
  }
2230
2418
  async function prerenderPages(ctx, outDir, template) {
@@ -2253,7 +2441,7 @@ function countRoutes(ctx) {
2253
2441
  }
2254
2442
  function assertSafeOutDir(outDir, cwd, contentRoot) {
2255
2443
  const contains = (parent, child) => {
2256
- const rel = relative3(parent, child);
2444
+ const rel = relative4(parent, child);
2257
2445
  return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
2258
2446
  };
2259
2447
  for (const [name, dir] of [
@@ -2353,8 +2541,8 @@ async function runDev(options) {
2353
2541
  }
2354
2542
 
2355
2543
  // src/cli/export.ts
2356
- import { existsSync as existsSync4, mkdtempSync as mkdtempSync2, readFileSync as readFileSync8, readdirSync as readdirSync2, rmSync as rmSync2, statSync, writeFileSync as writeFileSync8, mkdirSync as mkdirSync4 } from "fs";
2357
- import { basename as basename2, dirname as dirname9, extname, join as join13, relative as relative4, resolve as resolve6 } from "path";
2544
+ import { existsSync as existsSync5, mkdtempSync as mkdtempSync2, readFileSync as readFileSync8, readdirSync as readdirSync2, rmSync as rmSync2, statSync, writeFileSync as writeFileSync8, mkdirSync as mkdirSync4 } from "fs";
2545
+ import { basename as basename2, dirname as dirname10, extname, join as join13, relative as relative5, resolve as resolve7 } from "path";
2358
2546
  import { tmpdir as tmpdir4 } from "os";
2359
2547
  import pc4 from "picocolors";
2360
2548
  import { build as viteBuild2 } from "vite";
@@ -2389,11 +2577,11 @@ var THEME_INIT = `<script>(function () {
2389
2577
 
2390
2578
  // src/cli/export.ts
2391
2579
  async function runExport(options) {
2392
- const target = resolve6(options.cwd, options.file);
2393
- if (!existsSync4(target) || !statSync(target).isFile()) {
2580
+ const target = resolve7(options.cwd, options.file);
2581
+ if (!existsSync5(target) || !statSync(target).isFile()) {
2394
2582
  throw new Error(`No such file: ${options.file}`);
2395
2583
  }
2396
- const contentRoot = resolveContentRoot(options.cwd, dirname9(target));
2584
+ const contentRoot = resolveContentRoot(options.cwd, dirname10(target));
2397
2585
  const loaded = await loadConfig({ root: contentRoot, configPath: resolveConfigPath(options) });
2398
2586
  const config = {
2399
2587
  ...loaded.config,
@@ -2413,7 +2601,7 @@ ${errors.join("\n\n")}`);
2413
2601
  }
2414
2602
  const page = ctx.pages().find((candidate) => candidate.absPath === canonicalise(target));
2415
2603
  if (page === void 0) {
2416
- throw new Error(`${options.file} is not part of this site \u2014 excluded in the config, or outside ${relative4(options.cwd, contentRoot) || "."}.`);
2604
+ throw new Error(`${options.file} is not part of this site \u2014 excluded in the config, or outside ${relative5(options.cwd, contentRoot) || "."}.`);
2417
2605
  }
2418
2606
  const outDir = mkdtempSync2(join13(tmpdir4(), "seemore-export-"));
2419
2607
  const ssrOutDir = mkdtempSync2(join13(tmpdir4(), "seemore-export-ssr-"));
@@ -2426,14 +2614,14 @@ ${errors.join("\n\n")}`);
2426
2614
  const runtime = await bundleRuntime();
2427
2615
  const html = assemble({ article, css, runtime, config, outDir, contentRoot, template });
2428
2616
  const filename = `${basename2(target).replace(/\.(?:md|mdx)$/i, "")}.html`;
2429
- const targetPath = options.out === void 0 ? join13(dirname9(target), filename) : join13(resolve6(options.cwd, options.out), filename);
2430
- if (existsSync4(targetPath)) {
2431
- console.log(pc4.yellow(`seemore replacing existing ${relative4(options.cwd, targetPath) || targetPath}`));
2617
+ const targetPath = options.out === void 0 ? join13(dirname10(target), filename) : join13(resolve7(options.cwd, options.out), filename);
2618
+ if (existsSync5(targetPath)) {
2619
+ console.log(pc4.yellow(`seemore replacing existing ${relative5(options.cwd, targetPath) || targetPath}`));
2432
2620
  }
2433
- mkdirSync4(dirname9(targetPath), { recursive: true });
2621
+ mkdirSync4(dirname10(targetPath), { recursive: true });
2434
2622
  writeFileSync8(targetPath, html, "utf8");
2435
2623
  ctx.warnings.flush();
2436
- console.log(pc4.green(`seemore wrote ${relative4(options.cwd, targetPath) || targetPath} (${formatBytes2(html.length)})`));
2624
+ console.log(pc4.green(`seemore wrote ${relative5(options.cwd, targetPath) || targetPath} (${formatBytes2(html.length)})`));
2437
2625
  } finally {
2438
2626
  rmSync2(outDir, { recursive: true, force: true });
2439
2627
  rmSync2(ssrOutDir, { recursive: true, force: true });
@@ -2441,7 +2629,7 @@ ${errors.join("\n\n")}`);
2441
2629
  }
2442
2630
  function readBuiltCss(outDir) {
2443
2631
  const assets = join13(outDir, "assets");
2444
- const files = existsSync4(assets) ? readdirSync2(assets).filter((file) => file.endsWith(".css")) : [];
2632
+ const files = existsSync5(assets) ? readdirSync2(assets).filter((file) => file.endsWith(".css")) : [];
2445
2633
  if (files.length === 0) throw new Error("The build produced no stylesheet \u2014 the export would be unstyled.");
2446
2634
  return files.map((file) => readFileSync8(join13(assets, file), "utf8")).join("\n");
2447
2635
  }
@@ -2532,7 +2720,7 @@ function resolveAsset(src, base, outDir, contentRoot) {
2532
2720
  const suffix = path.replace(/^\/+/, "");
2533
2721
  for (const root of [outDir, contentRoot]) {
2534
2722
  const candidate = join13(root, suffix);
2535
- if (existsSync4(candidate) && statSync(candidate).isFile()) return candidate;
2723
+ if (existsSync5(candidate) && statSync(candidate).isFile()) return candidate;
2536
2724
  }
2537
2725
  return void 0;
2538
2726
  }
@@ -2560,7 +2748,7 @@ function dataUriFor(file) {
2560
2748
  function faviconLink(config, contentRoot, template) {
2561
2749
  if (config.favicon !== void 0) {
2562
2750
  const file = join13(contentRoot, config.favicon);
2563
- if (existsSync4(file)) {
2751
+ if (existsSync5(file)) {
2564
2752
  try {
2565
2753
  return `<link rel="icon" href="${dataUriFor(file)}" />`;
2566
2754
  } catch {