@ecency/render-helper 2.4.15 → 2.4.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.
@@ -22,7 +22,14 @@ interface SeoContext {
22
22
  postPayout?: number;
23
23
  }
24
24
 
25
- declare function markdown2Html(obj: Entry | string, forApp?: boolean, webp?: boolean, parentDomain?: string, seoContext?: SeoContext): string;
25
+ /**
26
+ * @param obj - Entry object or raw markdown string
27
+ * @param forApp - Whether rendering for app context
28
+ * @param _webp - @deprecated Ignored. Format is now handled server-side via Accept header content negotiation.
29
+ * @param parentDomain - Parent domain for iframe embed parameters
30
+ * @param seoContext - Optional SEO context for structured data
31
+ */
32
+ declare function markdown2Html(obj: Entry | string, forApp?: boolean, _webp?: boolean, parentDomain?: string, seoContext?: SeoContext): string;
26
33
 
27
34
  declare function catchPostImage(obj: Entry | string, width?: number, height?: number, format?: string): string | null;
28
35
 
@@ -37,7 +44,10 @@ declare function catchPostImage(obj: Entry | string, width?: number, height?: nu
37
44
  declare function getPostBodySummary(obj: Entry | string, length?: number, platform?: 'ios' | 'android' | 'web'): string;
38
45
 
39
46
  declare function setProxyBase(p: string): void;
40
- declare function proxifyImageSrc(url?: string, width?: number, height?: number, format?: string): string;
47
+ /**
48
+ * @param _format - @deprecated Ignored. Always uses 'match' — format is handled server-side via Accept header.
49
+ */
50
+ declare function proxifyImageSrc(url?: string, width?: number, height?: number, _format?: string): string;
41
51
 
42
52
  declare function setCacheSize(size: number): void;
43
53
 
@@ -78,8 +78,8 @@ var LBRY_REGEX = /^(https?:)?\/\/lbry.tv\/\$\/embed\/[^?#]+(?:$|[?#])/i;
78
78
  var ODYSEE_REGEX = /^(https?:)?\/\/odysee\.com\/(?:\$|%24)\/embed\/[^/?#]+(?:$|[?#])/i;
79
79
  var SKATEHIVE_IPFS_REGEX = /^https?:\/\/ipfs\.skatehive\.app\/ipfs\/([^/?#]+)/i;
80
80
  var ARCH_REGEX = /^(https?:)?\/\/archive.org\/embed\/[^/?#]+(?:$|[?#])/i;
81
- var SPEAK_REGEX = /(?:https?:\/\/(?:(?:play\.)?3speak.([a-z]+)\/watch\?v=)|(?:(?:play\.)?3speak.([a-z]+)\/embed\?v=))([A-Za-z0-9\_\-\.\/]+)(&.*)?/i;
82
- var SPEAK_EMBED_REGEX = /^(https?:)?\/\/(?:play\.)?3speak.([a-z]+)\/embed\?[^/]+$/i;
81
+ var SPEAK_REGEX = /(?:https?:\/\/(?:(?:play\.)?3speak\.([a-z]+)\/watch\?v=)|(?:(?:play\.)?3speak\.([a-z]+)\/embed\?v=))([A-Za-z0-9_\-\.\/]+)(&.*)?/i;
82
+ var SPEAK_EMBED_REGEX = /^(https?:)?\/\/(?:play\.)?3speak\.([a-z]+)\/(?:embed|watch)\?.+$/i;
83
83
  var TWITTER_REGEX = /(?:https?:\/\/(?:(?:twitter\.com\/(.*?)\/status\/(.*))))/gi;
84
84
  var SPOTIFY_REGEX = /^https:\/\/open\.spotify\.com\/playlist\/(.*)?$/gi;
85
85
  var RUMBLE_REGEX = /^https:\/\/rumble.com\/embed\/([a-zA-Z0-9-]+)\/\?pub=\w+/;
@@ -284,15 +284,13 @@ function sanitizeHtml(html) {
284
284
  });
285
285
  }
286
286
  var proxyBase = "https://images.ecency.com";
287
- var fileExtension = true;
288
287
  function setProxyBase(p2) {
289
288
  proxyBase = p2;
290
- fileExtension = proxyBase == "https://images.ecency.com";
291
289
  }
292
290
  function extractPHash(url) {
293
291
  if (url.startsWith(`${proxyBase}/p/`)) {
294
292
  const [hash] = url.split("/p/")[1].split("?");
295
- return hash.replace(/.webp/, "").replace(/.png/, "");
293
+ return hash.replace(/\.(webp|png)$/, "");
296
294
  }
297
295
  return null;
298
296
  }
@@ -307,7 +305,7 @@ function getLatestUrl(str) {
307
305
  const [last] = [...str.replace(/https?:\/\//g, "\n$&").trim().split("\n")].reverse();
308
306
  return last;
309
307
  }
310
- function proxifyImageSrc(url, width = 0, height = 0, format = "match") {
308
+ function proxifyImageSrc(url, width = 0, height = 0, _format = "match") {
311
309
  if (!url || typeof url !== "string" || !isValidUrl(url)) {
312
310
  return "";
313
311
  }
@@ -320,7 +318,7 @@ function proxifyImageSrc(url, width = 0, height = 0, format = "match") {
320
318
  const realUrl = getLatestUrl(url);
321
319
  const pHash = extractPHash(realUrl);
322
320
  const options = {
323
- format,
321
+ format: "match",
324
322
  mode: "fit"
325
323
  };
326
324
  if (width > 0) {
@@ -331,31 +329,29 @@ function proxifyImageSrc(url, width = 0, height = 0, format = "match") {
331
329
  }
332
330
  const qs = querystring.stringify(options);
333
331
  if (pHash) {
334
- if (fileExtension) {
335
- return `${proxyBase}/p/${pHash}${format === "webp" ? ".webp" : ".png"}?${qs}`;
336
- } else {
337
- return `${proxyBase}/p/${pHash}?${qs}`;
338
- }
332
+ return `${proxyBase}/p/${pHash}?${qs}`;
339
333
  }
340
334
  const b58url = multihash.toB58String(Buffer.from(realUrl.toString()));
341
- return `${proxyBase}/p/${b58url}${fileExtension ? format === "webp" ? ".webp" : ".png" : ""}?${qs}`;
335
+ return `${proxyBase}/p/${b58url}?${qs}`;
342
336
  }
343
337
 
344
338
  // src/methods/img.method.ts
345
- function img(el, webp, state) {
346
- let src = el.getAttribute("src") || "";
339
+ function img(el, state) {
340
+ const src = el.getAttribute("src") || "";
347
341
  const decodedSrc = decodeURIComponent(
348
342
  src.replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(dec)).replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
349
343
  ).trim();
344
+ ["onerror", "dynsrc", "lowsrc", "width", "height"].forEach((attr) => el.removeAttribute(attr));
350
345
  const isInvalid = !src || decodedSrc.startsWith("javascript") || decodedSrc.startsWith("vbscript") || decodedSrc === "x";
351
346
  if (isInvalid) {
352
- src = "";
347
+ el.removeAttribute("src");
348
+ return;
353
349
  }
354
350
  const isRelative = !/^https?:\/\//i.test(decodedSrc) && !decodedSrc.startsWith("/");
355
351
  if (isRelative) {
356
- src = "";
352
+ el.removeAttribute("src");
353
+ return;
357
354
  }
358
- ["onerror", "dynsrc", "lowsrc", "width", "height"].forEach((attr) => el.removeAttribute(attr));
359
355
  el.setAttribute("itemprop", "image");
360
356
  const isLCP = state && !state.firstImageFound;
361
357
  if (isLCP) {
@@ -370,14 +366,17 @@ function img(el, webp, state) {
370
366
  const shouldReplace = !cls.includes("no-replace");
371
367
  const hasAlreadyProxied = src.startsWith("https://images.ecency.com");
372
368
  if (shouldReplace && !hasAlreadyProxied) {
373
- const proxified = proxifyImageSrc(src, 0, 0, webp ? "webp" : "match");
374
- el.setAttribute("src", proxified);
369
+ const proxified = proxifyImageSrc(decodedSrc);
370
+ if (proxified) {
371
+ el.setAttribute("src", proxified);
372
+ }
375
373
  }
376
374
  }
377
- function createImageHTML(src, isLCP, webp) {
375
+ function createImageHTML(src, isLCP) {
376
+ const proxified = proxifyImageSrc(src);
377
+ if (!proxified) return "";
378
378
  const loading = isLCP ? "eager" : "lazy";
379
379
  const fetch = isLCP ? 'fetchpriority="high"' : 'decoding="async"';
380
- const proxified = proxifyImageSrc(src, 0, 0, webp ? "webp" : "match");
381
380
  return `<img
382
381
  class="markdown-img-link"
383
382
  src="${proxified}"
@@ -429,7 +428,7 @@ var addLineBreakBeforePostLink = (el, forApp, isInline) => {
429
428
  el.parentNode.insertBefore(br, el);
430
429
  }
431
430
  };
432
- function a(el, forApp, webp, parentDomain = "ecency.com", seoContext) {
431
+ function a(el, forApp, parentDomain = "ecency.com", seoContext) {
433
432
  if (!el || !el.parentNode) {
434
433
  return;
435
434
  }
@@ -447,7 +446,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com", seoContext) {
447
446
  }
448
447
  if (href.match(IMG_REGEX) && href.trim().replace(/&amp;/g, "&") === getSerializedInnerHTML(el).trim().replace(/&amp;/g, "&")) {
449
448
  const isLCP = false;
450
- const imgHTML = createImageHTML(href, isLCP, webp);
449
+ const imgHTML = createImageHTML(href, isLCP);
451
450
  const doc = DOMParser.parseFromString(imgHTML, "text/html");
452
451
  const replaceNode = doc.body?.firstChild || doc.firstChild;
453
452
  if (replaceNode) {
@@ -769,7 +768,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com", seoContext) {
769
768
  el.setAttribute("class", "markdown-video-link markdown-video-link-youtube");
770
769
  el.removeAttribute("href");
771
770
  const vid = match[1];
772
- const thumbnail = proxifyImageSrc(`https://img.youtube.com/vi/${vid.split("?")[0]}/hqdefault.jpg`, 0, 0, webp ? "webp" : "match");
771
+ const thumbnail = proxifyImageSrc(`https://img.youtube.com/vi/${vid.split("?")[0]}/hqdefault.jpg`, 0, 0, "match");
773
772
  const embedSrc = `https://www.youtube.com/embed/${vid}?autoplay=1`;
774
773
  el.textContent = "";
775
774
  el.setAttribute("data-embed-src", embedSrc);
@@ -865,7 +864,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com", seoContext) {
865
864
  if (imgEls.length === 1) {
866
865
  const src = imgEls[0].getAttribute("src");
867
866
  if (src) {
868
- const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, webp ? "webp" : "match");
867
+ const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, "match");
869
868
  const thumbImg = el.ownerDocument.createElement("img");
870
869
  thumbImg.setAttribute("class", "no-replace video-thumbnail");
871
870
  thumbImg.setAttribute("itemprop", "thumbnailUrl");
@@ -900,7 +899,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com", seoContext) {
900
899
  const imgEls2 = el.getElementsByTagName("img");
901
900
  if (imgEls2.length === 1 || el.textContent.trim() === href) {
902
901
  if ((match[1] || match[2]) && match[3]) {
903
- const videoHref = `https://play.3speak.tv/watch?v=${match[3]}&mode=iframe`;
902
+ const videoHref = `https://play.3speak.tv/embed?v=${match[3]}&mode=iframe`;
904
903
  el.setAttribute("class", "markdown-video-link markdown-video-link-speak");
905
904
  el.removeAttribute("href");
906
905
  el.setAttribute("data-embed-src", videoHref);
@@ -910,7 +909,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com", seoContext) {
910
909
  if (imgEls2.length === 1) {
911
910
  const src = imgEls2[0].getAttribute("src");
912
911
  if (src) {
913
- const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, webp ? "webp" : "match");
912
+ const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, "match");
914
913
  const thumbImg = el.ownerDocument.createElement("img");
915
914
  thumbImg.setAttribute("class", "no-replace video-thumbnail");
916
915
  thumbImg.setAttribute("itemprop", "thumbnailUrl");
@@ -1033,7 +1032,8 @@ function iframe(el, parentDomain = "ecency.com") {
1033
1032
  return;
1034
1033
  }
1035
1034
  if (src.match(SPEAK_EMBED_REGEX)) {
1036
- let normalizedSrc = src.replace(/3speak\.[a-z]+/i, "play.3speak.tv");
1035
+ let normalizedSrc = src.replace(/(?:play\.)?3speak\.[a-z]+/i, "play.3speak.tv");
1036
+ normalizedSrc = normalizedSrc.replace(/\/watch\?/, "/embed?");
1037
1037
  const hasMode = /[?&]mode=/.test(normalizedSrc);
1038
1038
  if (!hasMode) {
1039
1039
  normalizedSrc = `${normalizedSrc}&mode=iframe`;
@@ -1041,6 +1041,7 @@ function iframe(el, parentDomain = "ecency.com") {
1041
1041
  const hasAutoplay = /[?&]autoplay=/.test(normalizedSrc);
1042
1042
  const s = hasAutoplay ? normalizedSrc : `${normalizedSrc}&autoplay=true`;
1043
1043
  el.setAttribute("src", s);
1044
+ el.setAttribute("class", "speak-iframe");
1044
1045
  return;
1045
1046
  }
1046
1047
  if (src.match(SPOTIFY_EMBED_REGEX)) {
@@ -1149,7 +1150,7 @@ function p(el) {
1149
1150
  }
1150
1151
 
1151
1152
  // src/methods/linkify.method.ts
1152
- function linkify(content, forApp, webp) {
1153
+ function linkify(content, forApp) {
1153
1154
  content = content.replace(/(^|\s|>)(#[-a-z\d]+)/gi, (tag) => {
1154
1155
  if (/#[\d]+$/.test(tag)) return tag;
1155
1156
  const preceding = /^\s|>/.test(tag) ? tag[0] : "";
@@ -1191,21 +1192,21 @@ function linkify(content, forApp, webp) {
1191
1192
  content = content.replace(IMG_REGEX, (imglink) => {
1192
1193
  const isLCP = !firstImageUsed;
1193
1194
  firstImageUsed = true;
1194
- return createImageHTML(imglink, isLCP, webp);
1195
+ return createImageHTML(imglink, isLCP);
1195
1196
  });
1196
1197
  return content;
1197
1198
  }
1198
1199
 
1199
1200
  // src/methods/text.method.ts
1200
- function text(node, forApp, webp) {
1201
+ function text(node, forApp) {
1201
1202
  if (!node || !node.parentNode) {
1202
1203
  return;
1203
1204
  }
1204
- if (node.parentNode && ["a", "code"].includes(node.parentNode.nodeName.toLowerCase())) {
1205
+ if (["a", "code"].includes(node.parentNode.nodeName.toLowerCase())) {
1205
1206
  return;
1206
1207
  }
1207
1208
  const nodeValue = node.nodeValue || "";
1208
- const linkified = linkify(nodeValue, forApp, webp);
1209
+ const linkified = linkify(nodeValue, forApp);
1209
1210
  if (linkified !== nodeValue) {
1210
1211
  const doc = DOMParser.parseFromString(
1211
1212
  `<span class="wr">${linkified}</span>`,
@@ -1220,7 +1221,7 @@ function text(node, forApp, webp) {
1220
1221
  }
1221
1222
  if (nodeValue.match(IMG_REGEX)) {
1222
1223
  const isLCP = false;
1223
- const imageHTML = createImageHTML(nodeValue, isLCP, webp);
1224
+ const imageHTML = createImageHTML(nodeValue, isLCP);
1224
1225
  const doc = DOMParser.parseFromString(imageHTML, "text/html");
1225
1226
  const replaceNode = doc.body?.firstChild || doc.firstChild;
1226
1227
  if (replaceNode) {
@@ -1232,7 +1233,7 @@ function text(node, forApp, webp) {
1232
1233
  const e = YOUTUBE_REGEX.exec(nodeValue);
1233
1234
  if (e && e[1]) {
1234
1235
  const vid = e[1];
1235
- const thumbnail = proxifyImageSrc(`https://img.youtube.com/vi/${vid.split("?")[0]}/hqdefault.jpg`, 0, 0, webp ? "webp" : "match");
1236
+ const thumbnail = proxifyImageSrc(`https://img.youtube.com/vi/${vid.split("?")[0]}/hqdefault.jpg`, 0, 0, "match");
1236
1237
  const embedSrc = `https://www.youtube.com/embed/${vid}?autoplay=1`;
1237
1238
  const startTime = extractYtStartTime(nodeValue);
1238
1239
  const container = node.ownerDocument.createElement("p");
@@ -1278,7 +1279,7 @@ function text(node, forApp, webp) {
1278
1279
  }
1279
1280
 
1280
1281
  // src/methods/traverse.method.ts
1281
- function traverse(node, forApp, depth = 0, webp = false, state = { firstImageFound: false }, parentDomain = "ecency.com", seoContext) {
1282
+ function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, parentDomain = "ecency.com", seoContext) {
1282
1283
  if (!node || !node.childNodes) {
1283
1284
  return;
1284
1285
  }
@@ -1286,23 +1287,23 @@ function traverse(node, forApp, depth = 0, webp = false, state = { firstImageFou
1286
1287
  const child = node.childNodes[i];
1287
1288
  if (!child) return;
1288
1289
  if (child.nodeName.toLowerCase() === "a") {
1289
- a(child, forApp, webp, parentDomain, seoContext);
1290
+ a(child, forApp, parentDomain, seoContext);
1290
1291
  }
1291
1292
  if (child.nodeName.toLowerCase() === "iframe") {
1292
1293
  iframe(child, parentDomain);
1293
1294
  }
1294
1295
  if (child.nodeName === "#text") {
1295
- text(child, forApp, webp);
1296
+ text(child, forApp);
1296
1297
  }
1297
1298
  if (child.nodeName.toLowerCase() === "img") {
1298
- img(child, webp, state);
1299
+ img(child, state);
1299
1300
  }
1300
1301
  if (child.nodeName.toLowerCase() === "p") {
1301
1302
  p(child);
1302
1303
  }
1303
1304
  const currentChild = node.childNodes[i];
1304
1305
  if (currentChild) {
1305
- traverse(currentChild, forApp, depth + 1, webp, state, parentDomain, seoContext);
1306
+ traverse(currentChild, forApp, depth + 1, state, parentDomain, seoContext);
1306
1307
  }
1307
1308
  });
1308
1309
  }
@@ -1353,7 +1354,7 @@ function fixBlockLevelTagsInParagraphs(html) {
1353
1354
  html = html.replace(/<p><br>\s*<\/p>/g, "");
1354
1355
  return html;
1355
1356
  }
1356
- function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com", seoContext) {
1357
+ function markdownToHTML(input, forApp, parentDomain = "ecency.com", seoContext) {
1357
1358
  input = input.replace(new RegExp("https://leofinance.io/threads/view/", "g"), "/@");
1358
1359
  input = input.replace(new RegExp("https://leofinance.io/posts/", "g"), "/@");
1359
1360
  input = input.replace(new RegExp("https://leofinance.io/threads/", "g"), "/@");
@@ -1413,7 +1414,7 @@ function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com", seoCon
1413
1414
  output = md.render(input);
1414
1415
  output = fixBlockLevelTagsInParagraphs(output);
1415
1416
  const doc = DOMParser.parseFromString(`<body id="root">${removeDuplicateAttributes(output)}</body>`, "text/html");
1416
- traverse(doc, forApp, 0, webp, { firstImageFound: false }, parentDomain, seoContext);
1417
+ traverse(doc, forApp, 0, { firstImageFound: false }, parentDomain, seoContext);
1417
1418
  output = serializer.serializeToString(doc);
1418
1419
  } catch (error) {
1419
1420
  try {
@@ -1426,7 +1427,7 @@ function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com", seoCon
1426
1427
  });
1427
1428
  const repairedHtml = domSerializer(dom.children);
1428
1429
  const doc = DOMParser.parseFromString(`<body id="root">${removeDuplicateAttributes(repairedHtml)}</body>`, "text/html");
1429
- traverse(doc, forApp, 0, webp, { firstImageFound: false }, parentDomain, seoContext);
1430
+ traverse(doc, forApp, 0, { firstImageFound: false }, parentDomain, seoContext);
1430
1431
  output = serializer.serializeToString(doc);
1431
1432
  } catch (fallbackError) {
1432
1433
  const escapedContent = he2.encode(output || md.render(input));
@@ -1454,18 +1455,18 @@ function cacheSet(key, value) {
1454
1455
  }
1455
1456
 
1456
1457
  // src/markdown-2-html.ts
1457
- function markdown2Html(obj, forApp = true, webp = false, parentDomain = "ecency.com", seoContext) {
1458
+ function markdown2Html(obj, forApp = true, _webp = false, parentDomain = "ecency.com", seoContext) {
1458
1459
  if (typeof obj === "string") {
1459
1460
  const cleanedStr = cleanReply(obj);
1460
- return markdownToHTML(cleanedStr, forApp, webp, parentDomain, seoContext);
1461
+ return markdownToHTML(cleanedStr, forApp, parentDomain, seoContext);
1461
1462
  }
1462
- const key = `${makeEntryCacheKey(obj)}-md${webp ? "-webp" : ""}-${forApp ? "app" : "site"}-${parentDomain}${seoContext ? `-seo${seoContext.authorReputation ?? ""}-${seoContext.postPayout ?? ""}` : ""}`;
1463
+ const key = `${makeEntryCacheKey(obj)}-md-${forApp ? "app" : "site"}-${parentDomain}${seoContext ? `-seo${seoContext.authorReputation ?? ""}-${seoContext.postPayout ?? ""}` : ""}`;
1463
1464
  const item = cacheGet(key);
1464
1465
  if (item) {
1465
1466
  return item;
1466
1467
  }
1467
1468
  const cleanBody = cleanReply(obj.body);
1468
- const res = markdownToHTML(cleanBody, forApp, webp, parentDomain, seoContext);
1469
+ const res = markdownToHTML(cleanBody, forApp, parentDomain, seoContext);
1469
1470
  cacheSet(key, res);
1470
1471
  return res;
1471
1472
  }