@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.
@@ -9,7 +9,27 @@ interface Entry {
9
9
  json_metadata?: any;
10
10
  }
11
11
 
12
- declare function markdown2Html(obj: Entry | string, forApp?: boolean, webp?: boolean, parentDomain?: string): string;
12
+ /**
13
+ * SEO context for controlling rel attributes on external links in user-generated content.
14
+ *
15
+ * By default, all external links get rel="nofollow ugc noopener" to prevent link spam.
16
+ * High-quality content (high author reputation + meaningful post rewards) earns followed links.
17
+ */
18
+ interface SeoContext {
19
+ /** Human-readable author reputation score (after accountReputation() conversion) */
20
+ authorReputation?: number;
21
+ /** Total post payout in USD */
22
+ postPayout?: number;
23
+ }
24
+
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;
13
33
 
14
34
  declare function catchPostImage(obj: Entry | string, width?: number, height?: number, format?: string): string | null;
15
35
 
@@ -24,7 +44,10 @@ declare function catchPostImage(obj: Entry | string, width?: number, height?: nu
24
44
  declare function getPostBodySummary(obj: Entry | string, length?: number, platform?: 'ios' | 'android' | 'web'): string;
25
45
 
26
46
  declare function setProxyBase(p: string): void;
27
- 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;
28
51
 
29
52
  declare function setCacheSize(size: number): void;
30
53
 
@@ -32,4 +55,4 @@ declare const SECTION_LIST: string[];
32
55
 
33
56
  declare function isValidPermlink(permlink: string): boolean;
34
57
 
35
- export { type Entry, SECTION_LIST, catchPostImage, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase };
58
+ export { type Entry, SECTION_LIST, type SeoContext, catchPostImage, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase };
@@ -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}"
@@ -388,6 +387,14 @@ function createImageHTML(src, isLCP, webp) {
388
387
  }
389
388
 
390
389
  // src/methods/a.method.ts
390
+ var NOFOLLOW_REPUTATION_THRESHOLD = 40;
391
+ var FOLLOW_PAYOUT_THRESHOLD = 5;
392
+ function getExternalLinkRel(seoContext) {
393
+ if (seoContext?.authorReputation !== void 0 && seoContext?.postPayout !== void 0 && seoContext.authorReputation >= NOFOLLOW_REPUTATION_THRESHOLD && seoContext.postPayout > FOLLOW_PAYOUT_THRESHOLD) {
394
+ return "noopener";
395
+ }
396
+ return "nofollow ugc noopener";
397
+ }
391
398
  var normalizeValue = (value) => value ? value.trim() : "";
392
399
  var matchesHref = (href, value) => {
393
400
  const normalizedHref = normalizeValue(href);
@@ -421,7 +428,7 @@ var addLineBreakBeforePostLink = (el, forApp, isInline) => {
421
428
  el.parentNode.insertBefore(br, el);
422
429
  }
423
430
  };
