@ecency/render-helper 2.4.13 → 2.4.15

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.
@@ -193,10 +193,11 @@ var ALLOWED_ATTRIBUTES = {
193
193
  "del": [],
194
194
  "ins": []
195
195
  };
196
- var lenientErrorHandler = (level, msg) => {
197
- if (process.env.NODE_ENV === "development" && level === "fatalError") {
196
+ var lenientErrorHandler = (level, msg, context) => {
197
+ if (process.env.NODE_ENV === "development") {
198
198
  console.warn("[DOMParser]", level, msg);
199
199
  }
200
+ return void 0;
200
201
  };
201
202
  var DOMParser = new xmldom.DOMParser({
202
203
  // Use onError instead of deprecated errorHandler
@@ -205,11 +206,30 @@ var DOMParser = new xmldom.DOMParser({
205
206
  });
206
207
 
207
208
  // src/helper.ts
209
+ function removeDuplicateAttributes(html) {
210
+ const tagRegex = /<([a-zA-Z][a-zA-Z0-9]*)\s+((?:[^>"']+|"[^"]*"|'[^']*')*?)\s*(\/?)>/g;
211
+ return html.replace(tagRegex, (match, tagName, attrsString, selfClose) => {
212
+ const seenAttrs = /* @__PURE__ */ new Set();
213
+ const cleanedAttrs = [];
214
+ const attrRegex = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*(?:=\s*(?:"[^"]*"|'[^']*'|[^\s/>]+))?/g;
215
+ let attrMatch;
216
+ while ((attrMatch = attrRegex.exec(attrsString)) !== null) {
217
+ const attrName = attrMatch[1].toLowerCase();
218
+ if (!seenAttrs.has(attrName)) {
219
+ seenAttrs.add(attrName);
220
+ cleanedAttrs.push(attrMatch[0]);
221
+ }
222
+ }
223
+ const attrsJoined = cleanedAttrs.length > 0 ? ` ${cleanedAttrs.join(" ")}` : "";
224
+ return `<${tagName}${attrsJoined}${selfClose ? " /" : ""}>`;
225
+ });
226
+ }
208
227
  function createDoc(html) {
209
228
  if (html.trim() === "") {
210
229
  return null;
211
230
  }
212
- const doc = DOMParser.parseFromString(`<body>${html}</body>`, "text/html");
231
+ const cleanedHtml = removeDuplicateAttributes(html);
232
+ const doc = DOMParser.parseFromString(`<body>${cleanedHtml}</body>`, "text/html");
213
233
  return doc;
214
234
  }
215
235
  function makeEntryCacheKey(entry) {
@@ -400,6 +420,14 @@ function createImageHTML(src, isLCP, webp) {
400
420
  }
401
421
 
402
422
  // src/methods/a.method.ts
423
+ var NOFOLLOW_REPUTATION_THRESHOLD = 40;
424
+ var FOLLOW_PAYOUT_THRESHOLD = 5;
425
+ function getExternalLinkRel(seoContext) {
426
+ if (seoContext?.authorReputation !== void 0 && seoContext?.postPayout !== void 0 && seoContext.authorReputation >= NOFOLLOW_REPUTATION_THRESHOLD && seoContext.postPayout > FOLLOW_PAYOUT_THRESHOLD) {
427
+ return "noopener";
428
+ }
429
+ return "nofollow ugc noopener";
430
+ }
403
431
  var normalizeValue = (value) => value ? value.trim() : "";
404
432
  var matchesHref = (href, value) => {
405
433
  const normalizedHref = normalizeValue(href);
@@ -433,7 +461,7 @@ var addLineBreakBeforePostLink = (el, forApp, isInline) => {
433
461
  el.parentNode.insertBefore(br, el);
434
462
  }
435
463
  };
436
- function a(el, forApp, webp, parentDomain = "ecency.com") {
464
+ function a(el, forApp, webp, parentDomain = "ecency.com", seoContext) {
437
465
  if (!el || !el.parentNode) {
438
466
  return;
439
467
  }
@@ -993,7 +1021,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
993
1021
  el.setAttribute("class", "markdown-internal-link");
994
1022
  } else {
995
1023
  el.setAttribute("target", "_blank");
996
- el.setAttribute("rel", "noopener");
1024
+ el.setAttribute("rel", getExternalLinkRel(seoContext));
997
1025
  }
998
1026
  el.setAttribute("href", href);
999
1027
  }
@@ -1282,7 +1310,7 @@ function text(node, forApp, webp) {
1282
1310
  }
1283
1311
 
1284
1312
  // src/methods/traverse.method.ts
1285
- function traverse(node, forApp, depth = 0, webp = false, state = { firstImageFound: false }, parentDomain = "ecency.com") {
1313
+ function traverse(node, forApp, depth = 0, webp = false, state = { firstImageFound: false }, parentDomain = "ecency.com", seoContext) {
1286
1314
  if (!node || !node.childNodes) {
1287
1315
  return;
1288
1316
  }
@@ -1290,7 +1318,7 @@ function traverse(node, forApp, depth = 0, webp = false, state = { firstImageFou
1290
1318
  const child = node.childNodes[i];
1291
1319
  if (!child) return;
1292
1320
  if (child.nodeName.toLowerCase() === "a") {
1293
- a(child, forApp, webp, parentDomain);
1321
+ a(child, forApp, webp, parentDomain, seoContext);
1294
1322
  }
1295
1323
  if (child.nodeName.toLowerCase() === "iframe") {
1296
1324
  iframe(child, parentDomain);
@@ -1306,7 +1334,7 @@ function traverse(node, forApp, depth = 0, webp = false, state = { firstImageFou
1306
1334
  }
1307
1335
  const currentChild = node.childNodes[i];
1308
1336
  if (currentChild) {
1309
- traverse(currentChild, forApp, depth + 1, webp, state, parentDomain);
1337
+ traverse(currentChild, forApp, depth + 1, webp, state, parentDomain, seoContext);
1310
1338
  }
1311
1339
  });
1312
1340
  }
@@ -1357,7 +1385,7 @@ function fixBlockLevelTagsInParagraphs(html) {
1357
1385
  html = html.replace(/<p><br>\s*<\/p>/g, "");
1358
1386
  return html;
1359
1387
  }
1360
- function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com") {
1388
+ function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com", seoContext) {
1361
1389
  input = input.replace(new RegExp("https://leofinance.io/threads/view/", "g"), "/@");
1362
1390
  input = input.replace(new RegExp("https://leofinance.io/posts/", "g"), "/@");
1363
1391
  input = input.replace(new RegExp("https://leofinance.io/threads/", "g"), "/@");
@@ -1416,8 +1444,8 @@ function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com") {
1416
1444
  try {
1417
1445
  output = md.render(input);
1418
1446
  output = fixBlockLevelTagsInParagraphs(output);
1419
- const doc = DOMParser.parseFromString(`<body id="root">${output}</body>`, "text/html");
1420
- traverse(doc, forApp, 0, webp, { firstImageFound: false }, parentDomain);
1447
+ const doc = DOMParser.parseFromString(`<body id="root">${removeDuplicateAttributes(output)}</body>`, "text/html");
1448
+ traverse(doc, forApp, 0, webp, { firstImageFound: false }, parentDomain, seoContext);
1421
1449
  output = serializer.serializeToString(doc);
1422
1450
  } catch (error) {
1423
1451
  try {
@@ -1429,8 +1457,8 @@ function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com") {
1429
1457
  lowerCaseAttributeNames: false
1430
1458
  });
1431
1459
  const repairedHtml = domSerializer(dom.children);
1432
- const doc = DOMParser.parseFromString(`<body id="root">${repairedHtml}</body>`, "text/html");
1433
- traverse(doc, forApp, 0, webp, { firstImageFound: false }, parentDomain);
1460
+ const doc = DOMParser.parseFromString(`<body id="root">${removeDuplicateAttributes(repairedHtml)}</body>`, "text/html");
1461
+ traverse(doc, forApp, 0, webp, { firstImageFound: false }, parentDomain, seoContext);
1434
1462
  output = serializer.serializeToString(doc);
1435
1463
  } catch (fallbackError) {
1436
1464
  const escapedContent = he2__default.default.encode(output || md.render(input));
@@ -1458,18 +1486,18 @@ function cacheSet(key, value) {
1458
1486
  }
1459
1487
 
1460
1488
  // src/markdown-2-html.ts
1461
- function markdown2Html(obj, forApp = true, webp = false, parentDomain = "ecency.com") {
1489
+ function markdown2Html(obj, forApp = true, webp = false, parentDomain = "ecency.com", seoContext) {
1462
1490
  if (typeof obj === "string") {
1463
1491
  const cleanedStr = cleanReply(obj);
1464
- return markdownToHTML(cleanedStr, forApp, webp, parentDomain);
1492
+ return markdownToHTML(cleanedStr, forApp, webp, parentDomain, seoContext);
1465
1493
  }
1466
- const key = `${makeEntryCacheKey(obj)}-md${webp ? "-webp" : ""}-${forApp ? "app" : "site"}-${parentDomain}`;
1494
+ const key = `${makeEntryCacheKey(obj)}-md${webp ? "-webp" : ""}-${forApp ? "app" : "site"}-${parentDomain}${seoContext ? `-seo${seoContext.authorReputation ?? ""}-${seoContext.postPayout ?? ""}` : ""}`;
1467
1495
  const item = cacheGet(key);
1468
1496
  if (item) {
1469
1497
  return item;
1470
1498
  }
1471
1499
  const cleanBody = cleanReply(obj.body);
1472
- const res = markdownToHTML(cleanBody, forApp, webp, parentDomain);
1500
+ const res = markdownToHTML(cleanBody, forApp, webp, parentDomain, seoContext);
1473
1501
  cacheSet(key, res);
1474
1502
  return res;
1475
1503
  }