@ecency/render-helper 2.4.29 → 2.4.31

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.
@@ -54,6 +54,11 @@ declare function setProxyBase(p: string): void;
54
54
  * @param _format - @deprecated Ignored. Always uses 'match' — format is handled server-side via Accept header.
55
55
  */
56
56
  declare function proxifyImageSrc(url?: string, width?: number, height?: number, _format?: string): string;
57
+ /**
58
+ * Builds a srcset string with multiple width variants for responsive images.
59
+ * Uses the image proxy's width parameter to serve appropriately sized images.
60
+ */
61
+ declare function buildSrcSet(url?: string): string;
57
62
 
58
63
  declare function setCacheSize(size: number): void;
59
64
 
@@ -71,4 +76,4 @@ declare function isValidPermlink(permlink: string): boolean;
71
76
  */
72
77
  declare function simpleMarkdownToHTML(input: string): string;
73
78
 
74
- export { type Entry, type RenderOptions, SECTION_LIST, type SeoContext, catchPostImage, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase, simpleMarkdownToHTML };
79
+ export { type Entry, type RenderOptions, SECTION_LIST, type SeoContext, buildSrcSet, catchPostImage, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase, simpleMarkdownToHTML };
@@ -123,6 +123,8 @@ var ALLOWED_ATTRIBUTES = {
123
123
  ],
124
124
  "img": [
125
125
  "src",
126
+ "srcset",
127
+ "sizes",
126
128
  "alt",
127
129
  "class",
128
130
  "loading",
@@ -199,8 +201,11 @@ function createDoc(html) {
199
201
  return null;
200
202
  }
201
203
  const cleanedHtml = removeDuplicateAttributes(html);
202
- const doc = DOMParser.parseFromString(`<body>${cleanedHtml}</body>`, "text/html");
203
- return doc;
204
+ try {
205
+ return DOMParser.parseFromString(`<body>${cleanedHtml}</body>`, "text/html");
206
+ } catch {
207
+ return null;
208
+ }
204
209
  }
