@ecency/render-helper 2.5.9 → 2.5.11
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 +46 -2
- package/dist/browser/index.js +208 -70
- package/dist/browser/index.js.map +1 -1
- package/dist/node/index.cjs +212 -70
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.mjs +208 -70
- package/dist/node/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/node/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DOMParser as DOMParser$1, XMLSerializer } from '@xmldom/xmldom';
|
|
2
|
+
import he2 from 'he';
|
|
2
3
|
import xss from 'xss';
|
|
3
4
|
import multihash from 'multihashes';
|
|
4
5
|
import querystring from 'querystring';
|
|
@@ -7,7 +8,6 @@ import { Remarkable } from 'remarkable';
|
|
|
7
8
|
import { linkify as linkify$1 } from 'remarkable/linkify';
|
|
8
9
|
import * as htmlparser2 from 'htmlparser2';
|
|
9
10
|
import * as domSerializerModule from 'dom-serializer';
|
|
10
|
-
import he from 'he';
|
|
11
11
|
|
|
12
12
|
// src/consts/white-list.const.ts
|
|
13
13
|
var WHITE_LIST = [
|
|
@@ -133,6 +133,13 @@ var ALLOWED_ATTRIBUTES = {
|
|
|
133
133
|
"decoding",
|
|
134
134
|
"itemprop"
|
|
135
135
|
],
|
|
136
|
+
// Responsive image content-negotiation wrapper emitted for web/self-hosted
|
|
137
|
+
// (forApp === false). Without these entries `xss` silently collapses the
|
|
138
|
+
// <picture>/<source> to a bare <img>. `source` attrs are further constrained
|
|
139
|
+
// in sanitize-html (srcset must be a proxy /p/ URL; type must be avif/webp;
|
|
140
|
+
// a type-less <source> is dropped post-pass).
|
|
141
|
+
"picture": [],
|
|
142
|
+
"source": ["type", "srcset", "sizes"],
|
|
136
143
|
"span": ["class", "id", "data-align"],
|
|
137
144
|
"iframe": ["src", "class", "frameborder", "allowfullscreen", "webkitallowfullscreen", "mozallowfullscreen", "sandbox"],
|
|
138
145
|
"video": ["src", "controls", "poster"],
|
|
@@ -176,8 +183,14 @@ function createParser() {
|
|
|
176
183
|
});
|
|
177
184
|
}
|
|
178
185
|
var DOMParser = createParser();
|
|
179
|
-
|
|
180
|
-
|
|
186
|
+
function decodeImageSrc(src) {
|
|
187
|
+
const entityDecoded = he2.decode(src);
|
|
188
|
+
try {
|
|
189
|
+
return decodeURIComponent(entityDecoded).trim();
|
|
190
|
+
} catch {
|
|
191
|
+
return entityDecoded.trim();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
181
194
|
function isSpaceChar(c) {
|
|
182
195
|
return c === 32 || c === 9 || c === 10 || c === 13 || c === 12;
|
|
183
196
|
}
|
|
@@ -433,33 +446,6 @@ function removeChildNodes(node) {
|
|
|
433
446
|
node.removeChild(node.firstChild);
|
|
434
447
|
}
|
|
435
448
|
}
|
|
436
|
-
var decodeEntities = (input) => input.replace(/&#(\d+);?/g, (_, dec) => String.fromCodePoint(Number(dec))).replace(/&#x([0-9a-f]+);?/gi, (_, hex) => String.fromCodePoint(parseInt(hex, 16)));
|
|
437
|
-
function sanitizeHtml(html) {
|
|
438
|
-
return xss(html, {
|
|
439
|
-
whiteList: ALLOWED_ATTRIBUTES,
|
|
440
|
-
stripIgnoreTag: true,
|
|
441
|
-
stripIgnoreTagBody: ["style"],
|
|
442
|
-
css: false,
|
|
443
|
-
// block style attrs entirely for safety
|
|
444
|
-
onTagAttr: (tag, name, value) => {
|
|
445
|
-
const decoded = decodeEntities(value.trim());
|
|
446
|
-
const decodedLower = decoded.toLowerCase();
|
|
447
|
-
if (name.startsWith("on")) return "";
|
|
448
|
-
if (tag === "img" && name === "src" && !/^https?:\/\//.test(decodedLower)) return "";
|
|
449
|
-
if (tag === "img" && name === "srcset") {
|
|
450
|
-
const candidates = decoded.split(",").map((c) => c.trim().split(/\s+/)[0]);
|
|
451
|
-
if (candidates.some((url) => !/^https?:\/\//i.test(url))) return "";
|
|
452
|
-
}
|
|
453
|
-
if (tag === "video" && ["src", "poster"].includes(name) && !/^https?:\/\//.test(decodedLower)) return "";
|
|
454
|
-
if (tag === "img" && ["dynsrc", "lowsrc"].includes(name)) return "";
|
|
455
|
-
if (tag === "span" && name === "class" && decoded.toLowerCase().trim() === "wr") return "";
|
|
456
|
-
if (name === "id") {
|
|
457
|
-
if (!ID_WHITELIST.test(decoded)) return "";
|
|
458
|
-
}
|
|
459
|
-
return void 0;
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
449
|
var proxyBase = "https://i.ecency.com";
|
|
464
450
|
var urlHashCache = new LRUCache({ max: 500 });
|
|
465
451
|
function getUrlHash(url) {
|
|
@@ -495,7 +481,7 @@ function getLatestUrl(str) {
|
|
|
495
481
|
const [last] = [...str.replace(/https?:\/\//g, "\n$&").trim().split("\n")].reverse();
|
|
496
482
|
return last;
|
|
497
483
|
}
|
|
498
|
-
function
|
|
484
|
+
function proxifyForFormat(url, width = 0, height = 0, format = "match", opts = {}) {
|
|
499
485
|
if (!url || typeof url !== "string" || !isValidUrl(url)) {
|
|
500
486
|
return "";
|
|
501
487
|
}
|
|
@@ -512,7 +498,7 @@ function proxifyImageSrc(url, width = 0, height = 0, _format = "match", opts = {
|
|
|
512
498
|
const realUrl = getLatestUrl(url);
|
|
513
499
|
const pHash = extractPHash(realUrl);
|
|
514
500
|
const options = {
|
|
515
|
-
format
|
|
501
|
+
format,
|
|
516
502
|
mode: "fit"
|
|
517
503
|
};
|
|
518
504
|
if (width > 0) {
|
|
@@ -531,29 +517,127 @@ function proxifyImageSrc(url, width = 0, height = 0, _format = "match", opts = {
|
|
|
531
517
|
const b58url = getUrlHash(realUrl.toString());
|
|
532
518
|
return `${proxyBase}/p/${b58url}?${qs}`;
|
|
533
519
|
}
|
|
520
|
+
function proxifyImageSrc(url, width = 0, height = 0, _format = "match", opts = {}) {
|
|
521
|
+
return proxifyForFormat(url, width, height, "match", opts);
|
|
522
|
+
}
|
|
534
523
|
var SRCSET_WIDTHS = [320, 600, 800, 1024, 1280];
|
|
535
524
|
function buildSrcSet(url) {
|
|
525
|
+
return buildSrcSetForFormat(url, "match");
|
|
526
|
+
}
|
|
527
|
+
function buildSrcSetForFormat(url, format = "match") {
|
|
536
528
|
if (!url || typeof url !== "string") return "";
|
|
537
529
|
const proxyPrefix = `${proxyBase}/p/`;
|
|
530
|
+
let result;
|
|
538
531
|
if (url.startsWith(proxyPrefix)) {
|
|
539
532
|
const rest = url.slice(proxyPrefix.length);
|
|
540
533
|
const q = rest.indexOf("?");
|
|
541
534
|
const phash = extractPHash(url) || (q >= 0 ? rest.slice(0, q) : rest);
|
|
542
|
-
|
|
535
|
+
result = SRCSET_WIDTHS.map((w) => `${proxyBase}/p/${phash}?format=${format}&mode=fit&width=${w} ${w}w`).join(", ");
|
|
536
|
+
} else {
|
|
537
|
+
result = SRCSET_WIDTHS.map((w) => {
|
|
538
|
+
const proxied = proxifyForFormat(url, w, 0, format);
|
|
539
|
+
return proxied ? `${proxied} ${w}w` : "";
|
|
540
|
+
}).filter(Boolean).join(", ");
|
|
541
|
+
}
|
|
542
|
+
if (format !== "match" && result && !result.split(",").every((c) => c.includes(`format=${format}`))) {
|
|
543
|
+
return "";
|
|
543
544
|
}
|
|
544
|
-
return
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
545
|
+
return result;
|
|
546
|
+
}
|
|
547
|
+
var STATIC_RASTER_PATH_EXT = /\.(?:jpe?g|png|webp)$/i;
|
|
548
|
+
var SIZED_PROXY_PATH = /^\/\d+x\d+\//;
|
|
549
|
+
function isPictureEligibleRawUrl(rawUrl) {
|
|
550
|
+
if (!rawUrl || typeof rawUrl !== "string") return false;
|
|
551
|
+
let u;
|
|
552
|
+
try {
|
|
553
|
+
u = new URL(rawUrl);
|
|
554
|
+
} catch {
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return false;
|
|
558
|
+
const host = `${u.protocol}//${u.host}`;
|
|
559
|
+
const isProxyHost = host === proxyBase || host === "https://images.ecency.com";
|
|
560
|
+
if (isProxyHost && (u.pathname.startsWith("/p/") || u.pathname.startsWith("/u/") || SIZED_PROXY_PATH.test(u.pathname))) {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
return STATIC_RASTER_PATH_EXT.test(u.pathname);
|
|
564
|
+
}
|
|
565
|
+
function buildPictureSources(rawUrl) {
|
|
566
|
+
if (!isPictureEligibleRawUrl(rawUrl)) return null;
|
|
567
|
+
const avif = buildSrcSetForFormat(rawUrl, "avif");
|
|
568
|
+
const webp = buildSrcSetForFormat(rawUrl, "webp");
|
|
569
|
+
if (!avif || !webp) return null;
|
|
570
|
+
return { avif, webp };
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// src/methods/sanitize-html.method.ts
|
|
574
|
+
var decodeEntities = (input) => input.replace(/&#(\d+);?/g, (_, dec) => String.fromCodePoint(Number(dec))).replace(/&#x([0-9a-f]+);?/gi, (_, hex) => String.fromCodePoint(parseInt(hex, 16)));
|
|
575
|
+
var isProxyPSrcset = (srcset) => {
|
|
576
|
+
const base = trimTrailingSlash(getProxyBase());
|
|
577
|
+
const candidates = srcset.split(",").map((c) => c.trim().split(/\s+/)[0]).filter(Boolean);
|
|
578
|
+
return candidates.length > 0 && candidates.every((url) => url.startsWith(`${base}/p/`));
|
|
579
|
+
};
|
|
580
|
+
function sanitizeHtml(html) {
|
|
581
|
+
const cleaned = xss(html, {
|
|
582
|
+
whiteList: ALLOWED_ATTRIBUTES,
|
|
583
|
+
stripIgnoreTag: true,
|
|
584
|
+
stripIgnoreTagBody: ["style"],
|
|
585
|
+
css: false,
|
|
586
|
+
// block style attrs entirely for safety
|
|
587
|
+
onTagAttr: (tag, name, value) => {
|
|
588
|
+
const decoded = decodeEntities(value.trim());
|
|
589
|
+
const decodedLower = decoded.toLowerCase();
|
|
590
|
+
if (name.startsWith("on")) return "";
|
|
591
|
+
if (tag === "img" && name === "src" && !/^https?:\/\//.test(decodedLower)) return "";
|
|
592
|
+
if (tag === "img" && name === "srcset") {
|
|
593
|
+
const candidates = decoded.split(",").map((c) => c.trim().split(/\s+/)[0]);
|
|
594
|
+
if (candidates.some((url) => !/^https?:\/\//i.test(url))) return "";
|
|
595
|
+
}
|
|
596
|
+
if (tag === "source" && name === "srcset" && !isProxyPSrcset(decoded)) return "";
|
|
597
|
+
if (tag === "source" && name === "type" && decodedLower !== "image/avif" && decodedLower !== "image/webp") return "";
|
|
598
|
+
if (tag === "video" && ["src", "poster"].includes(name) && !/^https?:\/\//.test(decodedLower)) return "";
|
|
599
|
+
if (tag === "img" && ["dynsrc", "lowsrc"].includes(name)) return "";
|
|
600
|
+
if (tag === "span" && name === "class" && decoded.toLowerCase().trim() === "wr") return "";
|
|
601
|
+
if (name === "id") {
|
|
602
|
+
if (!ID_WHITELIST.test(decoded)) return "";
|
|
603
|
+
}
|
|
604
|
+
return void 0;
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
return cleaned.replace(
|
|
608
|
+
/<source\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi,
|
|
609
|
+
(t) => /\btype\s*=\s*["'](?:image\/avif|image\/webp)["']/i.test(t) ? t : ""
|
|
610
|
+
);
|
|
548
611
|
}
|
|
549
612
|
|
|
550
613
|
// src/methods/img.method.ts
|
|
551
614
|
var IMAGE_SIZES = "(max-width: 768px) 100vw, 700px";
|
|
552
|
-
function
|
|
615
|
+
function wrapInPicture(el, rawUrl) {
|
|
616
|
+
const parent = el.parentNode;
|
|
617
|
+
if (!parent) return;
|
|
618
|
+
if (parent.nodeName && parent.nodeName.toLowerCase() === "picture") return;
|
|
619
|
+
const sources = buildPictureSources(rawUrl);
|
|
620
|
+
if (!sources) return;
|
|
621
|
+
const doc = el.ownerDocument;
|
|
622
|
+
if (!doc) return;
|
|
623
|
+
const sizes = el.getAttribute("sizes") || IMAGE_SIZES;
|
|
624
|
+
const picture = doc.createElement("picture");
|
|
625
|
+
const avif = doc.createElement("source");
|
|
626
|
+
avif.setAttribute("type", "image/avif");
|
|
627
|
+
avif.setAttribute("srcset", sources.avif);
|
|
628
|
+
avif.setAttribute("sizes", sizes);
|
|
629
|
+
const webp = doc.createElement("source");
|
|
630
|
+
webp.setAttribute("type", "image/webp");
|
|
631
|
+
webp.setAttribute("srcset", sources.webp);
|
|
632
|
+
webp.setAttribute("sizes", sizes);
|
|
633
|
+
parent.insertBefore(picture, el);
|
|
634
|
+
picture.appendChild(avif);
|
|
635
|
+
picture.appendChild(webp);
|
|
636
|
+
picture.appendChild(el);
|
|
637
|
+
}
|
|
638
|
+
function img(el, state, forApp = true) {
|
|
553
639
|
const src = el.getAttribute("src") || "";
|
|
554
|
-
const decodedSrc =
|
|
555
|
-
src.replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(dec)).replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
556
|
-
).trim();
|
|
640
|
+
const decodedSrc = decodeImageSrc(src);
|
|
557
641
|
["onerror", "dynsrc", "lowsrc", "width", "height"].forEach((attr) => el.removeAttribute(attr));
|
|
558
642
|
const isInvalid = !src || decodedSrc.startsWith("javascript") || decodedSrc.startsWith("vbscript") || decodedSrc === "x";
|
|
559
643
|
if (isInvalid) {
|
|
@@ -592,6 +676,9 @@ function img(el, state) {
|
|
|
592
676
|
el.setAttribute("srcset", srcset);
|
|
593
677
|
el.setAttribute("sizes", IMAGE_SIZES);
|
|
594
678
|
}
|
|
679
|
+
if (!forApp) {
|
|
680
|
+
wrapInPicture(el, decodedSrc);
|
|
681
|
+
}
|
|
595
682
|
}
|
|
596
683
|
} else if (shouldReplace && hasAlreadyProxied) {
|
|
597
684
|
if (src.startsWith(`${base}/p/`)) {
|
|
@@ -603,16 +690,17 @@ function img(el, state) {
|
|
|
603
690
|
}
|
|
604
691
|
}
|
|
605
692
|
}
|
|
606
|
-
function createImageHTML(src, isLCP) {
|
|
607
|
-
const
|
|
693
|
+
function createImageHTML(src, isLCP, forApp = true) {
|
|
694
|
+
const decoded = decodeImageSrc(src);
|
|
695
|
+
const proxified = proxifyImageSrc(decoded, 0, 0, "match", { forceProxy: true });
|
|
608
696
|
if (!proxified) return "";
|
|
609
697
|
const base = trimTrailingSlash(getProxyBase());
|
|
610
|
-
const isAlreadyProxied =
|
|
611
|
-
const srcset = isAlreadyProxied ? "" : buildSrcSet(
|
|
698
|
+
const isAlreadyProxied = decoded.startsWith(`${base}/u/`) || new RegExp(`^${base.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/\\d+x\\d+/`).test(decoded);
|
|
699
|
+
const srcset = isAlreadyProxied ? "" : buildSrcSet(decoded);
|
|
612
700
|
const loading = isLCP ? "eager" : "lazy";
|
|
613
701
|
const fetch = isLCP ? 'fetchpriority="high"' : 'decoding="async"';
|
|
614
702
|
const srcsetAttr = srcset ? `srcset="${srcset}" sizes="${IMAGE_SIZES}"` : "";
|
|
615
|
-
|
|
703
|
+
const imgTag = `<img
|
|
616
704
|
class="markdown-img-link"
|
|
617
705
|
src="${proxified}"
|
|
618
706
|
${srcsetAttr}
|
|
@@ -620,6 +708,13 @@ function createImageHTML(src, isLCP) {
|
|
|
620
708
|
${fetch}
|
|
621
709
|
itemprop="image"
|
|
622
710
|
/>`;
|
|
711
|
+
if (!forApp) {
|
|
712
|
+
const sources = buildPictureSources(decoded);
|
|
713
|
+
if (sources) {
|
|
714
|
+
return `<picture><source type="image/avif" srcset="${sources.avif}" sizes="${IMAGE_SIZES}" /><source type="image/webp" srcset="${sources.webp}" sizes="${IMAGE_SIZES}" />${imgTag}</picture>`;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return imgTag;
|
|
623
718
|
}
|
|
624
719
|
|
|
625
720
|
// src/methods/a.method.ts
|
|
@@ -686,7 +781,7 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
|
686
781
|
}
|
|
687
782
|
if (href.match(IMG_REGEX) && href.trim().replace(/&/g, "&") === getSerializedInnerHTML(el).trim().replace(/&/g, "&")) {
|
|
688
783
|
const isLCP = false;
|
|
689
|
-
const imgHTML = createImageHTML(href, isLCP);
|
|
784
|
+
const imgHTML = createImageHTML(href, isLCP, forApp);
|
|
690
785
|
const doc = DOMParser.parseFromString(imgHTML, "text/html");
|
|
691
786
|
const replaceNode = doc.body?.firstChild || doc.firstChild;
|
|
692
787
|
if (replaceNode && el.parentNode) {
|
|
@@ -727,7 +822,7 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
|
727
822
|
el.setAttribute("data-author", author);
|
|
728
823
|
el.setAttribute("data-permlink", permlink);
|
|
729
824
|
} else {
|
|
730
|
-
const h =
|
|
825
|
+
const h = `/@${author}/${permlink}`;
|
|
731
826
|
el.setAttribute("href", h);
|
|
732
827
|
el.setAttribute("data-is-inline", "" + isInline);
|
|
733
828
|
}
|
|
@@ -806,7 +901,7 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
|
806
901
|
el.setAttribute("data-author", author);
|
|
807
902
|
el.setAttribute("data-permlink", permlink);
|
|
808
903
|
} else {
|
|
809
|
-
const h =
|
|
904
|
+
const h = `/@${author}/${permlink}`;
|
|
810
905
|
el.setAttribute("href", h);
|
|
811
906
|
el.setAttribute("data-is-inline", "" + isInline);
|
|
812
907
|
}
|
|
@@ -871,7 +966,7 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
|
871
966
|
el.setAttribute("data-author", author);
|
|
872
967
|
el.setAttribute("data-permlink", permlink);
|
|
873
968
|
} else {
|
|
874
|
-
const h =
|
|
969
|
+
const h = `/@${author}/${permlink}`;
|
|
875
970
|
el.setAttribute("href", h);
|
|
876
971
|
el.setAttribute("data-is-inline", "" + isInline);
|
|
877
972
|
}
|
|
@@ -957,7 +1052,7 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
|
957
1052
|
el.setAttribute("data-author", author);
|
|
958
1053
|
el.setAttribute("data-permlink", permlink);
|
|
959
1054
|
} else {
|
|
960
|
-
const h =
|
|
1055
|
+
const h = `/@${author}/${permlink}`;
|
|
961
1056
|
el.setAttribute("href", h);
|
|
962
1057
|
el.setAttribute("data-is-inline", "" + isInline);
|
|
963
1058
|
}
|
|
@@ -1501,7 +1596,7 @@ function linkify(content, forApp, renderOptions) {
|
|
|
1501
1596
|
const attrs = forApp ? `href="https://ecency.com/@${authorLower}/${permlink}"` : `href="/@${authorLower}/${permlink}"`;
|
|
1502
1597
|
return `${preceding}<a class="markdown-profile-link" ${attrs}>@${authorLower}/${permlink}</a>`;
|
|
1503
1598
|
} else {
|
|
1504
|
-
const attrs = forApp ? `data-author="${authorLower}" data-tag="${tag}" data-permlink="${permlink}"` : `href="
|
|
1599
|
+
const attrs = forApp ? `data-author="${authorLower}" data-tag="${tag}" data-permlink="${permlink}"` : `href="/@${authorLower}/${permlink}"`;
|
|
1505
1600
|
return `${preceding}<a class="markdown-post-link" ${attrs}>@${authorLower}/${permlink}</a>`;
|
|
1506
1601
|
}
|
|
1507
1602
|
}
|
|
@@ -1516,7 +1611,7 @@ function linkify(content, forApp, renderOptions) {
|
|
|
1516
1611
|
const attrs = forApp ? `href="https://ecency.com/@${uu}/${permlink}"` : `href="/@${uu}/${permlink}"`;
|
|
1517
1612
|
return ` <a class="markdown-profile-link" ${attrs}>@${uu}/${permlink}</a>`;
|
|
1518
1613
|
} else {
|
|
1519
|
-
const attrs = forApp ? `data-author="${uu}" data-tag="post" data-permlink="${permlink}"` : `href="
|
|
1614
|
+
const attrs = forApp ? `data-author="${uu}" data-tag="post" data-permlink="${permlink}"` : `href="/@${uu}/${permlink}"`;
|
|
1520
1615
|
return ` <a class="markdown-post-link" ${attrs}>@${uu}/${permlink}</a>`;
|
|
1521
1616
|
}
|
|
1522
1617
|
}
|
|
@@ -1525,7 +1620,7 @@ function linkify(content, forApp, renderOptions) {
|
|
|
1525
1620
|
content = content.replace(IMG_REGEX, (imglink) => {
|
|
1526
1621
|
const isLCP = !firstImageUsed;
|
|
1527
1622
|
firstImageUsed = true;
|
|
1528
|
-
return createImageHTML(imglink, isLCP);
|
|
1623
|
+
return createImageHTML(imglink, isLCP, forApp);
|
|
1529
1624
|
});
|
|
1530
1625
|
authorPlaceholders.forEach(({ placeholder, html }) => {
|
|
1531
1626
|
content = content.replace(placeholder, html);
|
|
@@ -1567,7 +1662,7 @@ function text(node, forApp, renderOptions) {
|
|
|
1567
1662
|
}
|
|
1568
1663
|
if (nodeValue.match(IMG_REGEX)) {
|
|
1569
1664
|
const isLCP = false;
|
|
1570
|
-
const imageHTML = createImageHTML(nodeValue, isLCP);
|
|
1665
|
+
const imageHTML = createImageHTML(nodeValue, isLCP, forApp);
|
|
1571
1666
|
const doc = DOMParser.parseFromString(imageHTML, "text/html");
|
|
1572
1667
|
const replaceNode = doc.body?.firstChild || doc.firstChild;
|
|
1573
1668
|
if (replaceNode) {
|
|
@@ -1611,7 +1706,7 @@ function text(node, forApp, renderOptions) {
|
|
|
1611
1706
|
if (!tag || !/^[a-z0-9_-]+$/i.test(tag)) return;
|
|
1612
1707
|
if (!isValidUsername(author)) return;
|
|
1613
1708
|
if (!isValidPermlink(permlink)) return;
|
|
1614
|
-
const attrs = forApp ? `data-tag="${tag}" data-author="${author}" data-permlink="${permlink}" class="markdown-post-link"` : `class="markdown-post-link" href="
|
|
1709
|
+
const attrs = forApp ? `data-tag="${tag}" data-author="${author}" data-permlink="${permlink}" class="markdown-post-link"` : `class="markdown-post-link" href="/@${author}/${permlink}"`;
|
|
1615
1710
|
const doc = DOMParser.parseFromString(
|
|
1616
1711
|
`<a ${attrs}>/@${author}/${permlink}</a>`,
|
|
1617
1712
|
"text/html"
|
|
@@ -1643,7 +1738,7 @@ function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, p
|
|
|
1643
1738
|
text(child, forApp);
|
|
1644
1739
|
}
|
|
1645
1740
|
if (child.nodeName.toLowerCase() === "img") {
|
|
1646
|
-
img(child, state);
|
|
1741
|
+
img(child, state, forApp);
|
|
1647
1742
|
}
|
|
1648
1743
|
if (child.nodeName.toLowerCase() === "p") {
|
|
1649
1744
|
p(child);
|
|
@@ -1866,8 +1961,11 @@ var INLINE_CODE_RE = /`[^`\n]*`/g;
|
|
|
1866
1961
|
var INDENTED_CODE_RE = /^(?: {4}|\t).+$/gm;
|
|
1867
1962
|
var MD_IMAGE_RE = /!\[[^\]]*\]\(\s*([^)\s]+)(?:\s+["'][^"']*["'])?\s*\)/;
|
|
1868
1963
|
var HTML_IMAGE_RE = /<img\b[^>]*?\bsrc\s*=\s*["']([^"']+)["']/i;
|
|
1964
|
+
var BARE_IMAGE_RE = /(^|\s)(https?:\/\/[^\s<>"'()[\]]+\.(?:tiff?|jpe?g|gif|png|svg|ico|heic|webp|arw)(?:[?#][^\s<>"'()[\]]*)?)/im;
|
|
1965
|
+
var MD_LINK_RE = /\[([^\]]*)\]\(\s*([^)\s]+)(?:\s+["'][^"']*["'])?\s*\)/g;
|
|
1966
|
+
var IMG_HREF_RE = /https?:\/\/.*\.(?:tiff?|jpe?g|gif|png|svg|ico|heic|webp|arw)/i;
|
|
1869
1967
|
var SAFE_URL_RE = /^https?:\/\//i;
|
|
1870
|
-
function findFirstImageUrl(body) {
|
|
1968
|
+
function findFirstImageUrl(body, includeBareUrls = false) {
|
|
1871
1969
|
if (!body) return null;
|
|
1872
1970
|
const cleaned = body.replace(BACKTICK_FENCE_RE, "").replace(TILDE_FENCE_RE, "").replace(INLINE_CODE_RE, "").replace(INDENTED_CODE_RE, "");
|
|
1873
1971
|
const mdMatch = cleaned.match(MD_IMAGE_RE);
|
|
@@ -1878,17 +1976,33 @@ function findFirstImageUrl(body) {
|
|
|
1878
1976
|
return null;
|
|
1879
1977
|
}
|
|
1880
1978
|
}
|
|
1881
|
-
const
|
|
1882
|
-
|
|
1883
|
-
if (
|
|
1884
|
-
|
|
1979
|
+
const candidates = [];
|
|
1980
|
+
if (mdMatch) candidates.push({ url: mdMatch[1], pos: mdMatch.index ?? 0 });
|
|
1981
|
+
if (htmlMatch && htmlMatch[1] && SAFE_URL_RE.test(htmlMatch[1])) {
|
|
1982
|
+
candidates.push({ url: htmlMatch[1], pos: htmlMatch.index ?? 0 });
|
|
1885
1983
|
}
|
|
1886
|
-
if (
|
|
1887
|
-
|
|
1888
|
-
|
|
1984
|
+
if (includeBareUrls) {
|
|
1985
|
+
const bareMatch = cleaned.match(BARE_IMAGE_RE);
|
|
1986
|
+
if (bareMatch && bareMatch[2] && SAFE_URL_RE.test(bareMatch[2])) {
|
|
1987
|
+
candidates.push({ url: bareMatch[2], pos: (bareMatch.index ?? 0) + bareMatch[1].length });
|
|
1988
|
+
}
|
|
1989
|
+
const deAmp = (s) => s.trim().replace(/&/g, "&");
|
|
1990
|
+
for (const m of cleaned.matchAll(MD_LINK_RE)) {
|
|
1991
|
+
const idx = m.index ?? 0;
|
|
1992
|
+
if (idx > 0 && cleaned[idx - 1] === "!") continue;
|
|
1993
|
+
const href = m[2];
|
|
1994
|
+
if (href && SAFE_URL_RE.test(href) && IMG_HREF_RE.test(href) && deAmp(m[1]) === deAmp(href)) {
|
|
1995
|
+
candidates.push({ url: href, pos: idx });
|
|
1996
|
+
break;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
if (candidates.length === 0) return null;
|
|
2001
|
+
candidates.sort((a2, b) => a2.pos - b.pos);
|
|
2002
|
+
return candidates[0].url;
|
|
1889
2003
|
}
|
|
1890
2004
|
function proxifyFound(src, width, height, format) {
|
|
1891
|
-
const decoded =
|
|
2005
|
+
const decoded = he2.decode(src);
|
|
1892
2006
|
if (isGifLink(decoded)) {
|
|
1893
2007
|
return proxifyImageSrc(decoded, 0, 0, format);
|
|
1894
2008
|
}
|
|
@@ -1906,7 +2020,7 @@ function getImage(entry, width = 0, height = 0, format = "match") {
|
|
|
1906
2020
|
}
|
|
1907
2021
|
}
|
|
1908
2022
|
if (meta && typeof meta.image === "string" && meta.image.length > 0) {
|
|
1909
|
-
const decodedImage =
|
|
2023
|
+
const decodedImage = he2.decode(meta.image);
|
|
1910
2024
|
if (isGifLink(decodedImage)) {
|
|
1911
2025
|
return proxifyImageSrc(decodedImage, 0, 0, format);
|
|
1912
2026
|
}
|
|
@@ -1914,7 +2028,7 @@ function getImage(entry, width = 0, height = 0, format = "match") {
|
|
|
1914
2028
|
}
|
|
1915
2029
|
if (meta && meta.image && !!meta.image.length && meta.image[0]) {
|
|
1916
2030
|
if (typeof meta.image[0] === "string") {
|
|
1917
|
-
const decodedImage =
|
|
2031
|
+
const decodedImage = he2.decode(meta.image[0]);
|
|
1918
2032
|
if (isGifLink(decodedImage)) {
|
|
1919
2033
|
return proxifyImageSrc(decodedImage, 0, 0, format);
|
|
1920
2034
|
}
|
|
@@ -1944,6 +2058,30 @@ function getImage(entry, width = 0, height = 0, format = "match") {
|
|
|
1944
2058
|
}
|
|
1945
2059
|
return null;
|
|
1946
2060
|
}
|
|
2061
|
+
function getEntryImageRawUrl(obj) {
|
|
2062
|
+
if (typeof obj === "string") {
|
|
2063
|
+
const src = findFirstImageUrl(obj, true);
|
|
2064
|
+
return src ? decodeImageSrc(src) : null;
|
|
2065
|
+
}
|
|
2066
|
+
let meta;
|
|
2067
|
+
if (typeof obj.json_metadata === "object") {
|
|
2068
|
+
meta = obj.json_metadata;
|
|
2069
|
+
} else {
|
|
2070
|
+
try {
|
|
2071
|
+
meta = JSON.parse(obj.json_metadata);
|
|
2072
|
+
} catch (e) {
|
|
2073
|
+
meta = null;
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
if (meta && typeof meta.image === "string" && meta.image.length > 0) {
|
|
2077
|
+
return decodeImageSrc(meta.image);
|
|
2078
|
+
}
|
|
2079
|
+
if (meta && meta.image && !!meta.image.length && typeof meta.image[0] === "string" && meta.image[0].length > 0) {
|
|
2080
|
+
return decodeImageSrc(meta.image[0]);
|
|
2081
|
+
}
|
|
2082
|
+
const bodySrc = findFirstImageUrl(obj.body, true);
|
|
2083
|
+
return bodySrc ? decodeImageSrc(bodySrc) : null;
|
|
2084
|
+
}
|
|
1947
2085
|
function catchPostImage(obj, width = 0, height = 0, format = "match") {
|
|
1948
2086
|
if (typeof obj === "string") {
|
|
1949
2087
|
const fast = findFirstImageUrl(obj);
|
|
@@ -2045,7 +2183,7 @@ function postBodySummary(entryBody, length = 200, platform = "web") {
|
|
|
2045
2183
|
text2 = joint(text2.split(" "), length);
|
|
2046
2184
|
}
|
|
2047
2185
|
if (text2) {
|
|
2048
|
-
text2 =
|
|
2186
|
+
text2 = he2.decode(text2);
|
|
2049
2187
|
}
|
|
2050
2188
|
return text2;
|
|
2051
2189
|
}
|
|
@@ -2065,6 +2203,6 @@ function getPostBodySummary(obj, length, platform) {
|
|
|
2065
2203
|
return res;
|
|
2066
2204
|
}
|
|
2067
2205
|
|
|
2068
|
-
export { IMAGE_SIZES, SECTION_LIST, buildSrcSet, catchPostImage, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase, setSlowRenderThresholdMs, simpleMarkdownToHTML };
|
|
2206
|
+
export { IMAGE_SIZES, SECTION_LIST, buildPictureSources, buildSrcSet, buildSrcSetForFormat, catchPostImage, getEntryImageRawUrl, isPictureEligibleRawUrl, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase, setSlowRenderThresholdMs, simpleMarkdownToHTML };
|
|
2069
2207
|
//# sourceMappingURL=index.mjs.map
|
|
2070
2208
|
//# sourceMappingURL=index.mjs.map
|