@ecency/render-helper 2.5.10 → 2.5.11

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.
@@ -1,4 +1,5 @@
1
1
  import { DOMParser as DOMParser$1, XMLSerializer } from '@xmldom/xmldom';
2
+ import he2 from 'he';
2
3
  import xss from 'xss';
3
4
  import multihash from 'multihashes';
4
5
  import querystring from 'querystring';
@@ -7,7 +8,6 @@ import { Remarkable } from 'remarkable';
7
8
  import { linkify as linkify$1 } from 'remarkable/linkify';
8
9
  import * as htmlparser2 from 'htmlparser2';
9
10
  import * as domSerializerModule from 'dom-serializer';
10
- import he from 'he';
11
11
 
12
12
  // src/consts/white-list.const.ts
13
13
  var WHITE_LIST = [
@@ -133,6 +133,13 @@ var ALLOWED_ATTRIBUTES = {
133
133
  "decoding",
134
134
  "itemprop"
135
135
  ],
136
+ // Responsive image content-negotiation wrapper emitted for web/self-hosted
137
+ // (forApp === false). Without these entries `xss` silently collapses the
138
+ // <picture>/<source> to a bare <img>. `source` attrs are further constrained
139
+ // in sanitize-html (srcset must be a proxy /p/ URL; type must be avif/webp;
140
+ // a type-less <source> is dropped post-pass).
141
+ "picture": [],
142
+ "source": ["type", "srcset", "sizes"],
136
143
  "span": ["class", "id", "data-align"],
137
144
  "iframe": ["src", "class", "frameborder", "allowfullscreen", "webkitallowfullscreen", "mozallowfullscreen", "sandbox"],
138
145
  "video": ["src", "controls", "poster"],
@@ -176,8 +183,14 @@ function createParser() {
176
183
  });
177
184
  }
178
185
  var DOMParser = createParser();
179
-
180
- // src/helper.ts
186
+ function decodeImageSrc(src) {
187
+ const entityDecoded = he2.decode(src);
188
+ try {
189
+ return decodeURIComponent(entityDecoded).trim();
190
+ } catch {
191
+ return entityDecoded.trim();
192
+ }
193
+ }
181
194
  function isSpaceChar(c) {
182
195
  return c === 32 || c === 9 || c === 10 || c === 13 || c === 12;
183
196
  }
@@ -433,33 +446,6 @@ function removeChildNodes(node) {
433
446
  node.removeChild(node.firstChild);
434
447
  }
435
448
  }