205
210
  function makeEntryCacheKey(entry) {
206
211
  return `${entry.author}-${entry.permlink}-${entry.last_update}-${entry.updated}`;
@@ -275,6 +280,10 @@ function sanitizeHtml(html) {
275
280
  const decodedLower = decoded.toLowerCase();
276
281
  if (name.startsWith("on")) return "";
277
282
  if (tag === "img" && name === "src" && (!/^https?:\/\//.test(decodedLower) || decodedLower.startsWith("javascript:"))) return "";
283
+ if (tag === "img" && name === "srcset") {
284
+ const candidates = decoded.split(",").map((c) => c.trim().split(/\s+/)[0]);
285
+ if (candidates.some((url) => !/^https?:\/\//.test(url))) return "";
286
+ }
278
287
  if (tag === "video" && ["src", "poster"].includes(name) && (!/^https?:\/\//.test(decodedLower) || decodedLower.startsWith("javascript:"))) return "";
279
288
  if (tag === "img" && ["dynsrc", "lowsrc"].includes(name)) return "";
280
289
  if (tag === "span" && name === "class" && decoded.toLowerCase().trim() === "wr") return "";
@@ -289,6 +298,9 @@ var proxyBase = "https://images.ecency.com";
289
298
  function setProxyBase(p2) {
290
299
  proxyBase = p2;
291
300
  }
301
+ function getProxyBase() {
302
+ return proxyBase;
303
+ }
292
304
  function extractPHash(url) {
293
305
  if (url.startsWith(`${proxyBase}/p/`)) {
294
306
  const [hash] = url.split("/p/")[1].split("?");
@@ -336,8 +348,24 @@ function proxifyImageSrc(url, width = 0, height = 0, _format = "match") {
336
348
  const b58url = multihash.toB58String(Buffer.from(realUrl.toString()));
337
349
  return `${proxyBase}/p/${b58url}?${qs}`;
338
350
  }
351
+ var SRCSET_WIDTHS = [320, 600, 800, 1024, 1280];
352
+ function buildSrcSet(url) {
353
+ if (!url || typeof url !== "string") return "";
354
+ const escapedBase = proxyBase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
355
+ const proxyPattern = new RegExp(`^${escapedBase}/p/([^?]+)`);
356
+ const match = url.match(proxyPattern);
357
+ if (match) {
358
+ const phash = extractPHash(url) || match[1];
359
+ return SRCSET_WIDTHS.map((w) => `${proxyBase}/p/${phash}?format=match&mode=fit&width=${w} ${w}w`).join(", ");
360
+ }
361
+ return SRCSET_WIDTHS.map((w) => {
362
+ const proxied = proxifyImageSrc(url, w);
363
+ return proxied ? `${proxied} ${w}w` : "";
364
+ }).filter(Boolean).join(", ");
365
+ }
339
366
 
340
367
  // src/methods/img.method.ts
368
+ var IMAGE_SIZES = "(max-width: 768px) 100vw, 700px";
341
369
  function img(el, state) {
342
370
  const src = el.getAttribute("src") || "";
343
371
  const decodedSrc = decodeURIComponent(
@@ -347,11 +375,15 @@ function img(el, state) {
347
375
  const isInvalid = !src || decodedSrc.startsWith("javascript") || decodedSrc.startsWith("vbscript") || decodedSrc === "x";
348
376
  if (isInvalid) {
349
377
  el.removeAttribute("src");
378
+ el.removeAttribute("srcset");
379
+ el.removeAttribute("sizes");
350
380
  return;
351
381
  }
352
382
  const isRelative = !/^https?:\/\//i.test(decodedSrc) && !decodedSrc.startsWith("/");
353
383
  if (isRelative) {
354
384
  el.removeAttribute("src");
385
+ el.removeAttribute("srcset");
386
+ el.removeAttribute("sizes");
355
387
  return;
356
388
  }
357
389
  el.setAttribute("itemprop", "image");
@@ -366,22 +398,41 @@ function img(el, state) {
366
398
  }
367
399
  const cls = el.getAttribute("class") || "";
368
400
  const shouldReplace = !cls.includes("no-replace");
369
- const hasAlreadyProxied = src.startsWith("https://images.ecency.com/p/") || src.startsWith("https://images.ecency.com/u/") || /^https:\/\/images\.ecency\.com\/\d+x\d+\//.test(src);
401
+ const base = getProxyBase().replace(/\/+$/, "");
402
+ const hasAlreadyProxied = src.startsWith(`${base}/p/`) || src.startsWith(`${base}/u/`) || new RegExp(`^${base.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/\\d+x\\d+/`).test(src);
370
403
  if (shouldReplace && !hasAlreadyProxied) {
371
404
  const proxified = proxifyImageSrc(decodedSrc);
372
405
  if (proxified) {
373
406
  el.setAttribute("src", proxified);
407
+ const srcset = buildSrcSet(decodedSrc);
408
+ if (srcset) {
409
+ el.setAttribute("srcset", srcset);
410
+ el.setAttribute("sizes", IMAGE_SIZES);
411
+ }
412
+ }
413
+ } else if (shouldReplace && hasAlreadyProxied) {
414
+ if (src.startsWith(`${base}/p/`)) {
415
+ const srcset = buildSrcSet(src);
416
+ if (srcset) {
417
+ el.setAttribute("srcset", srcset);
418
+ el.setAttribute("sizes", IMAGE_SIZES);
419
+ }
374
420
  }
375
421
  }
376
422
  }
377
423
  function createImageHTML(src, isLCP) {
378
424
  const proxified = proxifyImageSrc(src);
379
425
  if (!proxified) return "";
426
+ const base = getProxyBase().replace(/\/+$/, "");
427
+ const isAlreadyProxied = src.startsWith(`${base}/u/`) || new RegExp(`^${base.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/\\d+x\\d+/`).test(src);
428
+ const srcset = isAlreadyProxied ? "" : buildSrcSet(src);
380
429
  const loading = isLCP ? "eager" : "lazy";
381
430
  const fetch = isLCP ? 'fetchpriority="high"' : 'decoding="async"';
431
+ const srcsetAttr = srcset ? `srcset="${srcset}" sizes="${IMAGE_SIZES}"` : "";
382
432
  return `<img
383
433
  class="markdown-img-link"
384
434
  src="${proxified}"
435
+ ${srcsetAttr}
385
436
  loading="${loading}"
386
437
  ${fetch}
387
438
  itemprop="image"
@@ -1774,6 +1825,6 @@ function getPostBodySummary(obj, length, platform) {
1774
1825
  return res;
1775
1826
  }
1776
1827
 
1777
- export { SECTION_LIST, catchPostImage, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase, simpleMarkdownToHTML };
1828
+ export { SECTION_LIST, buildSrcSet, catchPostImage, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase, simpleMarkdownToHTML };
1778
1829
  //# sourceMappingURL=index.js.map
1779
1830
  //# sourceMappingURL=index.js.map