424
- function a(el, forApp, webp, parentDomain = "ecency.com") {
431
+ function a(el, forApp, parentDomain = "ecency.com", seoContext) {
425
432
  if (!el || !el.parentNode) {
426
433
  return;
427
434
  }
@@ -439,7 +446,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
439
446
  }
440
447
  if (href.match(IMG_REGEX) && href.trim().replace(/&amp;/g, "&") === getSerializedInnerHTML(el).trim().replace(/&amp;/g, "&")) {
441
448
  const isLCP = false;
442
- const imgHTML = createImageHTML(href, isLCP, webp);
449
+ const imgHTML = createImageHTML(href, isLCP);
443
450
  const doc = DOMParser.parseFromString(imgHTML, "text/html");
444
451
  const replaceNode = doc.body?.firstChild || doc.firstChild;
445
452
  if (replaceNode) {
@@ -761,7 +768,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
761
768
  el.setAttribute("class", "markdown-video-link markdown-video-link-youtube");
762
769
  el.removeAttribute("href");
763
770
  const vid = match[1];
764
- 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");
765
772
  const embedSrc = `https://www.youtube.com/embed/${vid}?autoplay=1`;
766
773
  el.textContent = "";
767
774
  el.setAttribute("data-embed-src", embedSrc);
@@ -857,7 +864,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
857
864
  if (imgEls.length === 1) {
858
865
  const src = imgEls[0].getAttribute("src");
859
866
  if (src) {
860
- const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, webp ? "webp" : "match");
867
+ const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, "match");
861
868
  const thumbImg = el.ownerDocument.createElement("img");
862
869
  thumbImg.setAttribute("class", "no-replace video-thumbnail");
863
870
  thumbImg.setAttribute("itemprop", "thumbnailUrl");
@@ -892,7 +899,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
892
899
  const imgEls2 = el.getElementsByTagName("img");
893
900
  if (imgEls2.length === 1 || el.textContent.trim() === href) {
894
901
  if ((match[1] || match[2]) && match[3]) {
895
- 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`;
896
903
  el.setAttribute("class", "markdown-video-link markdown-video-link-speak");
897
904
  el.removeAttribute("href");
898
905
  el.setAttribute("data-embed-src", videoHref);
@@ -902,7 +909,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
902
909
  if (imgEls2.length === 1) {
903
910
  const src = imgEls2[0].getAttribute("src");
904
911
  if (src) {
905
- const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, webp ? "webp" : "match");
912
+ const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, "match");
906
913
  const thumbImg = el.ownerDocument.createElement("img");
907
914
  thumbImg.setAttribute("class", "no-replace video-thumbnail");
908
915
  thumbImg.setAttribute("itemprop", "thumbnailUrl");
@@ -981,7 +988,7 @@ function a(el, forApp, webp, parentDomain = "ecency.com") {
981
988
  el.setAttribute("class", "markdown-internal-link");
982
989
  } else {
983
990
  el.setAttribute("target", "_blank");
984
- el.setAttribute("rel", "noopener");
991
+ el.setAttribute("rel", getExternalLinkRel(seoContext));
985
992
  }
986
993
  el.setAttribute("href", href);
987
994
  }
@@ -1025,7 +1032,8 @@ function iframe(el, parentDomain = "ecency.com") {
1025
1032
  return;
1026
1033
  }
1027
1034
  if (src.match(SPEAK_EMBED_REGEX)) {
1028
- 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?");
1029
1037
  const hasMode = /[?&]mode=/.test(normalizedSrc);
1030
1038
  if (!hasMode) {
1031
1039
  normalizedSrc = `${normalizedSrc}&mode=iframe`;
@@ -1033,6 +1041,7 @@ function iframe(el, parentDomain = "ecency.com") {
1033
1041
  const hasAutoplay = /[?&]autoplay=/.test(normalizedSrc);
1034
1042
  const s = hasAutoplay ? normalizedSrc : `${normalizedSrc}&autoplay=true`;
1035
1043
  el.setAttribute("src", s);
1044
+ el.setAttribute("class", "speak-iframe");
1036
1045
  return;
1037
1046
  }
1038
1047
  if (src.match(SPOTIFY_EMBED_REGEX)) {
@@ -1141,7 +1150,7 @@ function p(el) {
1141
1150
  }
1142
1151
 
1143
1152
  // src/methods/linkify.method.ts
1144
- function linkify(content, forApp, webp) {
1153
+ function linkify(content, forApp) {
1145
1154
  content = content.replace(/(^|\s|>)(#[-a-z\d]+)/gi, (tag) => {
1146
1155
  if (/#[\d]+$/.test(tag)) return tag;
1147
1156
  const preceding = /^\s|>/.test(tag) ? tag[0] : "";
@@ -1183,21 +1192,21 @@ function linkify(content, forApp, webp) {
1183
1192
  content = content.replace(IMG_REGEX, (imglink) => {
1184
1193
  const isLCP = !firstImageUsed;
1185
1194
  firstImageUsed = true;
1186
- return createImageHTML(imglink, isLCP, webp);
1195
+ return createImageHTML(imglink, isLCP);
1187
1196
  });
1188
1197
  return content;
1189
1198
  }
1190
1199
 
1191
1200
  // src/methods/text.method.ts
1192
- function text(node, forApp, webp) {
1201
+ function text(node, forApp) {
1193
1202
  if (!node || !node.parentNode) {
1194
1203
  return;
1195
1204
  }
1196
- if (node.parentNode && ["a", "code"].includes(node.parentNode.nodeName.toLowerCase())) {
1205
+ if (["a", "code"].includes(node.parentNode.nodeName.toLowerCase())) {
1197
1206
  return;
1198
1207
  }
1199
1208
  const nodeValue = node.nodeValue || "";
1200
- const linkified = linkify(nodeValue, forApp, webp);
1209
+ const linkified = linkify(nodeValue, forApp);
1201
1210
  if (linkified !== nodeValue) {
1202
1211
  const doc = DOMParser.parseFromString(
1203
1212
  `<span class="wr">${linkified}</span>`,
@@ -1212,7 +1221,7 @@ function text(node, forApp, webp) {
1212
1221
  }
1213
1222
  if (nodeValue.match(IMG_REGEX)) {
1214
1223
  const isLCP = false;
1215
- const imageHTML = createImageHTML(nodeValue, isLCP, webp);
1224
+ const imageHTML = createImageHTML(nodeValue, isLCP);
1216
1225
  const doc = DOMParser.parseFromString(imageHTML, "text/html");
1217
1226
  const replaceNode = doc.body?.firstChild || doc.firstChild;
1218
1227
  if (replaceNode) {
@@ -1224,7 +1233,7 @@ function text(node, forApp, webp) {
1224
1233
  const e = YOUTUBE_REGEX.exec(nodeValue);
1225
1234
  if (e && e[1]) {
1226
1235
  const vid = e[1];
1227
- 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");
1228
1237
  const embedSrc = `https://www.youtube.com/embed/${vid}?autoplay=1`;
1229
1238
  const startTime = extractYtStartTime(nodeValue);
1230
1239
  const container = node.ownerDocument.createElement("p");
@@ -1270,7 +1279,7 @@ function text(node, forApp, webp) {
1270
1279
  }
1271
1280
 
1272
1281
  // src/methods/traverse.method.ts
1273
- function traverse(node, forApp, depth = 0, webp = false, state = { firstImageFound: false }, parentDomain = "ecency.com") {
1282
+ function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, parentDomain = "ecency.com", seoContext) {
1274
1283
  if (!node || !node.childNodes) {
1275
1284
  return;
1276
1285
  }
@@ -1278,23 +1287,23 @@ function traverse(node, forApp, depth = 0, webp = false, state = { firstImageFou
1278
1287
  const child = node.childNodes[i];
1279
1288
  if (!child) return;
1280
1289
  if (child.nodeName.toLowerCase() === "a") {
1281
- a(child, forApp, webp, parentDomain);
1290
+ a(child, forApp, parentDomain, seoContext);
1282
1291
  }
1283
1292
  if (child.nodeName.toLowerCase() === "iframe") {
1284
1293
  iframe(child, parentDomain);
1285
1294
  }
1286
1295
  if (child.nodeName === "#text") {
1287
- text(child, forApp, webp);
1296
+ text(child, forApp);
1288
1297
  }
1289
1298
  if (child.nodeName.toLowerCase() === "img") {
1290
- img(child, webp, state);
1299
+ img(child, state);
1291
1300
  }
1292
1301
  if (child.nodeName.toLowerCase() === "p") {
1293
1302
  p(child);
1294
1303
  }
1295
1304
  const currentChild = node.childNodes[i];
1296
1305
  if (currentChild) {
1297
- traverse(currentChild, forApp, depth + 1, webp, state, parentDomain);
1306
+ traverse(currentChild, forApp, depth + 1, state, parentDomain, seoContext);
1298
1307
  }
1299
1308
  });
1300
1309
  }
@@ -1345,7 +1354,7 @@ function fixBlockLevelTagsInParagraphs(html) {
1345
1354
  html = html.replace(/<p><br>\s*<\/p>/g, "");
1346
1355
  return html;
1347
1356
  }
1348
- function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com") {
1357
+ function markdownToHTML(input, forApp, parentDomain = "ecency.com", seoContext) {
1349
1358
  input = input.replace(new RegExp("https://leofinance.io/threads/view/", "g"), "/@");
1350
1359
  input = input.replace(new RegExp("https://leofinance.io/posts/", "g"), "/@");
1351
1360
  input = input.replace(new RegExp("https://leofinance.io/threads/", "g"), "/@");
@@ -1405,7 +1414,7 @@ function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com") {
1405
1414
  output = md.render(input);
1406
1415
  output = fixBlockLevelTagsInParagraphs(output);
1407
1416
  const doc = DOMParser.parseFromString(`<body id="root">${removeDuplicateAttributes(output)}</body>`, "text/html");
1408
- traverse(doc, forApp, 0, webp, { firstImageFound: false }, parentDomain);
1417
+ traverse(doc, forApp, 0, { firstImageFound: false }, parentDomain, seoContext);
1409
1418
  output = serializer.serializeToString(doc);
1410
1419
  } catch (error) {
1411
1420
  try {
@@ -1418,7 +1427,7 @@ function markdownToHTML(input, forApp, webp, parentDomain = "ecency.com") {
1418
1427
  });
1419
1428
  const repairedHtml = domSerializer(dom.children);
1420
1429
  const doc = DOMParser.parseFromString(`<body id="root">${removeDuplicateAttributes(repairedHtml)}</body>`, "text/html");
1421
- traverse(doc, forApp, 0, webp, { firstImageFound: false }, parentDomain);
1430
+ traverse(doc, forApp, 0, { firstImageFound: false }, parentDomain, seoContext);
1422
1431
  output = serializer.serializeToString(doc);
1423
1432
  } catch (fallbackError) {
1424
1433
  const escapedContent = he2.encode(output || md.render(input));
@@ -1446,18 +1455,18 @@ function cacheSet(key, value) {
1446
1455
  }
1447
1456
 
1448
1457
  // src/markdown-2-html.ts
1449
- function markdown2Html(obj, forApp = true, webp = false, parentDomain = "ecency.com") {
1458
+ function markdown2Html(obj, forApp = true, _webp = false, parentDomain = "ecency.com", seoContext) {
1450
1459
  if (typeof obj === "string") {
1451
1460
  const cleanedStr = cleanReply(obj);
1452
- return markdownToHTML(cleanedStr, forApp, webp, parentDomain);
1461
+ return markdownToHTML(cleanedStr, forApp, parentDomain, seoContext);
1453
1462
  }
1454
- const key = `${makeEntryCacheKey(obj)}-md${webp ? "-webp" : ""}-${forApp ? "app" : "site"}-${parentDomain}`;
1463
+ const key = `${makeEntryCacheKey(obj)}-md-${forApp ? "app" : "site"}-${parentDomain}${seoContext ? `-seo${seoContext.authorReputation ?? ""}-${seoContext.postPayout ?? ""}` : ""}`;
1455
1464
  const item = cacheGet(key);
1456
1465
  if (item) {
1457
1466
  return item;
1458
1467
  }
1459
1468
  const cleanBody = cleanReply(obj.body);
1460
- const res = markdownToHTML(cleanBody, forApp, webp, parentDomain);
1469
+ const res = markdownToHTML(cleanBody, forApp, parentDomain, seoContext);
1461
1470
  cacheSet(key, res);
1462
1471
  return res;
1463
1472
  }