436
- var decodeEntities = (input) => input.replace(/&#(\d+);?/g, (_, dec) => String.fromCodePoint(Number(dec))).replace(/&#x([0-9a-f]+);?/gi, (_, hex) => String.fromCodePoint(parseInt(hex, 16)));
437
- function sanitizeHtml(html) {
438
- return xss(html, {
439
- whiteList: ALLOWED_ATTRIBUTES,
440
- stripIgnoreTag: true,
441
- stripIgnoreTagBody: ["style"],
442
- css: false,
443
- // block style attrs entirely for safety
444
- onTagAttr: (tag, name, value) => {
445
- const decoded = decodeEntities(value.trim());
446
- const decodedLower = decoded.toLowerCase();
447
- if (name.startsWith("on")) return "";
448
- if (tag === "img" && name === "src" && !/^https?:\/\//.test(decodedLower)) return "";
449
- if (tag === "img" && name === "srcset") {
450
- const candidates = decoded.split(",").map((c) => c.trim().split(/\s+/)[0]);
451
- if (candidates.some((url) => !/^https?:\/\//i.test(url))) return "";
452
- }
453
- if (tag === "video" && ["src", "poster"].includes(name) && !/^https?:\/\//.test(decodedLower)) return "";
454
- if (tag === "img" && ["dynsrc", "lowsrc"].includes(name)) return "";
455
- if (tag === "span" && name === "class" && decoded.toLowerCase().trim() === "wr") return "";
456
- if (name === "id") {
457
- if (!ID_WHITELIST.test(decoded)) return "";
458
- }
459
- return void 0;
460
- }
461
- });
462
- }
463
449
  var proxyBase = "https://i.ecency.com";
464
450
  var urlHashCache = new LRUCache({ max: 500 });
465
451
  function getUrlHash(url) {
@@ -495,7 +481,7 @@ function getLatestUrl(str) {
495
481
  const [last] = [...str.replace(/https?:\/\//g, "\n$&").trim().split("\n")].reverse();
496
482
  return last;
497
483
  }
498
- function proxifyImageSrc(url, width = 0, height = 0, _format = "match", opts = {}) {
484
+ function proxifyForFormat(url, width = 0, height = 0, format = "match", opts = {}) {
499
485
  if (!url || typeof url !== "string" || !isValidUrl(url)) {
500
486
  return "";
501
487
  }
@@ -512,7 +498,7 @@ function proxifyImageSrc(url, width = 0, height = 0, _format = "match", opts = {
512
498
  const realUrl = getLatestUrl(url);
513
499
  const pHash = extractPHash(realUrl);
514
500
  const options = {
515
- format: "match",
501
+ format,
516
502
  mode: "fit"
517
503
  };
518
504
  if (width > 0) {
@@ -531,29 +517,127 @@ function proxifyImageSrc(url, width = 0, height = 0, _format = "match", opts = {
531
517
  const b58url = getUrlHash(realUrl.toString());
532
518
  return `${proxyBase}/p/${b58url}?${qs}`;
533
519
  }
520
+ function proxifyImageSrc(url, width = 0, height = 0, _format = "match", opts = {}) {
521
+ return proxifyForFormat(url, width, height, "match", opts);
522
+ }
534
523
  var SRCSET_WIDTHS = [320, 600, 800, 1024, 1280];
535
524
  function buildSrcSet(url) {
525
+ return buildSrcSetForFormat(url, "match");
526
+ }
527
+ function buildSrcSetForFormat(url, format = "match") {
536
528
  if (!url || typeof url !== "string") return "";
537
529
  const proxyPrefix = `${proxyBase}/p/`;
530
+ let result;
538
531
  if (url.startsWith(proxyPrefix)) {
539
532
  const rest = url.slice(proxyPrefix.length);
540
533
  const q = rest.indexOf("?");
541
534
  const phash = extractPHash(url) || (q >= 0 ? rest.slice(0, q) : rest);
542
- return SRCSET_WIDTHS.map((w) => `${proxyBase}/p/${phash}?format=match&mode=fit&width=${w} ${w}w`).join(", ");
535
+ result = SRCSET_WIDTHS.map((w) => `${proxyBase}/p/${phash}?format=${format}&mode=fit&width=${w} ${w}w`).join(", ");
536
+ } else {
537
+ result = SRCSET_WIDTHS.map((w) => {
538
+ const proxied = proxifyForFormat(url, w, 0, format);
539
+ return proxied ? `${proxied} ${w}w` : "";
540
+ }).filter(Boolean).join(", ");
541
+ }
542
+ if (format !== "match" && result && !result.split(",").every((c) => c.includes(`format=${format}`))) {
543
+ return "";
543
544
  }
544
- return SRCSET_WIDTHS.map((w) => {
545
- const proxied = proxifyImageSrc(url, w);
546
- return proxied ? `${proxied} ${w}w` : "";
547
- }).filter(Boolean).join(", ");
545
+ return result;
546
+ }
547
+ var STATIC_RASTER_PATH_EXT = /\.(?:jpe?g|png|webp)$/i;
548
+ var SIZED_PROXY_PATH = /^\/\d+x\d+\//;
549
+ function isPictureEligibleRawUrl(rawUrl) {
550
+ if (!rawUrl || typeof rawUrl !== "string") return false;
551
+ let u;
552
+ try {
553
+ u = new URL(rawUrl);
554
+ } catch {
555
+ return false;
556
+ }
557
+ if (u.protocol !== "http:" && u.protocol !== "https:") return false;
558
+ const host = `${u.protocol}//${u.host}`;
559
+ const isProxyHost = host === proxyBase || host === "https://images.ecency.com";
560
+ if (isProxyHost && (u.pathname.startsWith("/p/") || u.pathname.startsWith("/u/") || SIZED_PROXY_PATH.test(u.pathname))) {
561
+ return false;
562
+ }
563
+ return STATIC_RASTER_PATH_EXT.test(u.pathname);
564
+ }
565
+ function buildPictureSources(rawUrl) {
566
+ if (!isPictureEligibleRawUrl(rawUrl)) return null;
567
+ const avif = buildSrcSetForFormat(rawUrl, "avif");
568
+ const webp = buildSrcSetForFormat(rawUrl, "webp");
569
+ if (!avif || !webp) return null;
570
+ return { avif, webp };
571
+ }
572
+
573
+ // src/methods/sanitize-html.method.ts
574
+ var decodeEntities = (input) => input.replace(/&#(\d+);?/g, (_, dec) => String.fromCodePoint(Number(dec))).replace(/&#x([0-9a-f]+);?/gi, (_, hex) => String.fromCodePoint(parseInt(hex, 16)));
575
+ var isProxyPSrcset = (srcset) => {
576
+ const base = trimTrailingSlash(getProxyBase());
577
+ const candidates = srcset.split(",").map((c) => c.trim().split(/\s+/)[0]).filter(Boolean);
578
+ return candidates.length > 0 && candidates.every((url) => url.startsWith(`${base}/p/`));
579
+ };
580
+ function sanitizeHtml(html) {
581
+ const cleaned = xss(html, {
582
+ whiteList: ALLOWED_ATTRIBUTES,
583
+ stripIgnoreTag: true,
584
+ stripIgnoreTagBody: ["style"],
585
+ css: false,
586
+ // block style attrs entirely for safety
587
+ onTagAttr: (tag, name, value) => {
588
+ const decoded = decodeEntities(value.trim());
589
+ const decodedLower = decoded.toLowerCase();
590
+ if (name.startsWith("on")) return "";
591
+ if (tag === "img" && name === "src" && !/^https?:\/\//.test(decodedLower)) return "";
592
+ if (tag === "img" && name === "srcset") {
593
+ const candidates = decoded.split(",").map((c) => c.trim().split(/\s+/)[0]);
594
+ if (candidates.some((url) => !/^https?:\/\//i.test(url))) return "";
595
+ }
596
+ if (tag === "source" && name === "srcset" && !isProxyPSrcset(decoded)) return "";
597
+ if (tag === "source" && name === "type" && decodedLower !== "image/avif" && decodedLower !== "image/webp") return "";
598
+ if (tag === "video" && ["src", "poster"].includes(name) && !/^https?:\/\//.test(decodedLower)) return "";
599
+ if (tag === "img" && ["dynsrc", "lowsrc"].includes(name)) return "";
600
+ if (tag === "span" && name === "class" && decoded.toLowerCase().trim() === "wr") return "";
601
+ if (name === "id") {
602
+ if (!ID_WHITELIST.test(decoded)) return "";
603
+ }
604
+ return void 0;
605
+ }
606
+ });
607
+ return cleaned.replace(
608
+ /<source\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi,
609
+ (t) => /\btype\s*=\s*["'](?:image\/avif|image\/webp)["']/i.test(t) ? t : ""
610
+ );
548
611
  }
549
612
 
550
613
  // src/methods/img.method.ts
551
614
  var IMAGE_SIZES = "(max-width: 768px) 100vw, 700px";
552
- function img(el, state) {
615
+ function wrapInPicture(el, rawUrl) {
616
+ const parent = el.parentNode;
617
+ if (!parent) return;
618
+ if (parent.nodeName && parent.nodeName.toLowerCase() === "picture") return;
619
+ const sources = buildPictureSources(rawUrl);
620
+ if (!sources) return;
621
+ const doc = el.ownerDocument;
622
+ if (!doc) return;
623
+ const sizes = el.getAttribute("sizes") || IMAGE_SIZES;
624
+ const picture = doc.createElement("picture");
625
+ const avif = doc.createElement("source");
626
+ avif.setAttribute("type", "image/avif");
627
+ avif.setAttribute("srcset", sources.avif);
628
+ avif.setAttribute("sizes", sizes);
629
+ const webp = doc.createElement("source");
630
+ webp.setAttribute("type", "image/webp");
631
+ webp.setAttribute("srcset", sources.webp);
632
+ webp.setAttribute("sizes", sizes);
633
+ parent.insertBefore(picture, el);
634
+ picture.appendChild(avif);
635
+ picture.appendChild(webp);
636
+ picture.appendChild(el);
637
+ }
638
+ function img(el, state, forApp = true) {
553
639
  const src = el.getAttribute("src") || "";
554
- const decodedSrc = decodeURIComponent(
555
- src.replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(dec)).replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
556
- ).trim();
640
+ const decodedSrc = decodeImageSrc(src);
557
641
  ["onerror", "dynsrc", "lowsrc", "width", "height"].forEach((attr) => el.removeAttribute(attr));
558
642
  const isInvalid = !src || decodedSrc.startsWith("javascript") || decodedSrc.startsWith("vbscript") || decodedSrc === "x";
559
643
  if (isInvalid) {
@@ -592,6 +676,9 @@ function img(el, state) {
592
676
  el.setAttribute("srcset", srcset);
593
677
  el.setAttribute("sizes", IMAGE_SIZES);
594
678
  }
679
+ if (!forApp) {
680
+ wrapInPicture(el, decodedSrc);
681
+ }
595
682
  }
596
683
  } else if (shouldReplace && hasAlreadyProxied) {
597
684
  if (src.startsWith(`${base}/p/`)) {
@@ -603,16 +690,17 @@ function img(el, state) {
603
690
  }
604
691
  }
605
692
  }
606
- function createImageHTML(src, isLCP) {
607
- const proxified = proxifyImageSrc(src, 0, 0, "match", { forceProxy: true });
693
+ function createImageHTML(src, isLCP, forApp = true) {
694
+ const decoded = decodeImageSrc(src);
695
+ const proxified = proxifyImageSrc(decoded, 0, 0, "match", { forceProxy: true });
608
696
  if (!proxified) return "";
609
697
  const base = trimTrailingSlash(getProxyBase());
610
- const isAlreadyProxied = src.startsWith(`${base}/u/`) || new RegExp(`^${base.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/\\d+x\\d+/`).test(src);
611
- const srcset = isAlreadyProxied ? "" : buildSrcSet(src);
698
+ const isAlreadyProxied = decoded.startsWith(`${base}/u/`) || new RegExp(`^${base.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/\\d+x\\d+/`).test(decoded);
699
+ const srcset = isAlreadyProxied ? "" : buildSrcSet(decoded);
612
700
  const loading = isLCP ? "eager" : "lazy";
613
701
  const fetch = isLCP ? 'fetchpriority="high"' : 'decoding="async"';
614
702
  const srcsetAttr = srcset ? `srcset="${srcset}" sizes="${IMAGE_SIZES}"` : "";
615
- return `<img
703
+ const imgTag = `<img
616
704
  class="markdown-img-link"
617
705
  src="${proxified}"
618
706
  ${srcsetAttr}
@@ -620,6 +708,13 @@ function createImageHTML(src, isLCP) {
620
708
  ${fetch}
621
709
  itemprop="image"
622
710
  />`;
711
+ if (!forApp) {
712
+ const sources = buildPictureSources(decoded);
713
+ if (sources) {
714
+ return `<picture><source type="image/avif" srcset="${sources.avif}" sizes="${IMAGE_SIZES}" /><source type="image/webp" srcset="${sources.webp}" sizes="${IMAGE_SIZES}" />${imgTag}</picture>`;
715
+ }
716
+ }
717
+ return imgTag;
623
718
  }
624
719
 
625
720
  // src/methods/a.method.ts
@@ -686,7 +781,7 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
686
781
  }
687
782
  if (href.match(IMG_REGEX) && href.trim().replace(/&amp;/g, "&") === getSerializedInnerHTML(el).trim().replace(/&amp;/g, "&")) {
688
783
  const isLCP = false;
689
- const imgHTML = createImageHTML(href, isLCP);
784
+ const imgHTML = createImageHTML(href, isLCP, forApp);
690
785
  const doc = DOMParser.parseFromString(imgHTML, "text/html");
691
786
  const replaceNode = doc.body?.firstChild || doc.firstChild;
692
787
  if (replaceNode && el.parentNode) {
@@ -1525,7 +1620,7 @@ function linkify(content, forApp, renderOptions) {
1525
1620
  content = content.replace(IMG_REGEX, (imglink) => {
1526
1621
  const isLCP = !firstImageUsed;
1527
1622
  firstImageUsed = true;
1528
- return createImageHTML(imglink, isLCP);
1623
+ return createImageHTML(imglink, isLCP, forApp);
1529
1624
  });
1530
1625
  authorPlaceholders.forEach(({ placeholder, html }) => {
1531
1626
  content = content.replace(placeholder, html);
@@ -1567,7 +1662,7 @@ function text(node, forApp, renderOptions) {
1567
1662
  }
1568
1663
  if (nodeValue.match(IMG_REGEX)) {
1569
1664
  const isLCP = false;
1570
- const imageHTML = createImageHTML(nodeValue, isLCP);
1665
+ const imageHTML = createImageHTML(nodeValue, isLCP, forApp);
1571
1666
  const doc = DOMParser.parseFromString(imageHTML, "text/html");
1572
1667
  const replaceNode = doc.body?.firstChild || doc.firstChild;
1573
1668
  if (replaceNode) {
@@ -1643,7 +1738,7 @@ function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, p
1643
1738
  text(child, forApp);
1644
1739
  }
1645
1740
  if (child.nodeName.toLowerCase() === "img") {
1646
- img(child, state);
1741
+ img(child, state, forApp);
1647
1742
  }
1648
1743
  if (child.nodeName.toLowerCase() === "p") {
1649
1744
  p(child);
@@ -1866,8 +1961,11 @@ var INLINE_CODE_RE = /`[^`\n]*`/g;
1866
1961
  var INDENTED_CODE_RE = /^(?: {4}|\t).+$/gm;
1867
1962
  var MD_IMAGE_RE = /!\[[^\]]*\]\(\s*([^)\s]+)(?:\s+["'][^"']*["'])?\s*\)/;
1868
1963
  var HTML_IMAGE_RE = /<img\b[^>]*?\bsrc\s*=\s*["']([^"']+)["']/i;
1964
+ var BARE_IMAGE_RE = /(^|\s)(https?:\/\/[^\s<>"'()[\]]+\.(?:tiff?|jpe?g|gif|png|svg|ico|heic|webp|arw)(?:[?#][^\s<>"'()[\]]*)?)/im;
1965
+ var MD_LINK_RE = /\[([^\]]*)\]\(\s*([^)\s]+)(?:\s+["'][^"']*["'])?\s*\)/g;
1966
+ var IMG_HREF_RE = /https?:\/\/.*\.(?:tiff?|jpe?g|gif|png|svg|ico|heic|webp|arw)/i;
1869
1967
  var SAFE_URL_RE = /^https?:\/\//i;
1870
- function findFirstImageUrl(body) {
1968
+ function findFirstImageUrl(body, includeBareUrls = false) {
1871
1969
  if (!body) return null;
1872
1970
  const cleaned = body.replace(BACKTICK_FENCE_RE, "").replace(TILDE_FENCE_RE, "").replace(INLINE_CODE_RE, "").replace(INDENTED_CODE_RE, "");
1873
1971
  const mdMatch = cleaned.match(MD_IMAGE_RE);
@@ -1878,17 +1976,33 @@ function findFirstImageUrl(body) {
1878
1976
  return null;
1879
1977
  }
1880
1978
  }
1881
- const mdValid = !!mdMatch;
1882
- const htmlValid = !!(htmlMatch && htmlMatch[1] && SAFE_URL_RE.test(htmlMatch[1]));
1883
- if (mdValid && htmlValid) {
1884
- return (mdMatch.index ?? 0) < (htmlMatch.index ?? 0) ? mdMatch[1] : htmlMatch[1];
1979
+ const candidates = [];
1980
+ if (mdMatch) candidates.push({ url: mdMatch[1], pos: mdMatch.index ?? 0 });
1981
+ if (htmlMatch && htmlMatch[1] && SAFE_URL_RE.test(htmlMatch[1])) {
1982
+ candidates.push({ url: htmlMatch[1], pos: htmlMatch.index ?? 0 });
1885
1983
  }
1886
- if (mdValid) return mdMatch[1];
1887
- if (htmlValid) return htmlMatch[1];
1888
- return null;
1984
+ if (includeBareUrls) {
1985
+ const bareMatch = cleaned.match(BARE_IMAGE_RE);
1986
+ if (bareMatch && bareMatch[2] && SAFE_URL_RE.test(bareMatch[2])) {
1987
+ candidates.push({ url: bareMatch[2], pos: (bareMatch.index ?? 0) + bareMatch[1].length });
1988
+ }
1989
+ const deAmp = (s) => s.trim().replace(/&amp;/g, "&");
1990
+ for (const m of cleaned.matchAll(MD_LINK_RE)) {
1991
+ const idx = m.index ?? 0;
1992
+ if (idx > 0 && cleaned[idx - 1] === "!") continue;
1993
+ const href = m[2];
1994
+ if (href && SAFE_URL_RE.test(href) && IMG_HREF_RE.test(href) && deAmp(m[1]) === deAmp(href)) {
1995
+ candidates.push({ url: href, pos: idx });
1996
+ break;
1997
+ }
1998
+ }
1999
+ }
2000
+ if (candidates.length === 0) return null;
2001
+ candidates.sort((a2, b) => a2.pos - b.pos);
2002
+ return candidates[0].url;
1889
2003
  }
1890
2004
  function proxifyFound(src, width, height, format) {
1891
- const decoded = he.decode(src);
2005
+ const decoded = he2.decode(src);
1892
2006
  if (isGifLink(decoded)) {
1893
2007
  return proxifyImageSrc(decoded, 0, 0, format);
1894
2008
  }
@@ -1906,7 +2020,7 @@ function getImage(entry, width = 0, height = 0, format = "match") {
1906
2020
  }
1907
2021
  }
1908
2022
  if (meta && typeof meta.image === "string" && meta.image.length > 0) {
1909
- const decodedImage = he.decode(meta.image);
2023
+ const decodedImage = he2.decode(meta.image);
1910
2024
  if (isGifLink(decodedImage)) {
1911
2025
  return proxifyImageSrc(decodedImage, 0, 0, format);
1912
2026
  }
@@ -1914,7 +2028,7 @@ function getImage(entry, width = 0, height = 0, format = "match") {
1914
2028
  }
1915
2029
  if (meta && meta.image && !!meta.image.length && meta.image[0]) {
1916
2030
  if (typeof meta.image[0] === "string") {
1917
- const decodedImage = he.decode(meta.image[0]);
2031
+ const decodedImage = he2.decode(meta.image[0]);
1918
2032
  if (isGifLink(decodedImage)) {
1919
2033
  return proxifyImageSrc(decodedImage, 0, 0, format);
1920
2034
  }
@@ -1944,6 +2058,30 @@ function getImage(entry, width = 0, height = 0, format = "match") {
1944
2058
  }
1945
2059
  return null;
1946
2060
  }
2061
+ function getEntryImageRawUrl(obj) {
2062
+ if (typeof obj === "string") {
2063
+ const src = findFirstImageUrl(obj, true);
2064
+ return src ? decodeImageSrc(src) : null;
2065
+ }
2066
+ let meta;
2067
+ if (typeof obj.json_metadata === "object") {
2068
+ meta = obj.json_metadata;
2069
+ } else {
2070
+ try {
2071
+ meta = JSON.parse(obj.json_metadata);
2072
+ } catch (e) {
2073
+ meta = null;
2074
+ }
2075
+ }
2076
+ if (meta && typeof meta.image === "string" && meta.image.length > 0) {
2077
+ return decodeImageSrc(meta.image);
2078
+ }
2079
+ if (meta && meta.image && !!meta.image.length && typeof meta.image[0] === "string" && meta.image[0].length > 0) {
2080
+ return decodeImageSrc(meta.image[0]);
2081
+ }
2082
+ const bodySrc = findFirstImageUrl(obj.body, true);
2083
+ return bodySrc ? decodeImageSrc(bodySrc) : null;
2084
+ }
1947
2085
  function catchPostImage(obj, width = 0, height = 0, format = "match") {
1948
2086
  if (typeof obj === "string") {
1949
2087
  const fast = findFirstImageUrl(obj);
@@ -2045,7 +2183,7 @@ function postBodySummary(entryBody, length = 200, platform = "web") {
2045
2183
  text2 = joint(text2.split(" "), length);
2046
2184
  }
2047
2185
  if (text2) {
2048
- text2 = he.decode(text2);
2186
+ text2 = he2.decode(text2);
2049
2187
  }
2050
2188
  return text2;
2051
2189
  }
@@ -2065,6 +2203,6 @@ function getPostBodySummary(obj, length, platform) {
2065
2203
  return res;
2066
2204
  }
2067
2205
 
2068
- export { IMAGE_SIZES, SECTION_LIST, buildSrcSet, catchPostImage, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase, setSlowRenderThresholdMs, simpleMarkdownToHTML };
2206
+ export { IMAGE_SIZES, SECTION_LIST, buildPictureSources, buildSrcSet, buildSrcSetForFormat, catchPostImage, getEntryImageRawUrl, isPictureEligibleRawUrl, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase, setSlowRenderThresholdMs, simpleMarkdownToHTML };
2069
2207
  //# sourceMappingURL=index.mjs.map
2070
2208
  //# sourceMappingURL=index.mjs.map