@ecency/render-helper 2.4.14 → 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.
@@ -107,8 +107,8 @@ var LBRY_REGEX = /^(https?:)?\/\/lbry.tv\/\$\/embed\/[^?#]+(?:$|[?#])/i;
107
107
  var ODYSEE_REGEX = /^(https?:)?\/\/odysee\.com\/(?:\$|%24)\/embed\/[^/?#]+(?:$|[?#])/i;
108
108
  var SKATEHIVE_IPFS_REGEX = /^https?:\/\/ipfs\.skatehive\.app\/ipfs\/([^/?#]+)/i;
109
109
  var ARCH_REGEX = /^(https?:)?\/\/archive.org\/embed\/[^/?#]+(?:$|[?#])/i;
110
- var SPEAK_REGEX = /(?:https?:\/\/(?:(?:play\.)?3speak.([a-z]+)\/watch\?v=)|(?:(?:play\.)?3speak.([a-z]+)\/embed\?v=))([A-Za-z0-9\_\-\.\/]+)(&.*)?/i;
111
- var SPEAK_EMBED_REGEX = /^(https?:)?\/\/(?:play\.)?3speak.([a-z]+)\/embed\?[^/]+$/i;
110
+ var SPEAK_REGEX = /(?:https?:\/\/(?:(?:play\.)?3speak\.([a-z]+)\/watch\?v=)|(?:(?:play\.)?3speak\.([a-z]+)\/embed\?v=))([A-Za-z0-9_\-\.\/]+)(&.*)?/i;
111
+ var SPEAK_EMBED_REGEX = /^(https?:)?\/\/(?:play\.)?3speak\.([a-z]+)\/(?:embed|watch)\?.+$/i;
112
112
  var TWITTER_REGEX = /(?:https?:\/\/(?:(?:twitter\.com\/(.*?)\/status\/(.*))))/gi;
113
113
  var SPOTIFY_REGEX = /^https:\/\/open\.spotify\.com\/playlist\/(.*)?$/gi;
114
114
  var RUMBLE_REGEX = /^https:\/\/rumble.com\/embed\/([a-zA-Z0-9-]+)\/\?pub=\w+/;
@@ -316,15 +316,13 @@ function sanitizeHtml(html) {
316
316
  });
317
317
  }
318
318
  var proxyBase = "https://images.ecency.com";
319
- var fileExtension = true;
320
319
  function setProxyBase(p2) {
321
320
  proxyBase = p2;
322
- fileExtension = proxyBase == "https://images.ecency.com";
323
321
  }
324
322
  function extractPHash(url) {
325
323
  if (url.startsWith(`${proxyBase}/p/`)) {
326
324
  const [hash] = url.split("/p/")[1].split("?");
327
- return hash.replace(/.webp/, "").replace(/.png/, "");
325
+ return hash.replace(/\.(webp|png)$/, "");
328
326
  }
329
327
  return null;
330
328
  }
@@ -339,7 +337,7 @@ function getLatestUrl(str) {
339
337
  const [last] = [...str.replace(/https?:\/\//g, "\n$&").trim().split("\n")].reverse();
340
338
  return last;
341
339
  }
342
- function proxifyImageSrc(url, width = 0, height = 0, format = "match") {
340
+ function proxifyImageSrc(url, width = 0, height = 0, _format = "match") {
343
341
  if (!url || typeof url !== "string" || !isValidUrl(url)) {
344
342
  return "";
345
343
  }
@@ -352,7 +350,7 @@ function proxifyImageSrc(url, width = 0, height = 0, format = "match") {
352
350
  const realUrl = getLatestUrl(url);
353
351
  const pHash = extractPHash(realUrl);
354
352
  const options = {
355
- format,
353
+ format: "match",
356
354
  mode: "fit"
357
355
  };
358
356
  if (width > 0) {
@@ -363,31 +361,29 @@ function proxifyImageSrc(url, width = 0, height = 0, format = "match") {
363
361
  }
364
362
  const qs = querystring__default.default.stringify(options);
365
363
  if (pHash) {
366
- if (fileExtension) {
367
- return `${proxyBase}/p/${pHash}${format === "webp" ? ".webp" : ".png"}?${qs}`;
368
- } else {
369
- return `${proxyBase}/p/${pHash}?${qs}`;
370
- }
364
+ return `${proxyBase}/p/${pHash}?${qs}`;
371
365
  }
372
366
  const b58url = multihash__default.default.toB58String(Buffer.from(realUrl.toString()));
373
- return `${proxyBase}/p/${b58url}${fileExtension ? format === "webp" ? ".webp" : ".png" : ""}?${qs}`;
367
+ return `${proxyBase}/p/${b58url}?${qs}`;
374
368
  }
375
369
 
376
370
  // src/methods/img.method.ts
377
- function img(el, webp, state) {
378
- let src = el.getAttribute("src") || "";
371
+ function img(el, state) {
372
+ const src = el.getAttribute("src") || "";
379
373
  const decodedSrc = decodeURIComponent(
380
374
  src.replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(dec)).replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
381
375
  ).trim();
376
+ ["onerror", "dynsrc", "lowsrc", "width", "height"].forEach((attr) => el.removeAttribute(attr));
382
377
  const isInvalid = !src || decodedSrc.startsWith("javascript") || decodedSrc.startsWith("vbscript") || decodedSrc === "x";
383
378
  if (isInvalid) {
384
- src = "";
379
+ el.removeAttribute("src");
380
+ return;
385
381
  }
386
382
  const isRelative = !/^https?:\/\//i.test(decodedSrc) && !decodedSrc.startsWith("/");
387
383
  if (isRelative) {
388
- src = "";
384
+ el.removeAttribute("src");
385
+ return;
389
386
  }
390
- ["onerror", "dynsrc", "lowsrc", "width", "height"].forEach((attr) => el.removeAttribute(attr));
391
387
  el.setAttribute("itemprop", "image");
392
388
  const isLCP = state && !state.firstImageFound;
393
389
  if (isLCP) {
@@ -402,14 +398,17 @@ function img(el, webp, state) {
402
398
  const shouldReplace = !cls.includes("no-replace");
403
399
  const hasAlreadyProxied = src.startsWith("https://images.ecency.com");
404
400
  if (shouldReplace && !hasAlreadyProxied) {
405
- const proxified = proxifyImageSrc(src, 0, 0, webp ? "webp" : "match");
406
- el.setAttribute("src", proxified);
401
+ const proxified = proxifyImageSrc(decodedSrc);
402
+ if (proxified) {
403
+ el.setAttribute("src", proxified);
404
+ }
407
405
  }
408
406
  }
409
- function createImageHTML(src, isLCP, webp) {
407
+ function createImageHTML(src, isLCP) {
408
+ const proxified = proxifyImageSrc(src);
409
+ if (!proxified) return "";
410
410
  const loading = isLCP ? "eager" : "lazy";
411
411
  const fetch = isLCP ? 'fetchpriority="high"' : 'decoding="async"';
412
- const proxified = proxifyImageSrc(src, 0, 0, webp ? "webp" : "match");
413
412
  return `<img
414
413
  class="markdown-img-link"
415
414
  src="${proxified}"
@@ -420,6 +419,14 @@ function createImageHTML(src, isLCP, webp) {
420
419
  }
421
420
 
422
421
  // src/methods/a.method.ts
422
+ var NOFOLLOW_REPUTATION_THRESHOLD = 40;
423
+ var FOLLOW_PAYOUT_THRESHOLD = 5;
424
+ function getExternalLinkRel(seoContext) {
425
+ if (seoContext?.authorReputation !== void 0 && seoContext?.postPayout !== void 0 && seoContext.authorReputation >= NOFOLLOW_REPUTATION_THRESHOLD && seoContext.postPayout > FOLLOW_PAYOUT_THRESHOLD) {
426
+ return "noopener";
427
+ }
428
+ return "nofollow ugc noopener";
429
+ }
423
430
  var normalizeValue = (value) => value ? value.trim() : "";
424
431
  var matchesHref = (href, value) => {
425
432
  const normalizedHref = normalizeValue(href);
@@ -453,7 +460,7 @@ var addLineBreakBeforePostLink = (el, forApp, isInline) => {
453
460
  el.parentNode.insertBefore(br, el);
454
461
  }
455
462
  };
456
- function a(el, forApp, webp, parentDomain = "ecency.com") {
463
+ function a(el, forApp, parentDomain = "ecency.com", seoContext) {
457
464
  if (!el || !el.parentNode) {
458
465
  return;
459
466
  }
@@ -471,7 +478,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
471
478
  }
472
479
  if (href.match(IMG_REGEX) && href.trim().replace(/&amp;/g, "&") === getSerializedInnerHTML(el).trim().replace(/&amp;/g, "&")) {
473
480
  const isLCP = false;
474
- const imgHTML = createImageHTML(href, isLCP, webp);
481
+ const imgHTML = createImageHTML(href, isLCP);
475
482
  const doc = DOMParser.parseFromString(imgHTML, "text/html");
476
483
  const replaceNode = doc.body?.firstChild || doc.firstChild;
477
484
  if (replaceNode) {
@@ -793,7 +800,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
793
800
  el.setAttribute("class", "markdown-video-link markdown-video-link-youtube");
794
801
  el.removeAttribute("href");
795
802
  const vid = match[1];
796
- const thumbnail = proxifyImageSrc(`https://img.youtube.com/vi/${vid.split("?")[0]}/hqdefault.jpg`, 0, 0, webp ? "webp" : "match");
803
+ const thumbnail = proxifyImageSrc(`https://img.youtube.com/vi/${vid.split("?")[0]}/hqdefault.jpg`, 0, 0, "match");
797
804
  const embedSrc = `https://www.youtube.com/embed/${vid}?autoplay=1`;
798
805
  el.textContent = "";
799
806
  el.setAttribute("data-embed-src", embedSrc);
@@ -889,7 +896,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
889
896
  if (imgEls.length === 1) {
890
897
  const src = imgEls[0].getAttribute("src");
891
898
  if (src) {
892
- const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, webp ? "webp" : "match");
899
+ const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, "match");
893
900
  const thumbImg = el.ownerDocument.createElement("img");
894
901
  thumbImg.setAttribute("class", "no-replace video-thumbnail");
895
902
  thumbImg.setAttribute("itemprop", "thumbnailUrl");
@@ -924,7 +931,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
924
931
  const imgEls2 = el.getElementsByTagName("img");
925
932
  if (imgEls2.length === 1 || el.textContent.trim() === href) {
926
933
  if ((match[1] || match[2]) && match[3]) {
927
- const videoHref = `https://play.3speak.tv/watch?v=${match[3]}&mode=iframe`;
934
+ const videoHref = `https://play.3speak.tv/embed?v=${match[3]}&mode=iframe`;
928
935
  el.setAttribute("class", "markdown-video-link markdown-video-link-speak");
929
936
  el.removeAttribute("href");
930
937
  el.setAttribute("data-embed-src", videoHref);
@@ -934,7 +941,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
934
941
  if (imgEls2.length === 1) {
935
942
  const src = imgEls2[0].getAttribute("src");
936
943
  if (src) {
937
- const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, webp ? "webp" : "match");
944
+ const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, "match");
938
945
  const thumbImg = el.ownerDocument.createElement("img");
939
946
  thumbImg.setAttribute("class", "no-replace video-thumbnail");
940
947
  thumbImg.setAttribute("itemprop", "thumbnailUrl");
@@ -1013,7 +1020,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
1013
1020
  el.setAttribute("class", "markdown-internal-link");
1014
1021
  } else {
1015
1022
  el.setAttribute("target", "_blank");
1016
- el.setAttribute("rel", "noopener");
1023
+ el.setAttribute("rel", getExternalLinkRel(seoContext));
1017
1024
  }
1018
1025
  el.setAttribute("href", href);
1019
1026
  }
@@ -1057,7 +1064,8 @@ function iframe(el, parentDomain = "ecency.com") {
1057
1064
  return;
1058
1065
  }
1059
1066
  if (src.match(SPEAK_EMBED_REGEX)) {
1060
- let normalizedSrc = src.replace(/3speak\.[a-z]+/i, "play.3speak.tv");
1067
+ let normalizedSrc = src.replace(/(?:play\.)?3speak\.[a-z]+/i, "play.3speak.tv");
1068
+ normalizedSrc = normalizedSrc.replace(/\/watch\?/, "/embed?");
1061
1069
  const hasMode = /[?&]mode=/.test(normalizedSrc);
1062
1070
  if (!hasMode) {
1063
1071
  normalizedSrc = `${normalizedSrc}&mode=iframe`;
@@ -1065,6 +1073,7 @@ function iframe(el, parentDomain = "ecency.com") {
1065
1073
  const hasAutoplay = /[?&]autoplay=/.test(normalizedSrc);
1066
1074
  const s = hasAutoplay ? normalizedSrc : `${normalizedSrc}&autoplay=true`;
1067
1075
  el.setAttribute("src", s);
1076
+ el.setAttribute("class", "speak-iframe");
1068
1077
  return;
1069
1078
  }
1070
1079
  if (src.match(SPOTIFY_EMBED_REGEX)) {
@@ -1173,7 +1182,7 @@ function p(el) {
1173
1182
  }
1174
1183
 
1175
1184
  // src/methods/linkify.method.ts
1176
- function linkify(content, forApp, webp) {
1185
+ function linkify(content, forApp) {
1177
1186
  content = content.replace(/(^|\s|>)(#[-a-z\d]+)/gi, (tag) => {
1178
1187
  if (/#[\d]+$/.test(tag)) return tag;
1179
1188
  const preceding = /^\s|>/.test(tag) ? tag[0] : "";
@@ -1215,21 +1224,21 @@ function linkify(content, forApp, webp) {
1215
1224
  content = content.replace(IMG_REGEX, (imglink) => {
1216
1225
  const isLCP = !firstImageUsed;
1217
1226
  firstImageUsed = true;
1218
- return createImageHTML(imglink, isLCP, webp);
1227
+ return createImageHTML(imglink, isLCP);
1219
1228
  });
1220
1229
  return content;
1221
1230
  }
1222
1231
 
1223
1232
  // src/methods/text.method.ts
1224
- function text(node, forApp, webp) {
1233
+ function text(node, forApp) {
1225
1234
  if (!node || !node.parentNode) {
1226
1235
  return;
1227
1236
  }
1228
- if (node.parentNode && ["a", "code"].includes(node.parentNode.nodeName.toLowerCase())) {
1237
+ if (["a", "code"].includes(node.parentNode.nodeName.toLowerCase())) {
1229
1238
  return;
1230
1239
  }
1231
1240
  const nodeValue = node.nodeValue || "";
1232
- const linkified = linkify(nodeValue, forApp, webp);
1241
+ const linkified = linkify(nodeValue, forApp);
1233
1242
  if (linkified !== nodeValue) {
1234
1243
  const doc = DOMParser.parseFromString(
1235
1244
  `<span class="wr">${linkified}</span>`,
@@ -1244,7 +1253,7 @@ function text(node, forApp, webp) {
1244
1253
  }
1245
1254
  if (nodeValue.match(IMG_REGEX)) {
1246
1255
  const isLCP = false;
1247
- const imageHTML = createImageHTML(nodeValue, isLCP, webp);
1256
+ const imageHTML = createImageHTML(nodeValue, isLCP);
1248
1257
  const doc = DOMParser.parseFromString(imageHTML, "text/html");
1249
1258
  const replaceNode = doc.body?.firstChild || doc.firstChild;
1250
1259
  if (replaceNode) {
@@ -1256,7 +1265,7 @@ function text(node, forApp, webp) {
1256
1265
  const e = YOUTUBE_REGEX.exec(nodeValue);
1257
1266
  if (e && e[1]) {
1258
1267
  const vid = e[1];
1259
- const thumbnail = proxifyImageSrc(`https://img.youtube.com/vi/${vid.split("?")[0]}/hqdefault.jpg`, 0, 0, webp ? "webp" : "match");
1268
+ const thumbnail = proxifyImageSrc(`https://img.youtube.com/vi/${vid.split("?")[0]}/hqdefault.jpg`, 0, 0, "match");
1260
1269
  const embedSrc = `https://www.youtube.com/embed/${vid}?autoplay=1`;
1261
1270
  const startTime = extractYtStartTime(nodeValue);
1262
1271
  const container = node.ownerDocument.createElement("p");
@@ -1302,7 +1311,7 @@ function text(node, forApp, webp) {
1302
1311
  }
1303
1312
 
1304
1313
  // src/methods/traverse.method.ts
1305
- function traverse(node, forApp, depth = 0, webp = false, state = { firstImageFound: false }, parentDomain = "ecency.com") {
1314
+ function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, parentDomain = "ecency.com", seoContext) {
1306
1315
  if (!node || !node.childNodes) {
1307
1316
  return;
1308
1317
  }
@@ -1310,23 +1319,23 @@ function traverse(node, forApp, depth = 0, webp = false, state = { firstImageFou
1310
1319
  const child = node.childNodes[i];
1311
1320
  if (!child) return;
1312
1321
  if (child.nodeName.toLowerCase() === "a") {
1313
- a(child, forApp, webp, parentDomain);
1322
+ a(child, forApp, parentDomain, seoContext);
1314
1323
  }
1315
1324
  if (child.nodeName.toLowerCase() === "iframe") {
1316
1325
  iframe(child, parentDomain);
1317
1326
  }
1318
1327
  if (child.nodeName === "#text") {
1319
- text(child, forApp, webp);
1328
+ text(child, forApp);
1320
1329
  }
1321
1330
  if (child.nodeName.toLowerCase() === "img") {
1322
- img(child, webp, state);
1331
+ img(child, state);
1323
1332
  }
1324
1333
  if (child.nodeName.toLowerCase() === "p") {
1325
1334
  p(child);
1326
1335
  }
1327
1336
  const currentChild = node.childNodes[i];
1328
1337
  if (currentChild) {
1329
- traverse(currentChild, forApp, depth + 1, webp, state, parentDomain);
1338
+ traverse(currentChild, forApp, depth + 1, state, parentDomain, seoContext);
1330
1339
  }
1331
1340
  });
1332
1341
  }
@@ -1377,7 +1386,7 @@ function fixBlockLevelTagsInParagraphs(html) {
1377
1386
  html = html.replace(/<p><br>\s*<\/p>/g, "");
1378
1387
  return html;
1379
1388
  }
1380
- function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com") {
1389
+ function markdownToHTML(input, forApp, parentDomain = "ecency.com", seoContext) {
1381
1390
  input = input.replace(new RegExp("https://leofinance.io/threads/view/", "g"), "/@");
1382
1391
  input = input.replace(new RegExp("https://leofinance.io/posts/", "g"), "/@");
1383
1392
  input = input.replace(new RegExp("https://leofinance.io/threads/", "g"), "/@");
@@ -1437,7 +1446,7 @@ function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com") {
1437
1446
  output = md.render(input);
1438
1447
  output = fixBlockLevelTagsInParagraphs(output);
1439
1448
  const doc = DOMParser.parseFromString(`<body id="root">${removeDuplicateAttributes(output)}</body>`, "text/html");
1440
- traverse(doc, forApp, 0, webp, { firstImageFound: false }, parentDomain);
1449
+ traverse(doc, forApp, 0, { firstImageFound: false }, parentDomain, seoContext);
1441
1450
  output = serializer.serializeToString(doc);
1442
1451
  } catch (error) {
1443
1452
  try {
@@ -1450,7 +1459,7 @@ function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com") {
1450
1459
  });
1451
1460
  const repairedHtml = domSerializer(dom.children);
1452
1461
  const doc = DOMParser.parseFromString(`<body id="root">${removeDuplicateAttributes(repairedHtml)}</body>`, "text/html");
1453
- traverse(doc, forApp, 0, webp, { firstImageFound: false }, parentDomain);
1462
+ traverse(doc, forApp, 0, { firstImageFound: false }, parentDomain, seoContext);
1454
1463
  output = serializer.serializeToString(doc);
1455
1464
  } catch (fallbackError) {
1456
1465
  const escapedContent = he2__default.default.encode(output || md.render(input));
@@ -1478,18 +1487,18 @@ function cacheSet(key, value) {
1478
1487
  }
1479
1488
 
1480
1489
  // src/markdown-2-html.ts
1481
- function markdown2Html(obj, forApp = true, webp = false, parentDomain = "ecency.com") {
1490
+ function markdown2Html(obj, forApp = true, _webp = false, parentDomain = "ecency.com", seoContext) {
1482
1491
  if (typeof obj === "string") {
1483
1492
  const cleanedStr = cleanReply(obj);
1484
- return markdownToHTML(cleanedStr, forApp, webp, parentDomain);
1493
+ return markdownToHTML(cleanedStr, forApp, parentDomain, seoContext);
1485
1494
  }
1486
- const key = `${makeEntryCacheKey(obj)}-md${webp ? "-webp" : ""}-${forApp ? "app" : "site"}-${parentDomain}`;
1495
+ const key = `${makeEntryCacheKey(obj)}-md-${forApp ? "app" : "site"}-${parentDomain}${seoContext ? `-seo${seoContext.authorReputation ?? ""}-${seoContext.postPayout ?? ""}` : ""}`;
1487
1496
  const item = cacheGet(key);
1488
1497
  if (item) {
1489
1498
  return item;
1490
1499
  }
1491
1500
  const cleanBody = cleanReply(obj.body);
1492
- const res = markdownToHTML(cleanBody, forApp, webp, parentDomain);
1501
+ const res = markdownToHTML(cleanBody, forApp, parentDomain, seoContext);
1493
1502
  cacheSet(key, res);
1494
1503
  return res;
1495
1504
  }