@ecency/render-helper 2.4.24 → 2.4.26

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,6 +9,11 @@ interface Entry {
9
9
  json_metadata?: any;
10
10
  }
11
11
 
12
+ interface RenderOptions {
13
+ /** When true, video embeds (3Speak, YouTube, etc.) render as iframes directly without a play button overlay. */
14
+ embedVideosDirectly?: boolean;
15
+ }
16
+
12
17
  /**
13
18
  * SEO context for controlling rel attributes on external links in user-generated content.
14
19
  *
@@ -28,8 +33,9 @@ interface SeoContext {
28
33
  * @param _webp - @deprecated Ignored. Format is now handled server-side via Accept header content negotiation.
29
34
  * @param parentDomain - Parent domain for iframe embed parameters
30
35
  * @param seoContext - Optional SEO context for structured data
36
+ * @param renderOptions - Optional rendering options (e.g. embedVideosDirectly)
31
37
  */
32
- declare function markdown2Html(obj: Entry | string, forApp?: boolean, _webp?: boolean, parentDomain?: string, seoContext?: SeoContext): string;
38
+ declare function markdown2Html(obj: Entry | string, forApp?: boolean, _webp?: boolean, parentDomain?: string, seoContext?: SeoContext, renderOptions?: RenderOptions): string;
33
39
 
34
40
  declare function catchPostImage(obj: Entry | string, width?: number, height?: number, format?: string): string | null;
35
41
 
@@ -55,4 +61,4 @@ declare const SECTION_LIST: string[];
55
61
 
56
62
  declare function isValidPermlink(permlink: string): boolean;
57
63
 
58
- export { type Entry, SECTION_LIST, type SeoContext, catchPostImage, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase };
64
+ export { type Entry, type RenderOptions, SECTION_LIST, type SeoContext, catchPostImage, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase };
@@ -430,7 +430,7 @@ var addLineBreakBeforePostLink = (el, forApp, isInline) => {
430
430
  el.parentNode.insertBefore(br, el);
431
431
  }
432
432
  };
433
- function a(el, forApp, parentDomain = "ecency.com", seoContext) {
433
+ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
434
434
  if (!el || !el.parentNode) {
435
435
  return;
436
436
  }
@@ -439,7 +439,7 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext) {
439
439
  return;
440
440
  }
441
441
  const className = el.getAttribute("class");
