@ecency/render-helper 2.4.25 → 2.4.27
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.
- package/dist/browser/index.d.ts +8 -2
- package/dist/browser/index.js +82 -40
- package/dist/browser/index.js.map +1 -1
- package/dist/node/index.cjs +82 -40
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.mjs +82 -40
- package/dist/node/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/browser/index.d.ts
CHANGED
|
@@ -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 };
|
package/dist/browser/index.js
CHANGED
|
@@ -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"].
|
|
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
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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 (
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
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
|
}
|
|
@@ -1184,24 +1214,33 @@ function p(el) {
|
|
|
1184
1214
|
}
|
|
1185
1215
|
|
|
1186
1216
|
// src/methods/linkify.method.ts
|
|
1187
|
-
function linkify(content, forApp) {
|
|
1217
|
+
function linkify(content, forApp, renderOptions) {
|
|
1188
1218
|
content = content.replace(/(^|\s|>)(#[-a-z\d]+)/gi, (tag) => {
|
|
1189
1219
|
if (/#[\d]+$/.test(tag)) return tag;
|
|
1190
1220
|
const preceding = /^\s|>/.test(tag) ? tag[0] : "";
|
|
1191
1221
|
tag = tag.replace(">", "");
|
|
1192
1222
|
const tag2 = tag.trim().substring(1);
|
|
1193
1223
|
const tagLower = tag2.toLowerCase();
|
|
1194
|
-
|
|
1195
|
-
|
|
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>`;
|
|
1196
1228
|
});
|
|
1229
|
+
const authorPlaceholders = [];
|
|
1197
1230
|
content = content.replace(
|
|
1198
1231
|
/(^|[^a-zA-Z0-9_!#$%&*@@/]|(^|[^a-zA-Z0-9_+~.-/]))[@@]([a-z][-.a-z\d^/]+[a-z\d])/gi,
|
|
1199
1232
|
(match, preceeding1, preceeding2, user) => {
|
|
1200
1233
|
const userLower = user.toLowerCase();
|
|
1201
1234
|
const preceedings = (preceeding1 || "") + (preceeding2 || "");
|
|
1202
1235
|
if (userLower.indexOf("/") === -1 && isValidUsername(user)) {
|
|
1203
|
-
|
|
1204
|
-
|
|
1236
|
+
if (!forApp) {
|
|
1237
|
+
const avatarSrc = `https://images.ecency.com/u/${userLower}/avatar/small`;
|
|
1238
|
+
const html = `${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>`;
|
|
1239
|
+
const placeholder = `\u200C${authorPlaceholders.length}\u200C`;
|
|
1240
|
+
authorPlaceholders.push({ placeholder, html });
|
|
1241
|
+
return placeholder;
|
|
1242
|
+
}
|
|
1243
|
+
return `${preceedings}<a class="markdown-author-link" data-author="${userLower}">@${user}</a>`;
|
|
1205
1244
|
} else {
|
|
1206
1245
|
return match;
|
|
1207
1246
|
}
|
|
@@ -1244,6 +1283,9 @@ function linkify(content, forApp) {
|
|
|
1244
1283
|
firstImageUsed = true;
|
|
1245
1284
|
return createImageHTML(imglink, isLCP);
|
|
1246
1285
|
});
|
|
1286
|
+
authorPlaceholders.forEach(({ placeholder, html }) => {
|
|
1287
|
+
content = content.replace(placeholder, html);
|
|
1288
|
+
});
|
|
1247
1289
|
return content;
|
|
1248
1290
|
}
|
|
1249
1291
|
|
|
@@ -1258,7 +1300,7 @@ function hasAncestor(node, tagNames) {
|
|
|
1258
1300
|
}
|
|
1259
1301
|
return false;
|
|
1260
1302
|
}
|
|
1261
|
-
function text(node, forApp) {
|
|
1303
|
+
function text(node, forApp, renderOptions) {
|
|
1262
1304
|
if (!node || !node.parentNode) {
|
|
1263
1305
|
return;
|
|
1264
1306
|
}
|
|
@@ -1339,7 +1381,7 @@ function text(node, forApp) {
|
|
|
1339
1381
|
}
|
|
1340
1382
|
|
|
1341
1383
|
// src/methods/traverse.method.ts
|
|
1342
|
-
function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, parentDomain = "ecency.com", seoContext) {
|
|
1384
|
+
function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
1343
1385
|
if (!node || !node.childNodes) {
|
|
1344
1386
|
return;
|
|
1345
1387
|
}
|
|
@@ -1348,7 +1390,7 @@ function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, p
|
|
|
1348
1390
|
const next = child.nextSibling;
|
|
1349
1391
|
const prev = child.previousSibling;
|
|
1350
1392
|
if (child.nodeName.toLowerCase() === "a") {
|
|
1351
|
-
a(child, forApp, parentDomain, seoContext);
|
|
1393
|
+
a(child, forApp, parentDomain, seoContext, renderOptions);
|
|
1352
1394
|
}
|
|
1353
1395
|
if (child.nodeName.toLowerCase() === "iframe") {
|
|
1354
1396
|
iframe(child, parentDomain, forApp);
|
|
@@ -1363,11 +1405,11 @@ function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, p
|
|
|
1363
1405
|
p(child);
|
|
1364
1406
|
}
|
|
1365
1407
|
if (child.parentNode) {
|
|
1366
|
-
traverse(child, forApp, depth + 1, state, parentDomain, seoContext);
|
|
1408
|
+
traverse(child, forApp, depth + 1, state, parentDomain, seoContext, renderOptions);
|
|
1367
1409
|
} else {
|
|
1368
1410
|
const possibleReplacement = next ? next.previousSibling : node.lastChild;
|
|
1369
1411
|
if (possibleReplacement && possibleReplacement !== prev && possibleReplacement.parentNode === node) {
|
|
1370
|
-
traverse(possibleReplacement, forApp, depth + 1, state, parentDomain, seoContext);
|
|
1412
|
+
traverse(possibleReplacement, forApp, depth + 1, state, parentDomain, seoContext, renderOptions);
|
|
1371
1413
|
}
|
|
1372
1414
|
}
|
|
1373
1415
|
child = next;
|
|
@@ -1420,7 +1462,7 @@ function fixBlockLevelTagsInParagraphs(html) {
|
|
|
1420
1462
|
html = html.replace(/<p><br>\s*<\/p>/g, "");
|
|
1421
1463
|
return html;
|
|
1422
1464
|
}
|
|
1423
|
-
function markdownToHTML(input, forApp, parentDomain = "ecency.com", seoContext) {
|
|
1465
|
+
function markdownToHTML(input, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
1424
1466
|
input = input.replace(new RegExp("https://leofinance.io/threads/view/", "g"), "/@");
|
|
1425
1467
|
input = input.replace(new RegExp("https://leofinance.io/posts/", "g"), "/@");
|
|
1426
1468
|
input = input.replace(new RegExp("https://leofinance.io/threads/", "g"), "/@");
|
|
@@ -1480,7 +1522,7 @@ function markdownToHTML(input, forApp, parentDomain = "ecency.com", seoContext)
|
|
|
1480
1522
|
output = md.render(input);
|
|
1481
1523
|
output = fixBlockLevelTagsInParagraphs(output);
|
|
1482
1524
|
const doc = DOMParser.parseFromString(`<body id="root">${removeDuplicateAttributes(output)}</body>`, "text/html");
|
|
1483
|
-
traverse(doc, forApp, 0, { firstImageFound: false }, parentDomain, seoContext);
|
|
1525
|
+
traverse(doc, forApp, 0, { firstImageFound: false }, parentDomain, seoContext, renderOptions);
|
|
1484
1526
|
output = serializer.serializeToString(doc);
|
|
1485
1527
|
} catch (error) {
|
|
1486
1528
|
try {
|
|
@@ -1493,7 +1535,7 @@ function markdownToHTML(input, forApp, parentDomain = "ecency.com", seoContext)
|
|
|
1493
1535
|
});
|
|
1494
1536
|
const repairedHtml = domSerializer(dom.children);
|
|
1495
1537
|
const doc = DOMParser.parseFromString(`<body id="root">${removeDuplicateAttributes(repairedHtml)}</body>`, "text/html");
|
|
1496
|
-
traverse(doc, forApp, 0, { firstImageFound: false }, parentDomain, seoContext);
|
|
1538
|
+
traverse(doc, forApp, 0, { firstImageFound: false }, parentDomain, seoContext, renderOptions);
|
|
1497
1539
|
output = serializer.serializeToString(doc);
|
|
1498
1540
|
} catch (fallbackError) {
|
|
1499
1541
|
const escapedContent = he2.encode(output || md.render(input));
|
|
@@ -1521,18 +1563,18 @@ function cacheSet(key, value) {
|
|
|
1521
1563
|
}
|
|
1522
1564
|
|
|
1523
1565
|
// src/markdown-2-html.ts
|
|
1524
|
-
function markdown2Html(obj, forApp = true, _webp = false, parentDomain = "ecency.com", seoContext) {
|
|
1566
|
+
function markdown2Html(obj, forApp = true, _webp = false, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
1525
1567
|
if (typeof obj === "string") {
|
|
1526
1568
|
const cleanedStr = cleanReply(obj);
|
|
1527
|
-
return markdownToHTML(cleanedStr, forApp, parentDomain, seoContext);
|
|
1569
|
+
return markdownToHTML(cleanedStr, forApp, parentDomain, seoContext, renderOptions);
|
|
1528
1570
|
}
|
|
1529
|
-
const key = `${makeEntryCacheKey(obj)}-md-${forApp ? "app" : "site"}-${parentDomain}${seoContext ? `-seo${seoContext.authorReputation ?? ""}-${seoContext.postPayout ?? ""}` : ""}`;
|
|
1571
|
+
const key = `${makeEntryCacheKey(obj)}-md-${forApp ? "app" : "site"}-${parentDomain}${seoContext ? `-seo${seoContext.authorReputation ?? ""}-${seoContext.postPayout ?? ""}` : ""}${renderOptions?.embedVideosDirectly ? "-embed" : ""}`;
|
|
1530
1572
|
const item = cacheGet(key);
|
|
1531
1573
|
if (item) {
|
|
1532
1574
|
return item;
|
|
1533
1575
|
}
|
|
1534
1576
|
const cleanBody = cleanReply(obj.body);
|
|
1535
|
-
const res = markdownToHTML(cleanBody, forApp, parentDomain, seoContext);
|
|
1577
|
+
const res = markdownToHTML(cleanBody, forApp, parentDomain, seoContext, renderOptions);
|
|
1536
1578
|
cacheSet(key, res);
|
|
1537
1579
|
return res;
|
|
1538
1580
|
}
|