@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.
@@ -164,10 +164,11 @@ var ALLOWED_ATTRIBUTES = {
164
164
  "del": [],
165
165
  "ins": []
166
166
  };
167
- var lenientErrorHandler = (level, msg) => {
168
- if (process.env.NODE_ENV === "development" && level === "fatalError") {
167
+ var lenientErrorHandler = (level, msg, context) => {
168
+ if (process.env.NODE_ENV === "development") {
169
169
  console.warn("[DOMParser]", level, msg);
170
170
  }
171
+ return void 0;
171
172
  };
172
173
  var DOMParser = new DOMParser$1({
173
174
  // Use onError instead of deprecated errorHandler
@@ -176,11 +177,30 @@ var DOMParser = new DOMParser$1({
176
177
  });
177
178
 
178
179
  // src/helper.ts
180
+ function removeDuplicateAttributes(html) {
181
+ const tagRegex = /<([a-zA-Z][a-zA-Z0-9]*)\s+((?:[^>"']+|"[^"]*"|'[^']*')*?)\s*(\/?)>/g;
182
+ return html.replace(tagRegex, (match, tagName, attrsString, selfClose) => {
183
+ const seenAttrs = /* @__PURE__ */ new Set();
184
+ const cleanedAttrs = [];
185
+ const attrRegex = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*(?:=\s*(?:"[^"]*"|'[^']*'|[^\s/>]+))?/g;
186
+ let attrMatch;
187
+ while ((attrMatch = attrRegex.exec(attrsString)) !== null) {
188
+ const attrName = attrMatch[1].toLowerCase();
189
+ if (!seenAttrs.has(attrName)) {
190
+ seenAttrs.add(attrName);
191
+ cleanedAttrs.push(attrMatch[0]);
192
+ }
193
+ }
194
+ const attrsJoined = cleanedAttrs.length > 0 ? ` ${cleanedAttrs.join(" ")}` : "";
195
+ return `<${tagName}${attrsJoined}${selfClose ? " /" : ""}>`;
196
+ });
197
+ }
179
198
  function createDoc(html) {
180
199
  if (html.trim() === "") {
181
200
  return null;
182
201
  }
183
- const doc = DOMParser.parseFromString(`<body>${html}</body>`, "text/html");
202
+ const cleanedHtml = removeDuplicateAttributes(html);
203
+ const doc = DOMParser.parseFromString(`<body>${cleanedHtml}</body>`, "text/html");
184
204
  return doc;
185
205
  }
186
206
  function makeEntryCacheKey(entry) {
@@ -371,6 +391,14 @@ function createImageHTML(src, isLCP, webp) {
371
391
  }
372
392
 
373
393
  // src/methods/a.method.ts
394
+ var NOFOLLOW_REPUTATION_THRESHOLD = 40;
395
+ var FOLLOW_PAYOUT_THRESHOLD = 5;
396
+ function getExternalLinkRel(seoContext) {
397
+ if (seoContext?.authorReputation !== void 0 && seoContext?.postPayout !== void 0 && seoContext.authorReputation >= NOFOLLOW_REPUTATION_THRESHOLD && seoContext.postPayout > FOLLOW_PAYOUT_THRESHOLD) {
398
+ return "noopener";
399
+ }
400
+ return "nofollow ugc noopener";
401
+ }
374
402
  var normalizeValue = (value) => value ? value.trim() : "";
375
403
  var matchesHref = (href, value) => {
376
404
  const normalizedHref = normalizeValue(href);
@@ -404,7 +432,7 @@ var addLineBreakBeforePostLink = (el, forApp, isInline) => {
404
432
  el.parentNode.insertBefore(br, el);
405
433
  }
406
434
  };
407
- function a(el, forApp, webp, parentDomain = "ecency.com") {
435
+ function a(el, forApp, webp, parentDomain = "ecency.com", seoContext) {
408
436
  if (!el || !el.parentNode) {
409
437
  return;
410
438
  }
@@ -964,7 +992,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
964
992
  el.setAttribute("class", "markdown-internal-link");
965
993
  } else {
966
994
  el.setAttribute("target", "_blank");
967
- el.setAttribute("rel", "noopener");
995
+ el.setAttribute("rel", getExternalLinkRel(seoContext));
968
996
  }
969
997
  el.setAttribute("href", href);
970
998
  }
@@ -1253,7 +1281,7 @@ function text(node, forApp, webp) {
1253
1281
  }
1254
1282
 
1255
1283
  // src/methods/traverse.method.ts
1256
- function traverse(node, forApp, depth = 0, webp = false, state = { firstImageFound: false }, parentDomain = "ecency.com") {
1284
+ function traverse(node, forApp, depth = 0, webp = false, state = { firstImageFound: false }, parentDomain = "ecency.com", seoContext) {
1257
1285
  if (!node || !node.childNodes) {
1258
1286
  return;
1259
1287
  }
@@ -1261,7 +1289,7 @@ function traverse(node, forApp, depth = 0, webp = false, state = { firstImageFou
1261
1289
  const child = node.childNodes[i];
1262
1290
  if (!child) return;
1263
1291
  if (child.nodeName.toLowerCase() === "a") {
1264
- a(child, forApp, webp, parentDomain);
1292
+ a(child, forApp, webp, parentDomain, seoContext);
1265
1293
  }
1266
1294
  if (child.nodeName.toLowerCase() === "iframe") {
1267
1295
  iframe(child, parentDomain);
@@ -1277,7 +1305,7 @@ function traverse(node, forApp, depth = 0, webp = false, state = { firstImageFou
1277
1305
  }
1278
1306
  const currentChild = node.childNodes[i];
1279
1307
  if (currentChild) {
1280
- traverse(currentChild, forApp, depth + 1, webp, state, parentDomain);
1308
+ traverse(currentChild, forApp, depth + 1, webp, state, parentDomain, seoContext);
1281
1309
  }
1282
1310
  });
1283
1311
  }
@@ -1328,7 +1356,7 @@ function fixBlockLevelTagsInParagraphs(html) {
1328
1356
  html = html.replace(/<p><br>\s*<\/p>/g, "");
1329
1357
  return html;
1330
1358
  }
1331
- function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com") {
1359
+ function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com", seoContext) {
1332
1360
  input = input.replace(new RegExp("https://leofinance.io/threads/view/", "g"), "/@");
1333
1361
  input = input.replace(new RegExp("https://leofinance.io/posts/", "g"), "/@");
1334
1362
  input = input.replace(new RegExp("https://leofinance.io/threads/", "g"), "/@");
@@ -1387,8 +1415,8 @@ function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com") {
1387
1415
  try {
1388
1416
  output = md.render(input);
1389
1417
  output = fixBlockLevelTagsInParagraphs(output);
1390
- const doc = DOMParser.parseFromString(`<body id="root">${output}</body>`, "text/html");
1391
- traverse(doc, forApp, 0, webp, { firstImageFound: false }, parentDomain);
1418
+ const doc = DOMParser.parseFromString(`<body id="root">${removeDuplicateAttributes(output)}</body>`, "text/html");
1419
+ traverse(doc, forApp, 0, webp, { firstImageFound: false }, parentDomain, seoContext);
1392
1420
  output = serializer.serializeToString(doc);
1393
1421
  } catch (error) {
1394
1422
  try {
@@ -1400,8 +1428,8 @@ function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com") {
1400
1428
  lowerCaseAttributeNames: false
1401
1429
  });
1402
1430
  const repairedHtml = domSerializer(dom.children);
1403
- const doc = DOMParser.parseFromString(`<body id="root">${repairedHtml}</body>`, "text/html");
1404
- traverse(doc, forApp, 0, webp, { firstImageFound: false }, parentDomain);
1431
+ const doc = DOMParser.parseFromString(`<body id="root">${removeDuplicateAttributes(repairedHtml)}</body>`, "text/html");
1432
+ traverse(doc, forApp, 0, webp, { firstImageFound: false }, parentDomain, seoContext);
1405
1433
  output = serializer.serializeToString(doc);
1406
1434
  } catch (fallbackError) {
1407
1435
  const escapedContent = he2.encode(output || md.render(input));
@@ -1429,18 +1457,18 @@ function cacheSet(key, value) {
1429
1457
  }
1430
1458
 
1431
1459
  // src/markdown-2-html.ts
1432
- function markdown2Html(obj, forApp = true, webp = false, parentDomain = "ecency.com") {
1460
+ function markdown2Html(obj, forApp = true, webp = false, parentDomain = "ecency.com", seoContext) {
1433
1461
  if (typeof obj === "string") {
1434
1462
  const cleanedStr = cleanReply(obj);
1435
- return markdownToHTML(cleanedStr, forApp, webp, parentDomain);
1463
+ return markdownToHTML(cleanedStr, forApp, webp, parentDomain, seoContext);
1436
1464
  }
1437
- const key = `${makeEntryCacheKey(obj)}-md${webp ? "-webp" : ""}-${forApp ? "app" : "site"}-${parentDomain}`;
1465
+ const key = `${makeEntryCacheKey(obj)}-md${webp ? "-webp" : ""}-${forApp ? "app" : "site"}-${parentDomain}${seoContext ? `-seo${seoContext.authorReputation ?? ""}-${seoContext.postPayout ?? ""}` : ""}`;
1438
1466
  const item = cacheGet(key);
1439
1467
  if (item) {
1440
1468
  return item;
1441
1469
  }
1442
1470
  const cleanBody = cleanReply(obj.body);
1443
- const res = markdownToHTML(cleanBody, forApp, webp, parentDomain);
1471
+ const res = markdownToHTML(cleanBody, forApp, webp, parentDomain, seoContext);
1444
1472
  cacheSet(key, res);
1445
1473
  return res;
1446
1474
  }