442
- if (["markdown-author-link", "markdown-tag-link"].indexOf(className) !== -1) {
442
+ if (className && (["markdown-author-link", "markdown-tag-link"].includes(className) || className.includes("er-author") || className.includes("er-tag"))) {
443
443
  return;
444
444
  }
445
445
  if (href && href.trim().toLowerCase().startsWith("javascript:")) {
@@ -779,14 +779,29 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext) {
779
779
  if (startTime) {
780
780
  el.setAttribute("data-start-time", startTime);
781
781
  }
782
- const thumbImg = el.ownerDocument.createElement("img");
783
- thumbImg.setAttribute("class", "no-replace video-thumbnail");
784
- thumbImg.setAttribute("itemprop", "thumbnailUrl");
785
- thumbImg.setAttribute("src", thumbnail);
786
- const play = el.ownerDocument.createElement("span");
787
- play.setAttribute("class", "markdown-video-play");
788
- el.appendChild(thumbImg);
789
- el.appendChild(play);
782
+ if (renderOptions?.embedVideosDirectly) {
783
+ const wrapper = el.ownerDocument.createElement("span");
784
+ wrapper.setAttribute("class", "er-youtube-frame");
785
+ wrapper.setAttribute("style", "display:block");
786
+ const iframe2 = el.ownerDocument.createElement("iframe");
787
+ iframe2.setAttribute("class", "youtube-player");
788
+ iframe2.setAttribute("src", embedSrc);
789
+ iframe2.setAttribute("title", "YouTube video");
790
+ iframe2.setAttribute("allow", "accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture; web-share");
791
+ iframe2.setAttribute("allowfullscreen", "");
792
+ wrapper.appendChild(iframe2);
793
+ el.appendChild(wrapper);
794
+ el.setAttribute("class", "markdown-video-link markdown-video-link-youtube er-youtube");
795
+ } else {
796
+ const thumbImg = el.ownerDocument.createElement("img");
797
+ thumbImg.setAttribute("class", "no-replace video-thumbnail");
798
+ thumbImg.setAttribute("itemprop", "thumbnailUrl");
799
+ thumbImg.setAttribute("src", thumbnail);
800
+ const play = el.ownerDocument.createElement("span");
801
+ play.setAttribute("class", "markdown-video-play");
802
+ el.appendChild(thumbImg);
803
+ el.appendChild(play);
804
+ }
790
805
  return;
791
806
  }
792
807
  match = href.match(VIMEO_REGEX);
@@ -908,21 +923,36 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext) {
908
923
  if (el.textContent.trim() === href) {
909
924
  el.textContent = "";
910
925
  }
911
- if (imgEls2.length === 1) {
912
- const src = imgEls2[0].getAttribute("src");
913
- if (src) {
914
- const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, "match");
915
- const thumbImg = el.ownerDocument.createElement("img");
916
- thumbImg.setAttribute("class", "no-replace video-thumbnail");
917
- thumbImg.setAttribute("itemprop", "thumbnailUrl");
918
- thumbImg.setAttribute("src", thumbnail);
919
- el.appendChild(thumbImg);
920
- el.removeChild(imgEls2[0]);
926
+ if (renderOptions?.embedVideosDirectly) {
927
+ const wrapper = el.ownerDocument.createElement("span");
928
+ wrapper.setAttribute("class", "er-speak-frame");
929
+ wrapper.setAttribute("style", "display:block");
930
+ const iframe2 = el.ownerDocument.createElement("iframe");
931
+ iframe2.setAttribute("class", "speak-iframe");
932
+ iframe2.setAttribute("src", videoHref);
933
+ iframe2.setAttribute("title", "3Speak video");
934
+ iframe2.setAttribute("allow", "accelerometer; encrypted-media; gyroscope; picture-in-picture; web-share");
935
+ iframe2.setAttribute("allowfullscreen", "");
936
+ wrapper.appendChild(iframe2);
937
+ el.appendChild(wrapper);
938
+ el.setAttribute("class", "markdown-video-link markdown-video-link-speak er-speak");
939
+ } else {
940
+ if (imgEls2.length === 1) {
941
+ const src = imgEls2[0].getAttribute("src");
942
+ if (src) {
943
+ const thumbnail = proxifyImageSrc(src.replace(/\s+/g, ""), 0, 0, "match");
944
+ const thumbImg = el.ownerDocument.createElement("img");
945
+ thumbImg.setAttribute("class", "no-replace video-thumbnail");
946
+ thumbImg.setAttribute("itemprop", "thumbnailUrl");
947
+ thumbImg.setAttribute("src", thumbnail);
948
+ el.appendChild(thumbImg);
949
+ el.removeChild(imgEls2[0]);
950
+ }
921
951
  }
952
+ const play = el.ownerDocument.createElement("span");
953
+ play.setAttribute("class", "markdown-video-play");
954
+ el.appendChild(play);
922
955
  }
923
- const play = el.ownerDocument.createElement("span");
924
- play.setAttribute("class", "markdown-video-play");
925
- el.appendChild(play);
926
956
  return;
927
957
  }
928
958
  }
@@ -1012,7 +1042,7 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext) {
1012
1042
  }
1013
1043
 
1014
1044
  // src/methods/iframe.method.ts
