@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.
@@ -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+/;
@@ -287,15 +287,13 @@ function sanitizeHtml(html) {
287
287
  });
288
288
  }
289
289
  var proxyBase = "https://images.ecency.com";
290
- var fileExtension = true;
291
290
  function setProxyBase(p2) {
292
291
  proxyBase = p2;
293
- fileExtension = proxyBase == "https://images.ecency.com";
294
292
  }
295
293
  function extractPHash(url) {
296
294
  if (url.startsWith(`${proxyBase}/p/`)) {
297
295
  const [hash] = url.split("/p/")[1].split("?");
298
- return hash.replace(/.webp/, "").replace(/.png/, "");
296
+ return hash.replace(/\.(webp|png)$/, "");
299
297
  }
300
298
  return null;
301
299
  }
@@ -310,7 +308,7 @@ function getLatestUrl(str) {
310
308
  const [last] = [...str.replace(/https?:\/\//g, "\n$&").trim().split("\n")].reverse();
311
309
  return last;
312
310
  }
313
- function proxifyImageSrc(url, width = 0, height = 0, format = "match") {
311
+ function proxifyImageSrc(url, width = 0, height = 0, _format = "match") {
314
312
  if (!url || typeof url !== "string" || !isValidUrl(url)) {
315
313
  return "";
316
314
  }
@@ -323,7 +321,7 @@ function proxifyImageSrc(url, width = 0, height = 0, format = "match") {
323
321
  const realUrl = getLatestUrl(url);
324
322
  const pHash = extractPHash(realUrl);
325
323
  const options = {
326
- format,
324
+ format: "match",
327
325
  mode: "fit"
328
326
  };
329
327
  if (width > 0) {
@@ -334,31 +332,29 @@ function proxifyImageSrc(url, width = 0, height = 0, format = "match") {
334
332
  }
335
333
  const qs = querystring.stringify(options);
336
334
  if (pHash) {
337
- if (fileExtension) {
338
- return `${proxyBase}/p/${pHash}${format === "webp" ? ".webp" : ".png"}?${qs}`;
339
- } else {
340
- return `${proxyBase}/p/${pHash}?${qs}`;
341
- }
335
+ return `${proxyBase}/p/${pHash}?${qs}`;
342
336
  }
343
337
  const b58url = multihash.toB58String(Buffer.from(realUrl.toString()));
344
- return `${proxyBase}/p/${b58url}${fileExtension ? format === "webp" ? ".webp" : ".png" : ""}?${qs}`;
338
+ return `${proxyBase}/p/${b58url}?${qs}`;
345
339
  }
346
340
 
347
341
  // src/methods/img.method.ts
348
- function img(el, webp, state) {
349
- let src = el.getAttribute("src") || "";
342
+ function img(el, state) {
343
+ const src = el.getAttribute("src") || "";
350
344
  const decodedSrc = decodeURIComponent(
351
345
  src.replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(dec)).replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
352
346
  ).trim();
347
+ ["onerror", "dynsrc", "lowsrc", "width", "height"].forEach((attr) => el.removeAttribute(attr));
353
348
  const isInvalid = !src || decodedSrc.startsWith("javascript") || decodedSrc.startsWith("vbscript") || decodedSrc === "x";
354
349
  if (isInvalid) {
355
- src = "";
350
+ el.removeAttribute("src");
351
+ return;
356
352
  }
357
353
  const isRelative = !/^https?:\/\//i.test(decodedSrc) && !decodedSrc.startsWith("/");
358
354
  if (isRelative) {
359
- src = "";
355
+ el.removeAttribute("src");
356
+ return;
360
357
  }
361
- ["onerror", "dynsrc", "lowsrc", "width", "height"].forEach((attr) => el.removeAttribute(attr));
362
358
  el.setAttribute("itemprop", "image");
363
359
  const isLCP = state && !state.firstImageFound;
364
360
  if (isLCP) {
@@ -373,14 +369,17 @@ function img(el, webp, state) {
373
369
  const shouldReplace = !cls.includes("no-replace");
374
370
  const hasAlreadyProxied = src.startsWith("https://images.ecency.com");
375
371
  if (shouldReplace && !hasAlreadyProxied) {
376
- const proxified = proxifyImageSrc(src, 0, 0, webp ? "webp" : "match");
377
- el.setAttribute("src", proxified);
372
+ const proxified = proxifyImageSrc(decodedSrc);
373
+ if (proxified) {
374
+ el.setAttribute("src", proxified);
375
+ }
378
376
  }
379
377
  }
380
- function createImageHTML(src, isLCP, webp) {
378
+ function createImageHTML(src, isLCP) {
379
+ const proxified = proxifyImageSrc(src);
380
+ if (!proxified) return "";
381
381
  const loading = isLCP ? "eager" : "lazy";
382
382
  const fetch = isLCP ? 'fetchpriority="high"' : 'decoding="async"';
383
- const proxified = proxifyImageSrc(src, 0, 0, webp ? "webp" : "match");
384
383
  return `<img
385
384
  class="markdown-img-link"
386
385
  src="${proxified}"
@@ -391,6 +390,14 @@ function createImageHTML(src, isLCP, webp) {
391
390
  }
392
391
 
393
392
  // src/methods/a.method.ts
393
+ var NOFOLLOW_REPUTATION_THRESHOLD = 40;
394
+ var FOLLOW_PAYOUT_THRESHOLD = 5;
395
+ function getExternalLinkRel(seoContext) {
396
+ if (seoContext?.authorReputation !== void 0 && seoContext?.postPayout !== void 0 && seoContext.authorReputation >= NOFOLLOW_REPUTATION_THRESHOLD && seoContext.postPayout > FOLLOW_PAYOUT_THRESHOLD) {
397
+ return "noopener";
398
+ }
399
+ return "nofollow ugc noopener";
400
+ }
394
401
  var normalizeValue = (value) => value ? value.trim() : "";
395
402
  var matchesHref = (href, value) => {
396
403
  const normalizedHref = normalizeValue(href);
@@ -424,7 +431,7 @@ var addLineBreakBeforePostLink = (el, forApp, isInline) => {
424
431
  el.parentNode.insertBefore(br, el);
425
432
  }
426
433
  };
427
- function a(el, forApp, webp, parentDomain = "ecency.com") {
434
+ function a(el, forApp, parentDomain = "ecency.com", seoContext) {
428
435
  if (!el || !el.parentNode) {
429
436
  return;
430
437
  }
@@ -442,7 +449,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
442
449
  }
443
450
  if (href.match(IMG_REGEX) && href.trim().replace(/&amp;/g, "&") === getSerializedInnerHTML(el).trim().replace(/&amp;/g, "&")) {
444
451
  const isLCP = false;
445
- const imgHTML = createImageHTML(href, isLCP, webp);
452
+ const imgHTML = createImageHTML(href, isLCP);
446
453
  const doc = DOMParser.parseFromString(imgHTML, "text/html");
447
454
  const replaceNode = doc.body?.firstChild || doc.firstChild;
448
455
  if (replaceNode) {
@@ -764,7 +771,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
764
771
  el.setAttribute("class", "markdown-video-link markdown-video-link-youtube");
765
772
  el.removeAttribute("href");
766
773
  const vid = match[1];
767
- const thumbnail = proxifyImageSrc(`https://img.youtube.com/vi/${vid.split("?")[0]}/hqdefault.jpg`, 0, 0, webp ? "webp" : "match");
774
+ const thumbnail = proxifyImageSrc(`https://img.youtube.com/vi/${vid.split("?")[0]}/hqdefault.jpg`, 0, 0, "match");
768
775
  const embedSrc = `https://www.youtube.com/embed/${vid}?autoplay=1`;
769
776
  el.textContent = "";
770
777
  el.setAttribute("data-embed-src", embedSrc);
@@ -860,7 +867,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
860
867
  if (imgEls.length === 1) {
861
868
  const src = imgEls[0].getAttribute("src");
862
869
  if (src) {
863
- const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, webp ? "webp" : "match");
870
+ const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, "match");
864
871
  const thumbImg = el.ownerDocument.createElement("img");
865
872
  thumbImg.setAttribute("class", "no-replace video-thumbnail");
866
873
  thumbImg.setAttribute("itemprop", "thumbnailUrl");
@@ -895,7 +902,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
895
902
  const imgEls2 = el.getElementsByTagName("img");
896
903
  if (imgEls2.length === 1 || el.textContent.trim() === href) {
897
904
  if ((match[1] || match[2]) && match[3]) {
898
- const videoHref = `https://play.3speak.tv/watch?v=${match[3]}&mode=iframe`;
905
+ const videoHref = `https://play.3speak.tv/embed?v=${match[3]}&mode=iframe`;
899
906
  el.setAttribute("class", "markdown-video-link markdown-video-link-speak");
900
907
  el.removeAttribute("href");
901
908
  el.setAttribute("data-embed-src", videoHref);
@@ -905,7 +912,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
905
912
  if (imgEls2.length === 1) {
906
913
  const src = imgEls2[0].getAttribute("src");
907
914
  if (src) {
908
- const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, webp ? "webp" : "match");
915
+ const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, "match");
909
916
  const thumbImg = el.ownerDocument.createElement("img");
910
917
  thumbImg.setAttribute("class", "no-replace video-thumbnail");
911
918
  thumbImg.setAttribute("itemprop", "thumbnailUrl");
@@ -984,7 +991,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
984
991
  el.setAttribute("class", "markdown-internal-link");
985
992
  } else {
986
993
  el.setAttribute("target", "_blank");
987
- el.setAttribute("rel", "noopener");
994
+ el.setAttribute("rel", getExternalLinkRel(seoContext));
988
995
  }
989
996
  el.setAttribute("href", href);
990
997
  }
@@ -1028,7 +1035,8 @@ function iframe(el, parentDomain = "ecency.com") {
1028
1035
  return;
1029
1036
  }
1030
1037
  if (src.match(SPEAK_EMBED_REGEX)) {
1031
- let normalizedSrc = src.replace(/3speak\.[a-z]+/i, "play.3speak.tv");
1038
+ let normalizedSrc = src.replace(/(?:play\.)?3speak\.[a-z]+/i, "play.3speak.tv");
1039
+ normalizedSrc = normalizedSrc.replace(/\/watch\?/, "/embed?");
1032
1040
  const hasMode = /[?&]mode=/.test(normalizedSrc);
1033
1041
  if (!hasMode) {
1034
1042
  normalizedSrc = `${normalizedSrc}&mode=iframe`;
@@ -1036,6 +1044,7 @@ function iframe(el, parentDomain = "ecency.com") {
1036
1044
  const hasAutoplay = /[?&]autoplay=/.test(normalizedSrc);
1037
1045
  const s = hasAutoplay ? normalizedSrc : `${normalizedSrc}&autoplay=true`;
1038
1046
  el.setAttribute("src", s);
1047
+ el.setAttribute("class", "speak-iframe");
1039
1048
  return;
1040
1049
  }
1041
1050
  if (src.match(SPOTIFY_EMBED_REGEX)) {
@@ -1144,7 +1153,7 @@ function p(el) {
1144
1153
  }
1145
1154
 
1146
1155
  // src/methods/linkify.method.ts
1147
- function linkify(content, forApp, webp) {
1156
+ function linkify(content, forApp) {
1148
1157
  content = content.replace(/(^|\s|>)(#[-a-z\d]+)/gi, (tag) => {
1149
1158
  if (/#[\d]+$/.test(tag)) return tag;
1150
1159
  const preceding = /^\s|>/.test(tag) ? tag[0] : "";
@@ -1186,21 +1195,21 @@ function linkify(content, forApp, webp) {
1186
1195
  content = content.replace(IMG_REGEX, (imglink) => {
1187
1196
  const isLCP = !firstImageUsed;
1188
1197
  firstImageUsed = true;
1189
- return createImageHTML(imglink, isLCP, webp);
1198
+ return createImageHTML(imglink, isLCP);
1190
1199
  });
1191
1200
  return content;
1192
1201
  }
1193
1202
 
1194
1203
  // src/methods/text.method.ts
1195
- function text(node, forApp, webp) {
1204
+ function text(node, forApp) {
1196
1205
  if (!node || !node.parentNode) {
1197
1206
  return;
1198
1207
  }
1199
- if (node.parentNode && ["a", "code"].includes(node.parentNode.nodeName.toLowerCase())) {
1208
+ if (["a", "code"].includes(node.parentNode.nodeName.toLowerCase())) {
1200
1209
  return;
1201
1210
  }
1202
1211
  const nodeValue = node.nodeValue || "";
1203
- const linkified = linkify(nodeValue, forApp, webp);
1212
+ const linkified = linkify(nodeValue, forApp);
1204
1213
  if (linkified !== nodeValue) {
1205
1214
  const doc = DOMParser.parseFromString(
1206
1215
  `<span class="wr">${linkified}</span>`,
@@ -1215,7 +1224,7 @@ function text(node, forApp, webp) {
1215
1224
  }
1216
1225
  if (nodeValue.match(IMG_REGEX)) {
1217
1226
  const isLCP = false;
1218
- const imageHTML = createImageHTML(nodeValue, isLCP, webp);
1227
+ const imageHTML = createImageHTML(nodeValue, isLCP);
1219
1228
  const doc = DOMParser.parseFromString(imageHTML, "text/html");
1220
1229
  const replaceNode = doc.body?.firstChild || doc.firstChild;
1221
1230
  if (replaceNode) {
@@ -1227,7 +1236,7 @@ function text(node, forApp, webp) {
1227
1236
  const e = YOUTUBE_REGEX.exec(nodeValue);
1228
1237
  if (e && e[1]) {
1229
1238
  const vid = e[1];
1230
- const thumbnail = proxifyImageSrc(`https://img.youtube.com/vi/${vid.split("?")[0]}/hqdefault.jpg`, 0, 0, webp ? "webp" : "match");
1239
+ const thumbnail = proxifyImageSrc(`https://img.youtube.com/vi/${vid.split("?")[0]}/hqdefault.jpg`, 0, 0, "match");
1231
1240
  const embedSrc = `https://www.youtube.com/embed/${vid}?autoplay=1`;
1232
1241
  const startTime = extractYtStartTime(nodeValue);
1233
1242
  const container = node.ownerDocument.createElement("p");
@@ -1273,7 +1282,7 @@ function text(node, forApp, webp) {
1273
1282
  }
1274
1283
 
1275
1284
  // src/methods/traverse.method.ts
1276
- function traverse(node, forApp, depth = 0, webp = false, state = { firstImageFound: false }, parentDomain = "ecency.com") {
1285
+ function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, parentDomain = "ecency.com", seoContext) {
1277
1286
  if (!node || !node.childNodes) {
1278
1287
  return;
1279
1288
  }
@@ -1281,23 +1290,23 @@ function traverse(node, forApp, depth = 0, webp = false, state = { firstImageFou
1281
1290
  const child = node.childNodes[i];
1282
1291
  if (!child) return;
1283
1292
  if (child.nodeName.toLowerCase() === "a") {
1284
- a(child, forApp, webp, parentDomain);
1293
+ a(child, forApp, parentDomain, seoContext);
1285
1294
  }
1286
1295
  if (child.nodeName.toLowerCase() === "iframe") {
1287
1296
  iframe(child, parentDomain);
1288
1297
  }
1289
1298
  if (child.nodeName === "#text") {
1290
- text(child, forApp, webp);
1299
+ text(child, forApp);
1291
1300
  }
1292
1301
  if (child.nodeName.toLowerCase() === "img") {
1293
- img(child, webp, state);
1302
+ img(child, state);
1294
1303
  }
1295
1304
  if (child.nodeName.toLowerCase() === "p") {
1296
1305
  p(child);
1297
1306
  }
1298
1307
  const currentChild = node.childNodes[i];
1299
1308
  if (currentChild) {
1300
- traverse(currentChild, forApp, depth + 1, webp, state, parentDomain);
1309
+ traverse(currentChild, forApp, depth + 1, state, parentDomain, seoContext);
1301
1310
  }
1302
1311
  });
1303
1312
  }
@@ -1348,7 +1357,7 @@ function fixBlockLevelTagsInParagraphs(html) {
1348
1357
  html = html.replace(/<p><br>\s*<\/p>/g, "");
1349
1358
  return html;
1350
1359
  }
1351
- function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com") {
1360
+ function markdownToHTML(input, forApp, parentDomain = "ecency.com", seoContext) {
1352
1361
  input = input.replace(new RegExp("https://leofinance.io/threads/view/", "g"), "/@");
1353
1362
  input = input.replace(new RegExp("https://leofinance.io/posts/", "g"), "/@");
1354
1363
  input = input.replace(new RegExp("https://leofinance.io/threads/", "g"), "/@");
@@ -1408,7 +1417,7 @@ function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com") {
1408
1417
  output = md.render(input);
1409
1418
  output = fixBlockLevelTagsInParagraphs(output);
1410
1419
  const doc = DOMParser.parseFromString(`<body id="root">${removeDuplicateAttributes(output)}</body>`, "text/html");
1411
- traverse(doc, forApp, 0, webp, { firstImageFound: false }, parentDomain);
1420
+ traverse(doc, forApp, 0, { firstImageFound: false }, parentDomain, seoContext);
1412
1421
  output = serializer.serializeToString(doc);
1413
1422
  } catch (error) {
1414
1423
  try {
@@ -1421,7 +1430,7 @@ function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com") {
1421
1430
  });
1422
1431
  const repairedHtml = domSerializer(dom.children);
1423
1432
  const doc = DOMParser.parseFromString(`<body id="root">${removeDuplicateAttributes(repairedHtml)}</body>`, "text/html");
1424
- traverse(doc, forApp, 0, webp, { firstImageFound: false }, parentDomain);
1433
+ traverse(doc, forApp, 0, { firstImageFound: false }, parentDomain, seoContext);
1425
1434
  output = serializer.serializeToString(doc);
1426
1435
  } catch (fallbackError) {
1427
1436
  const escapedContent = he2.encode(output || md.render(input));
@@ -1449,18 +1458,18 @@ function cacheSet(key, value) {
1449
1458
  }
1450
1459
 
1451
1460
  // src/markdown-2-html.ts
1452
- function markdown2Html(obj, forApp = true, webp = false, parentDomain = "ecency.com") {
1461
+ function markdown2Html(obj, forApp = true, _webp = false, parentDomain = "ecency.com", seoContext) {
1453
1462
  if (typeof obj === "string") {
1454
1463
  const cleanedStr = cleanReply(obj);
1455
- return markdownToHTML(cleanedStr, forApp, webp, parentDomain);
1464
+ return markdownToHTML(cleanedStr, forApp, parentDomain, seoContext);
1456
1465
  }
1457
- const key = `${makeEntryCacheKey(obj)}-md${webp ? "-webp" : ""}-${forApp ? "app" : "site"}-${parentDomain}`;
1466
+ const key = `${makeEntryCacheKey(obj)}-md-${forApp ? "app" : "site"}-${parentDomain}${seoContext ? `-seo${seoContext.authorReputation ?? ""}-${seoContext.postPayout ?? ""}` : ""}`;
1458
1467
  const item = cacheGet(key);
1459
1468
  if (item) {
1460
1469
  return item;
1461
1470
  }
1462
1471
  const cleanBody = cleanReply(obj.body);
1463
- const res = markdownToHTML(cleanBody, forApp, webp, parentDomain);
1472
+ const res = markdownToHTML(cleanBody, forApp, parentDomain, seoContext);
1464
1473
  cacheSet(key, res);
1465
1474
  return res;
1466
1475
  }