@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.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var xmldom = require('@xmldom/xmldom');
|
|
4
|
+
var he2 = require('he');
|
|
4
5
|
var xss = require('xss');
|
|
5
6
|
var multihash = require('multihashes');
|
|
6
7
|
var querystring = require('querystring');
|
|
@@ -9,7 +10,6 @@ var remarkable = require('remarkable');
|
|
|
9
10
|
var linkify$1 = require('remarkable/linkify');
|
|
10
11
|
var htmlparser2 = require('htmlparser2');
|
|
11
12
|
var domSerializerModule = require('dom-serializer');
|
|
12
|
-
var he = require('he');
|
|
13
13
|
|
|
14
14
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
15
|
|
|
@@ -31,12 +31,12 @@ function _interopNamespace(e) {
|
|
|
31
31
|
return Object.freeze(n);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
var he2__default = /*#__PURE__*/_interopDefault(he2);
|
|
34
35
|
var xss__default = /*#__PURE__*/_interopDefault(xss);
|
|
35
36
|
var multihash__default = /*#__PURE__*/_interopDefault(multihash);
|
|
36
37
|
var querystring__default = /*#__PURE__*/_interopDefault(querystring);
|
|
37
38
|
var htmlparser2__namespace = /*#__PURE__*/_interopNamespace(htmlparser2);
|
|
38
39
|
var domSerializerModule__namespace = /*#__PURE__*/_interopNamespace(domSerializerModule);
|
|
39
|
-
var he__default = /*#__PURE__*/_interopDefault(he);
|
|
40
40
|
|
|
41
41
|
// src/consts/white-list.const.ts
|
|
42
42
|
var WHITE_LIST = [
|
|
@@ -162,6 +162,13 @@ var ALLOWED_ATTRIBUTES = {
|
|
|
162
162
|
"decoding",
|
|
163
163
|
"itemprop"
|
|
164
164
|
],
|
|
165
|
+
// Responsive image content-negotiation wrapper emitted for web/self-hosted
|
|
166
|
+
// (forApp === false). Without these entries `xss` silently collapses the
|
|
167
|
+
// <picture>/<source> to a bare <img>. `source` attrs are further constrained
|
|
168
|
+
// in sanitize-html (srcset must be a proxy /p/ URL; type must be avif/webp;
|
|
169
|
+
// a type-less <source> is dropped post-pass).
|
|
170
|
+
"picture": [],
|
|
171
|
+
"source": ["type", "srcset", "sizes"],
|
|
165
172
|
"span": ["class", "id", "data-align"],
|
|
166
173
|
"iframe": ["src", "class", "frameborder", "allowfullscreen", "webkitallowfullscreen", "mozallowfullscreen", "sandbox"],
|
|
167
174
|
"video": ["src", "controls", "poster"],
|
|
@@ -205,8 +212,14 @@ function createParser() {
|
|
|
205
212
|
});
|
|
206
213
|
}
|
|
207
214
|
var DOMParser = createParser();
|
|
208
|
-
|
|
209
|
-
|
|
215
|
+
function decodeImageSrc(src) {
|
|
216
|
+
const entityDecoded = he2__default.default.decode(src);
|
|
217
|
+
try {
|
|
218
|
+
return decodeURIComponent(entityDecoded).trim();
|
|
219
|
+
} catch {
|
|
220
|
+
return entityDecoded.trim();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
210
223
|
function isSpaceChar(c) {
|
|
211
224
|
return c === 32 || c === 9 || c === 10 || c === 13 || c === 12;
|
|
212
225
|
}
|
|
@@ -462,33 +475,6 @@ function removeChildNodes(node) {
|
|
|
462
475
|
node.removeChild(node.firstChild);
|
|
463
476
|
}
|
|
464
477
|
}
|
|
465
|
-
var decodeEntities = (input) => input.replace(/&#(\d+);?/g, (_, dec) => String.fromCodePoint(Number(dec))).replace(/&#x([0-9a-f]+);?/gi, (_, hex) => String.fromCodePoint(parseInt(hex, 16)));
|
|
466
|
-
function sanitizeHtml(html) {
|
|
467
|
-
return xss__default.default(html, {
|
|
468
|
-
whiteList: ALLOWED_ATTRIBUTES,
|
|
469
|
-
stripIgnoreTag: true,
|
|
470
|
-
stripIgnoreTagBody: ["style"],
|
|
471
|
-
css: false,
|
|
472
|
-
// block style attrs entirely for safety
|
|
473
|
-
onTagAttr: (tag, name, value) => {
|
|
474
|
-
const decoded = decodeEntities(value.trim());
|
|
475
|
-
const decodedLower = decoded.toLowerCase();
|
|
476
|
-
if (name.startsWith("on")) return "";
|
|
477
|
-
if (tag === "img" && name === "src" && !/^https?:\/\//.test(decodedLower)) return "";
|
|
478
|
-
if (tag === "img" && name === "srcset") {
|
|
479
|
-
const candidates = decoded.split(",").map((c) => c.trim().split(/\s+/)[0]);
|
|
480
|
-
if (candidates.some((url) => !/^https?:\/\//i.test(url))) return "";
|
|
481
|
-
}
|
|
482
|
-
if (tag === "video" && ["src", "poster"].includes(name) && !/^https?:\/\//.test(decodedLower)) return "";
|
|
483
|
-
if (tag === "img" && ["dynsrc", "lowsrc"].includes(name)) return "";
|
|
484
|
-
if (tag === "span" && name === "class" && decoded.toLowerCase().trim() === "wr") return "";
|
|
485
|
-
if (name === "id") {
|
|
486
|
-
if (!ID_WHITELIST.test(decoded)) return "";
|
|
487
|
-
}
|
|
488
|
-
return void 0;
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
478
|
var proxyBase = "https://i.ecency.com";
|
|
493
479
|
var urlHashCache = new lruCache.LRUCache({ max: 500 });
|
|
494
480
|
function getUrlHash(url) {
|
|
@@ -524,7 +510,7 @@ function getLatestUrl(str) {
|
|
|
524
510
|
const [last] = [...str.replace(/https?:\/\//g, "\n$&").trim().split("\n")].reverse();
|
|
525
511
|
return last;
|
|
526
512
|
}
|
|
527
|
-
function
|
|
513
|
+
function proxifyForFormat(url, width = 0, height = 0, format = "match", opts = {}) {
|
|
528
514
|
if (!url || typeof url !== "string" || !isValidUrl(url)) {
|
|
529
515
|
return "";
|
|
530
516
|
}
|
|
@@ -541,7 +527,7 @@ function proxifyImageSrc(url, width = 0, height = 0, _format = "match", opts = {
|
|
|
541
527
|
const realUrl = getLatestUrl(url);
|
|
542
528
|
const pHash = extractPHash(realUrl);
|
|
543
529
|
const options = {
|
|
544
|
-
format
|
|
530
|
+
format,
|
|
545
531
|
mode: "fit"
|
|
546
532
|
};
|
|
547
533
|
if (width > 0) {
|
|
@@ -560,29 +546,127 @@ function proxifyImageSrc(url, width = 0, height = 0, _format = "match", opts = {
|
|
|
560
546
|
const b58url = getUrlHash(realUrl.toString());
|
|
561
547
|
return `${proxyBase}/p/${b58url}?${qs}`;
|
|
562
548
|
}
|
|
549
|
+
function proxifyImageSrc(url, width = 0, height = 0, _format = "match", opts = {}) {
|
|
550
|
+
return proxifyForFormat(url, width, height, "match", opts);
|
|
551
|
+
}
|
|
563
552
|
var SRCSET_WIDTHS = [320, 600, 800, 1024, 1280];
|
|
564
553
|
function buildSrcSet(url) {
|
|
554
|
+
return buildSrcSetForFormat(url, "match");
|
|
555
|
+
}
|
|
556
|
+
function buildSrcSetForFormat(url, format = "match") {
|
|
565
557
|
if (!url || typeof url !== "string") return "";
|
|
566
558
|
const proxyPrefix = `${proxyBase}/p/`;
|
|
559
|
+
let result;
|
|
567
560
|
if (url.startsWith(proxyPrefix)) {
|
|
568
561
|
const rest = url.slice(proxyPrefix.length);
|
|
569
562
|
const q = rest.indexOf("?");
|
|
570
563
|
const phash = extractPHash(url) || (q >= 0 ? rest.slice(0, q) : rest);
|
|
571
|
-
|
|
564
|
+
result = SRCSET_WIDTHS.map((w) => `${proxyBase}/p/${phash}?format=${format}&mode=fit&width=${w} ${w}w`).join(", ");
|
|
565
|
+
} else {
|
|
566
|
+
result = SRCSET_WIDTHS.map((w) => {
|
|
567
|
+
const proxied = proxifyForFormat(url, w, 0, format);
|
|
568
|
+
return proxied ? `${proxied} ${w}w` : "";
|
|
569
|
+
}).filter(Boolean).join(", ");
|
|
570
|
+
}
|
|
571
|
+
if (format !== "match" && result && !result.split(",").every((c) => c.includes(`format=${format}`))) {
|
|
572
|
+
return "";
|
|
572
573
|
}
|
|
573
|
-
return
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
574
|
+
return result;
|
|
575
|
+
}
|
|
576
|
+
var STATIC_RASTER_PATH_EXT = /\.(?:jpe?g|png|webp)$/i;
|
|
577
|
+
var SIZED_PROXY_PATH = /^\/\d+x\d+\//;
|
|
578
|
+
function isPictureEligibleRawUrl(rawUrl) {
|
|
579
|
+
if (!rawUrl || typeof rawUrl !== "string") return false;
|
|
580
|
+
let u;
|
|
581
|
+
try {
|
|
582
|
+
u = new URL(rawUrl);
|
|
583
|
+
} catch {
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return false;
|
|
587
|
+
const host = `${u.protocol}//${u.host}`;
|
|
588
|
+
const isProxyHost = host === proxyBase || host === "https://images.ecency.com";
|
|
589
|
+
if (isProxyHost && (u.pathname.startsWith("/p/") || u.pathname.startsWith("/u/") || SIZED_PROXY_PATH.test(u.pathname))) {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
return STATIC_RASTER_PATH_EXT.test(u.pathname);
|
|
593
|
+
}
|
|
594
|
+
function buildPictureSources(rawUrl) {
|
|
595
|
+
if (!isPictureEligibleRawUrl(rawUrl)) return null;
|
|
596
|
+
const avif = buildSrcSetForFormat(rawUrl, "avif");
|
|
597
|
+
const webp = buildSrcSetForFormat(rawUrl, "webp");
|
|
598
|
+
if (!avif || !webp) return null;
|
|
599
|
+
return { avif, webp };
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/methods/sanitize-html.method.ts
|
|
603
|
+
var decodeEntities = (input) => input.replace(/&#(\d+);?/g, (_, dec) => String.fromCodePoint(Number(dec))).replace(/&#x([0-9a-f]+);?/gi, (_, hex) => String.fromCodePoint(parseInt(hex, 16)));
|
|
604
|
+
var isProxyPSrcset = (srcset) => {
|
|
605
|
+
const base = trimTrailingSlash(getProxyBase());
|
|
606
|
+
const candidates = srcset.split(",").map((c) => c.trim().split(/\s+/)[0]).filter(Boolean);
|
|
607
|
+
return candidates.length > 0 && candidates.every((url) => url.startsWith(`${base}/p/`));
|
|
608
|
+
};
|
|
609
|
+
function sanitizeHtml(html) {
|
|
610
|
+
const cleaned = xss__default.default(html, {
|
|
611
|
+
whiteList: ALLOWED_ATTRIBUTES,
|
|
612
|
+
stripIgnoreTag: true,
|
|
613
|
+
stripIgnoreTagBody: ["style"],
|
|
614
|
+
css: false,
|
|
615
|
+
// block style attrs entirely for safety
|
|
616
|
+
onTagAttr: (tag, name, value) => {
|
|
617
|
+
const decoded = decodeEntities(value.trim());
|
|
618
|
+
const decodedLower = decoded.toLowerCase();
|
|
619
|
+
if (name.startsWith("on")) return "";
|
|
620
|
+
if (tag === "img" && name === "src" && !/^https?:\/\//.test(decodedLower)) return "";
|
|
621
|
+
if (tag === "img" && name === "srcset") {
|
|
622
|
+
const candidates = decoded.split(",").map((c) => c.trim().split(/\s+/)[0]);
|
|
623
|
+
if (candidates.some((url) => !/^https?:\/\//i.test(url))) return "";
|
|
624
|
+
}
|
|
625
|
+
if (tag === "source" && name === "srcset" && !isProxyPSrcset(decoded)) return "";
|
|
626
|
+
if (tag === "source" && name === "type" && decodedLower !== "image/avif" && decodedLower !== "image/webp") return "";
|
|
627
|
+
if (tag === "video" && ["src", "poster"].includes(name) && !/^https?:\/\//.test(decodedLower)) return "";
|
|
628
|
+
if (tag === "img" && ["dynsrc", "lowsrc"].includes(name)) return "";
|
|
629
|
+
if (tag === "span" && name === "class" && decoded.toLowerCase().trim() === "wr") return "";
|
|
630
|
+
if (name === "id") {
|
|
631
|
+
if (!ID_WHITELIST.test(decoded)) return "";
|
|
632
|
+
}
|
|
633
|
+
return void 0;
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
return cleaned.replace(
|
|
637
|
+
/<source\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi,
|
|
638
|
+
(t) => /\btype\s*=\s*["'](?:image\/avif|image\/webp)["']/i.test(t) ? t : ""
|
|
639
|
+
);
|
|
577
640
|
}
|
|
578
641
|
|
|
579
642
|
// src/methods/img.method.ts
|
|
580
643
|
var IMAGE_SIZES = "(max-width: 768px) 100vw, 700px";
|
|
581
|
-
function
|
|
644
|
+
function wrapInPicture(el, rawUrl) {
|
|
645
|
+
const parent = el.parentNode;
|
|
646
|
+
if (!parent) return;
|
|
647
|
+
if (parent.nodeName && parent.nodeName.toLowerCase() === "picture") return;
|
|
648
|
+
const sources = buildPictureSources(rawUrl);
|
|
649
|
+
if (!sources) return;
|
|
650
|
+
const doc = el.ownerDocument;
|
|
651
|
+
if (!doc) return;
|
|
652
|
+
const sizes = el.getAttribute("sizes") || IMAGE_SIZES;
|
|
653
|
+
const picture = doc.createElement("picture");
|
|
654
|
+
const avif = doc.createElement("source");
|
|
655
|
+
avif.setAttribute("type", "image/avif");
|
|
656
|
+
avif.setAttribute("srcset", sources.avif);
|
|
657
|
+
avif.setAttribute("sizes", sizes);
|
|
658
|
+
const webp = doc.createElement("source");
|
|
659
|
+
webp.setAttribute("type", "image/webp");
|
|
660
|
+
webp.setAttribute("srcset", sources.webp);
|
|
661
|
+
webp.setAttribute("sizes", sizes);
|
|
662
|
+
parent.insertBefore(picture, el);
|
|
663
|
+
picture.appendChild(avif);
|
|
664
|
+
picture.appendChild(webp);
|
|
665
|
+
picture.appendChild(el);
|
|
666
|
+
}
|
|
667
|
+
function img(el, state, forApp = true) {
|
|
582
668
|
const src = el.getAttribute("src") || "";
|
|
583
|
-
const decodedSrc =
|
|
584
|
-
src.replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(dec)).replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
585
|
-
).trim();
|
|
669
|
+
const decodedSrc = decodeImageSrc(src);
|
|
586
670
|
["onerror", "dynsrc", "lowsrc", "width", "height"].forEach((attr) => el.removeAttribute(attr));
|
|
587
671
|
const isInvalid = !src || decodedSrc.startsWith("javascript") || decodedSrc.startsWith("vbscript") || decodedSrc === "x";
|
|
588
672
|
if (isInvalid) {
|
|
@@ -621,6 +705,9 @@ function img(el, state) {
|
|
|
621
705
|
el.setAttribute("srcset", srcset);
|
|
622
706
|
el.setAttribute("sizes", IMAGE_SIZES);
|
|
623
707
|
}
|
|
708
|
+
if (!forApp) {
|
|
709
|
+
wrapInPicture(el, decodedSrc);
|
|
710
|
+
}
|
|
624
711
|
}
|
|
625
712
|
} else if (shouldReplace && hasAlreadyProxied) {
|
|
626
713
|
if (src.startsWith(`${base}/p/`)) {
|
|
@@ -632,16 +719,17 @@ function img(el, state) {
|
|
|
632
719
|
}
|
|
633
720
|
}
|
|
634
721
|
}
|
|
635
|
-
function createImageHTML(src, isLCP) {
|
|
636
|
-
const
|
|
722
|
+
function createImageHTML(src, isLCP, forApp = true) {
|
|
723
|
+
const decoded = decodeImageSrc(src);
|
|
724
|
+
const proxified = proxifyImageSrc(decoded, 0, 0, "match", { forceProxy: true });
|
|
637
725
|
if (!proxified) return "";
|
|
638
726
|
const base = trimTrailingSlash(getProxyBase());
|
|
639
|
-
const isAlreadyProxied =
|
|
640
|
-
const srcset = isAlreadyProxied ? "" : buildSrcSet(
|
|
727
|
+
const isAlreadyProxied = decoded.startsWith(`${base}/u/`) || new RegExp(`^${base.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/\\d+x\\d+/`).test(decoded);
|
|
728
|
+
const srcset = isAlreadyProxied ? "" : buildSrcSet(decoded);
|
|
641
729
|
const loading = isLCP ? "eager" : "lazy";
|
|
642
730
|
const fetch = isLCP ? 'fetchpriority="high"' : 'decoding="async"';
|
|
643
731
|
const srcsetAttr = srcset ? `srcset="${srcset}" sizes="${IMAGE_SIZES}"` : "";
|
|
644
|
-
|
|
732
|
+
const imgTag = `<img
|
|
645
733
|
class="markdown-img-link"
|
|
646
734
|
src="${proxified}"
|
|
647
735
|
${srcsetAttr}
|
|
@@ -649,6 +737,13 @@ function createImageHTML(src, isLCP) {
|
|
|
649
737
|
${fetch}
|
|
650
738
|
itemprop="image"
|
|
651
739
|
/>`;
|
|
740
|
+
if (!forApp) {
|
|
741
|
+
const sources = buildPictureSources(decoded);
|
|
742
|
+
if (sources) {
|
|
743
|
+
return `<picture><source type="image/avif" srcset="${sources.avif}" sizes="${IMAGE_SIZES}" /><source type="image/webp" srcset="${sources.webp}" sizes="${IMAGE_SIZES}" />${imgTag}</picture>`;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return imgTag;
|
|
652
747
|
}
|
|
653
748
|
|
|
654
749
|
// src/methods/a.method.ts
|
|
@@ -715,7 +810,7 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
|
715
810
|
}
|
|
716
811
|
if (href.match(IMG_REGEX) && href.trim().replace(/&/g, "&") === getSerializedInnerHTML(el).trim().replace(/&/g, "&")) {
|
|
717
812
|
const isLCP = false;
|
|
718
|
-
const imgHTML = createImageHTML(href, isLCP);
|
|
813
|
+
const imgHTML = createImageHTML(href, isLCP, forApp);
|
|
719
814
|
const doc = DOMParser.parseFromString(imgHTML, "text/html");
|
|
720
815
|
const replaceNode = doc.body?.firstChild || doc.firstChild;
|
|
721
816
|
if (replaceNode && el.parentNode) {
|
|
@@ -756,7 +851,7 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
|
756
851
|
el.setAttribute("data-author", author);
|
|
757
852
|
el.setAttribute("data-permlink", permlink);
|
|
758
853
|
} else {
|
|
759
|
-
const h =
|
|
854
|
+
const h = `/@${author}/${permlink}`;
|
|
760
855
|
el.setAttribute("href", h);
|
|
761
856
|
el.setAttribute("data-is-inline", "" + isInline);
|
|
762
857
|
}
|
|
@@ -835,7 +930,7 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
|
835
930
|
el.setAttribute("data-author", author);
|
|
836
931
|
el.setAttribute("data-permlink", permlink);
|
|
837
932
|
} else {
|
|
838
|
-
const h =
|
|
933
|
+
const h = `/@${author}/${permlink}`;
|
|
839
934
|
el.setAttribute("href", h);
|
|
840
935
|
el.setAttribute("data-is-inline", "" + isInline);
|
|
841
936
|
}
|
|
@@ -900,7 +995,7 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
|
900
995
|
el.setAttribute("data-author", author);
|
|
901
996
|
el.setAttribute("data-permlink", permlink);
|
|
902
997
|
} else {
|
|
903
|
-
const h =
|
|
998
|
+
const h = `/@${author}/${permlink}`;
|
|
904
999
|
el.setAttribute("href", h);
|
|
905
1000
|
el.setAttribute("data-is-inline", "" + isInline);
|
|
906
1001
|
}
|
|
@@ -986,7 +1081,7 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
|
986
1081
|
el.setAttribute("data-author", author);
|
|
987
1082
|
el.setAttribute("data-permlink", permlink);
|
|
988
1083
|
} else {
|
|
989
|
-
const h =
|
|
1084
|
+
const h = `/@${author}/${permlink}`;
|
|
990
1085
|
el.setAttribute("href", h);
|
|
991
1086
|
el.setAttribute("data-is-inline", "" + isInline);
|
|
992
1087
|
}
|
|
@@ -1530,7 +1625,7 @@ function linkify(content, forApp, renderOptions) {
|
|
|
1530
1625
|
const attrs = forApp ? `href="https://ecency.com/@${authorLower}/${permlink}"` : `href="/@${authorLower}/${permlink}"`;
|
|
1531
1626
|
return `${preceding}<a class="markdown-profile-link" ${attrs}>@${authorLower}/${permlink}</a>`;
|
|
1532
1627
|
} else {
|
|
1533
|
-
const attrs = forApp ? `data-author="${authorLower}" data-tag="${tag}" data-permlink="${permlink}"` : `href="
|
|
1628
|
+
const attrs = forApp ? `data-author="${authorLower}" data-tag="${tag}" data-permlink="${permlink}"` : `href="/@${authorLower}/${permlink}"`;
|
|
1534
1629
|
return `${preceding}<a class="markdown-post-link" ${attrs}>@${authorLower}/${permlink}</a>`;
|
|
1535
1630
|
}
|
|
1536
1631
|
}
|
|
@@ -1545,7 +1640,7 @@ function linkify(content, forApp, renderOptions) {
|
|
|
1545
1640
|
const attrs = forApp ? `href="https://ecency.com/@${uu}/${permlink}"` : `href="/@${uu}/${permlink}"`;
|
|
1546
1641
|
return ` <a class="markdown-profile-link" ${attrs}>@${uu}/${permlink}</a>`;
|
|
1547
1642
|
} else {
|
|
1548
|
-
const attrs = forApp ? `data-author="${uu}" data-tag="post" data-permlink="${permlink}"` : `href="
|
|
1643
|
+
const attrs = forApp ? `data-author="${uu}" data-tag="post" data-permlink="${permlink}"` : `href="/@${uu}/${permlink}"`;
|
|
1549
1644
|
return ` <a class="markdown-post-link" ${attrs}>@${uu}/${permlink}</a>`;
|
|
1550
1645
|
}
|
|
1551
1646
|
}
|
|
@@ -1554,7 +1649,7 @@ function linkify(content, forApp, renderOptions) {
|
|
|
1554
1649
|
content = content.replace(IMG_REGEX, (imglink) => {
|
|
1555
1650
|
const isLCP = !firstImageUsed;
|
|
1556
1651
|
firstImageUsed = true;
|
|
1557
|
-
return createImageHTML(imglink, isLCP);
|
|
1652
|
+
return createImageHTML(imglink, isLCP, forApp);
|
|
1558
1653
|
});
|
|
1559
1654
|
authorPlaceholders.forEach(({ placeholder, html }) => {
|
|
1560
1655
|
content = content.replace(placeholder, html);
|
|
@@ -1596,7 +1691,7 @@ function text(node, forApp, renderOptions) {
|
|
|
1596
1691
|
}
|
|
1597
1692
|
if (nodeValue.match(IMG_REGEX)) {
|
|
1598
1693
|
const isLCP = false;
|
|
1599
|
-
const imageHTML = createImageHTML(nodeValue, isLCP);
|
|
1694
|
+
const imageHTML = createImageHTML(nodeValue, isLCP, forApp);
|
|
1600
1695
|
const doc = DOMParser.parseFromString(imageHTML, "text/html");
|
|
1601
1696
|
const replaceNode = doc.body?.firstChild || doc.firstChild;
|
|
1602
1697
|
if (replaceNode) {
|
|
@@ -1640,7 +1735,7 @@ function text(node, forApp, renderOptions) {
|
|
|
1640
1735
|
if (!tag || !/^[a-z0-9_-]+$/i.test(tag)) return;
|
|
1641
1736
|
if (!isValidUsername(author)) return;
|
|
1642
1737
|
if (!isValidPermlink(permlink)) return;
|
|
1643
|
-
const attrs = forApp ? `data-tag="${tag}" data-author="${author}" data-permlink="${permlink}" class="markdown-post-link"` : `class="markdown-post-link" href="
|
|
1738
|
+
const attrs = forApp ? `data-tag="${tag}" data-author="${author}" data-permlink="${permlink}" class="markdown-post-link"` : `class="markdown-post-link" href="/@${author}/${permlink}"`;
|
|
1644
1739
|
const doc = DOMParser.parseFromString(
|
|
1645
1740
|
`<a ${attrs}>/@${author}/${permlink}</a>`,
|
|
1646
1741
|
"text/html"
|
|
@@ -1672,7 +1767,7 @@ function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, p
|
|
|
1672
1767
|
text(child, forApp);
|
|
1673
1768
|
}
|
|
1674
1769
|
if (child.nodeName.toLowerCase() === "img") {
|
|
1675
|
-
img(child, state);
|
|
1770
|
+
img(child, state, forApp);
|
|
1676
1771
|
}
|
|
1677
1772
|
if (child.nodeName.toLowerCase() === "p") {
|
|
1678
1773
|
p(child);
|
|
@@ -1895,8 +1990,11 @@ var INLINE_CODE_RE = /`[^`\n]*`/g;
|
|
|
1895
1990
|
var INDENTED_CODE_RE = /^(?: {4}|\t).+$/gm;
|
|
1896
1991
|
var MD_IMAGE_RE = /!\[[^\]]*\]\(\s*([^)\s]+)(?:\s+["'][^"']*["'])?\s*\)/;
|
|
1897
1992
|
var HTML_IMAGE_RE = /<img\b[^>]*?\bsrc\s*=\s*["']([^"']+)["']/i;
|
|
1993
|
+
var BARE_IMAGE_RE = /(^|\s)(https?:\/\/[^\s<>"'()[\]]+\.(?:tiff?|jpe?g|gif|png|svg|ico|heic|webp|arw)(?:[?#][^\s<>"'()[\]]*)?)/im;
|
|
1994
|
+
var MD_LINK_RE = /\[([^\]]*)\]\(\s*([^)\s]+)(?:\s+["'][^"']*["'])?\s*\)/g;
|
|
1995
|
+
var IMG_HREF_RE = /https?:\/\/.*\.(?:tiff?|jpe?g|gif|png|svg|ico|heic|webp|arw)/i;
|
|
1898
1996
|
var SAFE_URL_RE = /^https?:\/\//i;
|
|
1899
|
-
function findFirstImageUrl(body) {
|
|
1997
|
+
function findFirstImageUrl(body, includeBareUrls = false) {
|
|
1900
1998
|
if (!body) return null;
|
|
1901
1999
|
const cleaned = body.replace(BACKTICK_FENCE_RE, "").replace(TILDE_FENCE_RE, "").replace(INLINE_CODE_RE, "").replace(INDENTED_CODE_RE, "");
|
|
1902
2000
|
const mdMatch = cleaned.match(MD_IMAGE_RE);
|
|
@@ -1907,17 +2005,33 @@ function findFirstImageUrl(body) {
|
|
|
1907
2005
|
return null;
|
|
1908
2006
|
}
|
|
1909
2007
|
}
|
|
1910
|
-
const
|
|
1911
|
-
|
|
1912
|
-
if (
|
|
1913
|
-
|
|
2008
|
+
const candidates = [];
|
|
2009
|
+
if (mdMatch) candidates.push({ url: mdMatch[1], pos: mdMatch.index ?? 0 });
|
|
2010
|
+
if (htmlMatch && htmlMatch[1] && SAFE_URL_RE.test(htmlMatch[1])) {
|
|
2011
|
+
candidates.push({ url: htmlMatch[1], pos: htmlMatch.index ?? 0 });
|
|
1914
2012
|
}
|
|
1915
|
-
if (
|
|
1916
|
-
|
|
1917
|
-
|
|
2013
|
+
if (includeBareUrls) {
|
|
2014
|
+
const bareMatch = cleaned.match(BARE_IMAGE_RE);
|
|
2015
|
+
if (bareMatch && bareMatch[2] && SAFE_URL_RE.test(bareMatch[2])) {
|
|
2016
|
+
candidates.push({ url: bareMatch[2], pos: (bareMatch.index ?? 0) + bareMatch[1].length });
|
|
2017
|
+
}
|
|
2018
|
+
const deAmp = (s) => s.trim().replace(/&/g, "&");
|
|
2019
|
+
for (const m of cleaned.matchAll(MD_LINK_RE)) {
|
|
2020
|
+
const idx = m.index ?? 0;
|
|
2021
|
+
if (idx > 0 && cleaned[idx - 1] === "!") continue;
|
|
2022
|
+
const href = m[2];
|
|
2023
|
+
if (href && SAFE_URL_RE.test(href) && IMG_HREF_RE.test(href) && deAmp(m[1]) === deAmp(href)) {
|
|
2024
|
+
candidates.push({ url: href, pos: idx });
|
|
2025
|
+
break;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
if (candidates.length === 0) return null;
|
|
2030
|
+
candidates.sort((a2, b) => a2.pos - b.pos);
|
|
2031
|
+
return candidates[0].url;
|
|
1918
2032
|
}
|
|
1919
2033
|
function proxifyFound(src, width, height, format) {
|
|
1920
|
-
const decoded =
|
|
2034
|
+
const decoded = he2__default.default.decode(src);
|
|
1921
2035
|
if (isGifLink(decoded)) {
|
|
1922
2036
|
return proxifyImageSrc(decoded, 0, 0, format);
|
|
1923
2037
|
}
|
|
@@ -1935,7 +2049,7 @@ function getImage(entry, width = 0, height = 0, format = "match") {
|
|
|
1935
2049
|
}
|
|
1936
2050
|
}
|
|
1937
2051
|
if (meta && typeof meta.image === "string" && meta.image.length > 0) {
|
|
1938
|
-
const decodedImage =
|
|
2052
|
+
const decodedImage = he2__default.default.decode(meta.image);
|
|
1939
2053
|
if (isGifLink(decodedImage)) {
|
|
1940
2054
|
return proxifyImageSrc(decodedImage, 0, 0, format);
|
|
1941
2055
|
}
|
|
@@ -1943,7 +2057,7 @@ function getImage(entry, width = 0, height = 0, format = "match") {
|
|
|
1943
2057
|
}
|
|
1944
2058
|
if (meta && meta.image && !!meta.image.length && meta.image[0]) {
|
|
1945
2059
|
if (typeof meta.image[0] === "string") {
|
|
1946
|
-
const decodedImage =
|
|
2060
|
+
const decodedImage = he2__default.default.decode(meta.image[0]);
|
|
1947
2061
|
if (isGifLink(decodedImage)) {
|
|
1948
2062
|
return proxifyImageSrc(decodedImage, 0, 0, format);
|
|
1949
2063
|
}
|
|
@@ -1973,6 +2087,30 @@ function getImage(entry, width = 0, height = 0, format = "match") {
|
|
|
1973
2087
|
}
|
|
1974
2088
|
return null;
|
|
1975
2089
|
}
|
|
2090
|
+
function getEntryImageRawUrl(obj) {
|
|
2091
|
+
if (typeof obj === "string") {
|
|
2092
|
+
const src = findFirstImageUrl(obj, true);
|
|
2093
|
+
return src ? decodeImageSrc(src) : null;
|
|
2094
|
+
}
|
|
2095
|
+
let meta;
|
|
2096
|
+
if (typeof obj.json_metadata === "object") {
|
|
2097
|
+
meta = obj.json_metadata;
|
|
2098
|
+
} else {
|
|
2099
|
+
try {
|
|
2100
|
+
meta = JSON.parse(obj.json_metadata);
|
|
2101
|
+
} catch (e) {
|
|
2102
|
+
meta = null;
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
if (meta && typeof meta.image === "string" && meta.image.length > 0) {
|
|
2106
|
+
return decodeImageSrc(meta.image);
|
|
2107
|
+
}
|
|
2108
|
+
if (meta && meta.image && !!meta.image.length && typeof meta.image[0] === "string" && meta.image[0].length > 0) {
|
|
2109
|
+
return decodeImageSrc(meta.image[0]);
|
|
2110
|
+
}
|
|
2111
|
+
const bodySrc = findFirstImageUrl(obj.body, true);
|
|
2112
|
+
return bodySrc ? decodeImageSrc(bodySrc) : null;
|
|
2113
|
+
}
|
|
1976
2114
|
function catchPostImage(obj, width = 0, height = 0, format = "match") {
|
|
1977
2115
|
if (typeof obj === "string") {
|
|
1978
2116
|
const fast = findFirstImageUrl(obj);
|
|
@@ -2074,7 +2212,7 @@ function postBodySummary(entryBody, length = 200, platform = "web") {
|
|
|
2074
2212
|
text2 = joint(text2.split(" "), length);
|
|
2075
2213
|
}
|
|
2076
2214
|
if (text2) {
|
|
2077
|
-
text2 =
|
|
2215
|
+
text2 = he2__default.default.decode(text2);
|
|
2078
2216
|
}
|
|
2079
2217
|
return text2;
|
|
2080
2218
|
}
|
|
@@ -2096,8 +2234,12 @@ function getPostBodySummary(obj, length, platform) {
|
|
|
2096
2234
|
|
|
2097
2235
|
exports.IMAGE_SIZES = IMAGE_SIZES;
|
|
2098
2236
|
exports.SECTION_LIST = SECTION_LIST;
|
|
2237
|
+
exports.buildPictureSources = buildPictureSources;
|
|
2099
2238
|
exports.buildSrcSet = buildSrcSet;
|
|
2239
|
+
exports.buildSrcSetForFormat = buildSrcSetForFormat;
|
|
2100
2240
|
exports.catchPostImage = catchPostImage;
|
|
2241
|
+
exports.getEntryImageRawUrl = getEntryImageRawUrl;
|
|
2242
|
+
exports.isPictureEligibleRawUrl = isPictureEligibleRawUrl;
|
|
2101
2243
|
exports.isValidPermlink = isValidPermlink;
|
|
2102
2244
|
exports.postBodySummary = getPostBodySummary;
|
|
2103
2245
|
exports.proxifyImageSrc = proxifyImageSrc;
|