1015
- function iframe(el, parentDomain = "ecency.com") {
1045
+ function iframe(el, parentDomain = "ecency.com", forApp = false) {
1016
1046
  if (!el || !el.parentNode) {
1017
1047
  return;
1018
1048
  }
@@ -1056,7 +1086,10 @@ function iframe(el, parentDomain = "ecency.com") {
1056
1086
  normalizedSrc = `${normalizedSrc}&mode=iframe`;
1057
1087
  }
1058
1088
  const hasAutoplay = /[?&]autoplay=/.test(normalizedSrc);
1059
- const s = hasAutoplay ? normalizedSrc : `${normalizedSrc}&autoplay=true`;
1089
+ let s = hasAutoplay ? normalizedSrc : `${normalizedSrc}&autoplay=true`;
1090
+ if (forApp && !/[?&]layout=/.test(s)) {
1091
+ s = `${s}&layout=mobile`;
1092
+ }
1060
1093
  el.setAttribute("src", s);
1061
1094
  el.setAttribute("class", "speak-iframe");
1062
1095
  return;
@@ -1181,15 +1214,17 @@ function p(el) {
1181
1214
  }
1182
1215
 
1183
1216
  // src/methods/linkify.method.ts
1184
- function linkify(content, forApp) {
1217
+ function linkify(content, forApp, renderOptions) {
1185
1218
  content = content.replace(/(^|\s|>)(#[-a-z\d]+)/gi, (tag) => {
1186
1219
  if (/#[\d]+$/.test(tag)) return tag;
1187
1220
  const preceding = /^\s|>/.test(tag) ? tag[0] : "";
1188
1221
  tag = tag.replace(">", "");
1189
1222
  const tag2 = tag.trim().substring(1);
1190
1223
  const tagLower = tag2.toLowerCase();
1191
- const attrs = forApp ? `data-tag="${tagLower}"` : `href="/trending/${tagLower}"`;
1192
- return `${preceding}<a class="markdown-tag-link" ${attrs}>${tag.trim()}</a>`;
1224
+ if (!forApp) {
1225
+ return `${preceding}<a class="er-tag er-tag-link" href="/trending/${tagLower}">${tag.trim()}</a>`;
1226
+ }
1227
+ return `${preceding}<a class="markdown-tag-link" data-tag="${tagLower}">${tag.trim()}</a>`;
1193
1228
  });
1194
1229
  content = content.replace(
1195
1230
  /(^|[^a-zA-Z0-9_!#$%&*@@/]|(^|[^a-zA-Z0-9_+~.-/]))[@@]([a-z][-.a-z\d^/]+[a-z\d])/gi,
@@ -1197,8 +1232,11 @@ function linkify(content, forApp) {
1197
1232
  const userLower = user.toLowerCase();
1198
1233
  const preceedings = (preceeding1 || "") + (preceeding2 || "");
1199
1234
  if (userLower.indexOf("/") === -1 && isValidUsername(user)) {
1200
- const attrs = forApp ? `data-author="${userLower}"` : `href="/@${userLower}"`;
1201
- return `${preceedings}<a class="markdown-author-link" ${attrs}>@${user}</a>`;
1235
+ if (!forApp) {
1236
+ const avatarSrc = `https://images.ecency.com/u/${userLower}/avatar/small`;
1237
+ return `${preceedings}<a class="er-author er-author-link" href="/@${userLower}"><img class="er-author-link-image" src="${avatarSrc}" alt="${userLower}"/><span class="er-author-link-content"><span class="er-author-link-label">Hive account</span><span>@${userLower}</span></span></a>`;
1238
+ }
1239
+ return `${preceedings}<a class="markdown-author-link" data-author="${userLower}">@${user}</a>`;
1202
1240
  } else {
1203
1241
  return match;
1204
1242
  }
@@ -1255,7 +1293,7 @@ function hasAncestor(node, tagNames) {
1255
1293
  }
1256
1294
  return false;
1257
1295
  }
1258
- function text(node, forApp) {
1296
+ function text(node, forApp, renderOptions) {
1259
1297
  if (!node || !node.parentNode) {
1260
1298
  return;
1261
1299
  }
@@ -1336,7 +1374,7 @@ function text(node, forApp) {
1336
1374
  }
1337
1375
 
1338
1376
  // src/methods/traverse.method.ts
1339
- function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, parentDomain = "ecency.com", seoContext) {
1377
+ function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, parentDomain = "ecency.com", seoContext, renderOptions) {
1340
1378
  if (!node || !node.childNodes) {
1341
1379
  return;
1342
1380
  }
@@ -1345,10 +1383,10 @@ function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, p
1345
1383
  const next = child.nextSibling;
1346
1384
  const prev = child.previousSibling;
1347
1385
  if (child.nodeName.toLowerCase() === "a") {
1348
- a(child, forApp, parentDomain, seoContext);
1386
+ a(child, forApp, parentDomain, seoContext, renderOptions);
1349
1387
  }
1350
1388
  if (child.nodeName.toLowerCase() === "iframe") {
1351
- iframe(child, parentDomain);
1389
+ iframe(child, parentDomain, forApp);
1352
1390
  }
1353
1391
  if (child.nodeName === "#text") {
1354
1392
  text(child, forApp);
@@ -1360,11 +1398,11 @@ function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, p
1360
1398
  p(child);
1361
1399
  }
1362
1400
  if (child.parentNode) {
1363
- traverse(child, forApp, depth + 1, state, parentDomain, seoContext);
1401
+ traverse(child, forApp, depth + 1, state, parentDomain, seoContext, renderOptions);
1364
1402
  } else {
1365
1403
  const possibleReplacement = next ? next.previousSibling : node.lastChild;
1366
1404
  if (possibleReplacement && possibleReplacement !== prev && possibleReplacement.parentNode === node) {
1367
- traverse(possibleReplacement, forApp, depth + 1, state, parentDomain, seoContext);
1405
+ traverse(possibleReplacement, forApp, depth + 1, state, parentDomain, seoContext, renderOptions);
1368
1406
  }
1369
1407
  }
1370
1408
  child = next;
@@ -1417,7 +1455,7 @@ function fixBlockLevelTagsInParagraphs(html) {
1417
1455
  html = html.replace(/<p><br>\s*<\/p>/g, "");
1418
1456
  return html;
1419
1457
  }
1420
- function markdownToHTML(input, forApp, parentDomain = "ecency.com", seoContext) {
1458
+ function markdownToHTML(input, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
1421
1459
  input = input.replace(new RegExp("https://leofinance.io/threads/view/", "g"), "/@");
1422
1460
  input = input.replace(new RegExp("https://leofinance.io/posts/", "g"), "/@");
1423
1461
  input = input.replace(new RegExp("https://leofinance.io/threads/", "g"), "/@");
@@ -1477,7 +1515,7 @@ function markdownToHTML(input, forApp, parentDomain = "ecency.com", seoContext)
1477
1515
  output = md.render(input);
1478
1516
  output = fixBlockLevelTagsInParagraphs(output);
1479
1517
  const doc = DOMParser.parseFromString(`<body id="root">${removeDuplicateAttributes(output)}</body>`, "text/html");
1480
- traverse(doc, forApp, 0, { firstImageFound: false }, parentDomain, seoContext);
1518
+ traverse(doc, forApp, 0, { firstImageFound: false }, parentDomain, seoContext, renderOptions);
1481
1519
  output = serializer.serializeToString(doc);
1482
1520
  } catch (error) {
1483
1521
  try {
@@ -1490,7 +1528,7 @@ function markdownToHTML(input, forApp, parentDomain = "ecency.com", seoContext)
1490
1528
  });
1491
1529
  const repairedHtml = domSerializer(dom.children);
1492
1530
  const doc = DOMParser.parseFromString(`<body id="root">${removeDuplicateAttributes(repairedHtml)}</body>`, "text/html");
1493
- traverse(doc, forApp, 0, { firstImageFound: false }, parentDomain, seoContext);
1531
+ traverse(doc, forApp, 0, { firstImageFound: false }, parentDomain, seoContext, renderOptions);
1494
1532
  output = serializer.serializeToString(doc);
1495
1533
  } catch (fallbackError) {
1496
1534
  const escapedContent = he2.encode(output || md.render(input));
@@ -1518,18 +1556,18 @@ function cacheSet(key, value) {
1518
1556
  }
1519
1557
 
1520
1558
  // src/markdown-2-html.ts
1521
- function markdown2Html(obj, forApp = true, _webp = false, parentDomain = "ecency.com", seoContext) {
1559
+ function markdown2Html(obj, forApp = true, _webp = false, parentDomain = "ecency.com", seoContext, renderOptions) {
1522
1560
  if (typeof obj === "string") {
1523
1561
  const cleanedStr = cleanReply(obj);
1524
- return markdownToHTML(cleanedStr, forApp, parentDomain, seoContext);
1562
+ return markdownToHTML(cleanedStr, forApp, parentDomain, seoContext, renderOptions);
1525
1563
  }
1526
- const key = `${makeEntryCacheKey(obj)}-md-${forApp ? "app" : "site"}-${parentDomain}${seoContext ? `-seo${seoContext.authorReputation ?? ""}-${seoContext.postPayout ?? ""}` : ""}`;
1564
+ const key = `${makeEntryCacheKey(obj)}-md-${forApp ? "app" : "site"}-${parentDomain}${seoContext ? `-seo${seoContext.authorReputation ?? ""}-${seoContext.postPayout ?? ""}` : ""}${renderOptions?.embedVideosDirectly ? "-embed" : ""}`;
1527
1565
  const item = cacheGet(key);
1528
1566
  if (item) {
1529
1567
  return item;
1530
1568
  }
1531
1569
  const cleanBody = cleanReply(obj.body);
1532
- const res = markdownToHTML(cleanBody, forApp, parentDomain, seoContext);
1570
+ const res = markdownToHTML(cleanBody, forApp, parentDomain, seoContext, renderOptions);
1533
1571
  cacheSet(key, res);
1534
1572
  return res;
1535
1573
  }