@ecency/render-helper 2.5.10 → 2.5.12
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 +60 -2
- package/dist/browser/index.js +301 -63
- package/dist/browser/index.js.map +1 -1
- package/dist/node/index.cjs +306 -63
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.mjs +301 -63
- package/dist/node/index.mjs.map +1 -1
- package/package.json +1 -1
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"],
|
|
@@ -198,6 +205,96 @@ var ALLOWED_ATTRIBUTES = {
|
|
|
198
205
|
"del": [],
|
|
199
206
|
"ins": []
|
|
200
207
|
};
|
|
208
|
+
|
|
209
|
+
// src/consts/embed-hosts.const.ts
|
|
210
|
+
var ALLOWED_EMBED_HOSTS = /* @__PURE__ */ new Set([
|
|
211
|
+
// YouTube
|
|
212
|
+
"www.youtube.com",
|
|
213
|
+
"youtube.com",
|
|
214
|
+
"www.youtube-nocookie.com",
|
|
215
|
+
"youtube-nocookie.com",
|
|
216
|
+
// Vimeo
|
|
217
|
+
"player.vimeo.com",
|
|
218
|
+
// Twitch
|
|
219
|
+
"player.twitch.tv",
|
|
220
|
+
// DTube
|
|
221
|
+
"emb.d.tube",
|
|
222
|
+
// 3Speak (video + audio)
|
|
223
|
+
"play.3speak.tv",
|
|
224
|
+
"3speak.tv",
|
|
225
|
+
"audio.3speak.tv",
|
|
226
|
+
// Loom
|
|
227
|
+
"www.loom.com",
|
|
228
|
+
// Spotify
|
|
229
|
+
"open.spotify.com",
|
|
230
|
+
// SoundCloud
|
|
231
|
+
"w.soundcloud.com",
|
|
232
|
+
// BitChute
|
|
233
|
+
"www.bitchute.com",
|
|
234
|
+
"bitchute.com",
|
|
235
|
+
// Rumble
|
|
236
|
+
"www.rumble.com",
|
|
237
|
+
"rumble.com",
|
|
238
|
+
// Brighteon
|
|
239
|
+
"www.brighteon.com",
|
|
240
|
+
"brighteon.com",
|
|
241
|
+
// VIMM
|
|
242
|
+
"www.vimm.tv",
|
|
243
|
+
// BrandNewTube
|
|
244
|
+
"brandnewtube.com",
|
|
245
|
+
// LBRY / Odysee
|
|
246
|
+
"lbry.tv",
|
|
247
|
+
"odysee.com",
|
|
248
|
+
// Skatehive / Skatehype
|
|
249
|
+
"ipfs.skatehive.app",
|
|
250
|
+
"www.skatehype.com",
|
|
251
|
+
"skatehype.com",
|
|
252
|
+
// archive.org
|
|
253
|
+
"archive.org",
|
|
254
|
+
// Truvvl
|
|
255
|
+
"embed.truvvl.com",
|
|
256
|
+
// Aureal
|
|
257
|
+
"aureal-embed.web.app",
|
|
258
|
+
"www.aureal-embed.web.app"
|
|
259
|
+
// Dapplr (player.*.dapplr.in / *.dapplr.in) — host suffix, handled below
|
|
260
|
+
]);
|
|
261
|
+
var ALLOWED_EMBED_HOST_SUFFIXES = [".dapplr.in"];
|
|
262
|
+
var EMBED_HOST_PATH_PATTERNS = {
|
|
263
|
+
"www.youtube.com": /^\/embed\//,
|
|
264
|
+
"youtube.com": /^\/embed\//,
|
|
265
|
+
"www.youtube-nocookie.com": /^\/embed\//,
|
|
266
|
+
"youtube-nocookie.com": /^\/embed\//,
|
|
267
|
+
"player.vimeo.com": /^\/video\//,
|
|
268
|
+
"player.twitch.tv": /^\/$/,
|
|
269
|
+
// channel/video carried in the query string
|
|
270
|
+
"emb.d.tube": /^\/$/,
|
|
271
|
+
// dtube carries the ref in the #! fragment
|
|
272
|
+
"play.3speak.tv": /^\/(watch|embed)/,
|
|
273
|
+
"open.spotify.com": /^\/embed\//,
|
|
274
|
+
"www.loom.com": /^\/embed\//,
|
|
275
|
+
"www.bitchute.com": /^\/embed\//,
|
|
276
|
+
"bitchute.com": /^\/embed\//,
|
|
277
|
+
"www.rumble.com": /^\/embed\//,
|
|
278
|
+
"rumble.com": /^\/embed\//,
|
|
279
|
+
"www.brighteon.com": /^\/embed\//,
|
|
280
|
+
"brighteon.com": /^\/embed\//
|
|
281
|
+
};
|
|
282
|
+
function isAllowedEmbedSrc(value) {
|
|
283
|
+
if (!value) return false;
|
|
284
|
+
let url;
|
|
285
|
+
try {
|
|
286
|
+
url = new URL(value.trim());
|
|
287
|
+
} catch {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
if (url.protocol !== "https:") return false;
|
|
291
|
+
const host = url.hostname.toLowerCase();
|
|
292
|
+
const hostAllowed = ALLOWED_EMBED_HOSTS.has(host) || ALLOWED_EMBED_HOST_SUFFIXES.some((suffix) => host.endsWith(suffix));
|
|
293
|
+
if (!hostAllowed) return false;
|
|
294
|
+
const pathPattern = EMBED_HOST_PATH_PATTERNS[host];
|
|
295
|
+
if (pathPattern && !pathPattern.test(url.pathname)) return false;
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
201
298
|
function createParser() {
|
|
202
299
|
return new xmldom.DOMParser({
|
|
203
300
|
onError(level, msg) {
|
|
@@ -205,8 +302,14 @@ function createParser() {
|
|
|
205
302
|
});
|
|
206
303
|
}
|
|
207
304
|
var DOMParser = createParser();
|
|
208
|
-
|
|
209
|
-
|
|
305
|
+
function decodeImageSrc(src) {
|
|
306
|
+
const entityDecoded = he2__default.default.decode(src);
|
|
307
|
+
try {
|
|
308
|
+
return decodeURIComponent(entityDecoded).trim();
|
|
309
|
+
} catch {
|
|
310
|
+
return entityDecoded.trim();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
210
313
|
function isSpaceChar(c) {
|
|
211
314
|
return c === 32 || c === 9 || c === 10 || c === 13 || c === 12;
|
|
212
315
|
}
|
|
@@ -462,33 +565,6 @@ function removeChildNodes(node) {
|
|
|
462
565
|
node.removeChild(node.firstChild);
|
|
463
566
|
}
|
|
464
567
|
}
|
|
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
568
|
var proxyBase = "https://i.ecency.com";
|
|
493
569
|
var urlHashCache = new lruCache.LRUCache({ max: 500 });
|
|
494
570
|
function getUrlHash(url) {
|
|
@@ -524,7 +600,7 @@ function getLatestUrl(str) {
|
|
|
524
600
|
const [last] = [...str.replace(/https?:\/\//g, "\n$&").trim().split("\n")].reverse();
|
|
525
601
|
return last;
|
|
526
602
|
}
|
|
527
|
-
function
|
|
603
|
+
function proxifyForFormat(url, width = 0, height = 0, format = "match", opts = {}) {
|
|
528
604
|
if (!url || typeof url !== "string" || !isValidUrl(url)) {
|
|
529
605
|
return "";
|
|
530
606
|
}
|
|
@@ -541,7 +617,7 @@ function proxifyImageSrc(url, width = 0, height = 0, _format = "match", opts = {
|
|
|
541
617
|
const realUrl = getLatestUrl(url);
|
|
542
618
|
const pHash = extractPHash(realUrl);
|
|
543
619
|
const options = {
|
|
544
|
-
format
|
|
620
|
+
format,
|
|
545
621
|
mode: "fit"
|
|
546
622
|
};
|
|
547
623
|
if (width > 0) {
|
|
@@ -560,29 +636,137 @@ function proxifyImageSrc(url, width = 0, height = 0, _format = "match", opts = {
|
|
|
560
636
|
const b58url = getUrlHash(realUrl.toString());
|
|
561
637
|
return `${proxyBase}/p/${b58url}?${qs}`;
|
|
562
638
|
}
|
|
639
|
+
function proxifyImageSrc(url, width = 0, height = 0, _format = "match", opts = {}) {
|
|
640
|
+
return proxifyForFormat(url, width, height, "match", opts);
|
|
641
|
+
}
|
|
563
642
|
var SRCSET_WIDTHS = [320, 600, 800, 1024, 1280];
|
|
564
643
|
function buildSrcSet(url) {
|
|
644
|
+
return buildSrcSetForFormat(url, "match");
|
|
645
|
+
}
|
|
646
|
+
function buildSrcSetForFormat(url, format = "match") {
|
|
565
647
|
if (!url || typeof url !== "string") return "";
|
|
566
648
|
const proxyPrefix = `${proxyBase}/p/`;
|
|
649
|
+
let result;
|
|
567
650
|
if (url.startsWith(proxyPrefix)) {
|
|
568
651
|
const rest = url.slice(proxyPrefix.length);
|
|
569
652
|
const q = rest.indexOf("?");
|
|
570
653
|
const phash = extractPHash(url) || (q >= 0 ? rest.slice(0, q) : rest);
|
|
571
|
-
|
|
654
|
+
result = SRCSET_WIDTHS.map((w) => `${proxyBase}/p/${phash}?format=${format}&mode=fit&width=${w} ${w}w`).join(", ");
|
|
655
|
+
} else {
|
|
656
|
+
result = SRCSET_WIDTHS.map((w) => {
|
|
657
|
+
const proxied = proxifyForFormat(url, w, 0, format);
|
|
658
|
+
return proxied ? `${proxied} ${w}w` : "";
|
|
659
|
+
}).filter(Boolean).join(", ");
|
|
660
|
+
}
|
|
661
|
+
if (format !== "match" && result && !result.split(",").every((c) => c.includes(`format=${format}`))) {
|
|
662
|
+
return "";
|
|
572
663
|
}
|
|
573
|
-
return
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
664
|
+
return result;
|
|
665
|
+
}
|
|
666
|
+
var STATIC_RASTER_PATH_EXT = /\.(?:jpe?g|png|webp)$/i;
|
|
667
|
+
var SIZED_PROXY_PATH = /^\/\d+x\d+\//;
|
|
668
|
+
function isPictureEligibleRawUrl(rawUrl) {
|
|
669
|
+
if (!rawUrl || typeof rawUrl !== "string") return false;
|
|
670
|
+
let u;
|
|
671
|
+
try {
|
|
672
|
+
u = new URL(rawUrl);
|
|
673
|
+
} catch {
|
|
674
|
+
return false;
|
|
675
|
+
}
|
|
676
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return false;
|
|
677
|
+
const host = `${u.protocol}//${u.host}`;
|
|
678
|
+
const isProxyHost = host === proxyBase || host === "https://images.ecency.com";
|
|
679
|
+
if (isProxyHost && (u.pathname.startsWith("/p/") || u.pathname.startsWith("/u/") || SIZED_PROXY_PATH.test(u.pathname))) {
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
return STATIC_RASTER_PATH_EXT.test(u.pathname);
|
|
683
|
+
}
|
|
684
|
+
function buildPictureSources(rawUrl) {
|
|
685
|
+
if (!isPictureEligibleRawUrl(rawUrl)) return null;
|
|
686
|
+
const avif = buildSrcSetForFormat(rawUrl, "avif");
|
|
687
|
+
const webp = buildSrcSetForFormat(rawUrl, "webp");
|
|
688
|
+
if (!avif || !webp) return null;
|
|
689
|
+
return { avif, webp };
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// src/methods/sanitize-html.method.ts
|
|
693
|
+
var EMBED_SRC_DATA_ATTRS = /* @__PURE__ */ new Set(["data-embed-src", "data-video-href"]);
|
|
694
|
+
var isSafeNavValue = (value) => {
|
|
695
|
+
const trimmed = value.trim().replace(/[\t\n\r\f\v\0]/g, "").toLowerCase();
|
|
696
|
+
if (!trimmed) return false;
|
|
697
|
+
const isSafeScheme = /^(https?|mailto|hive|tel|web\+[a-z0-9.+-]+):/i.test(trimmed);
|
|
698
|
+
const isRelative = /^(\/\/|\/[^/]?|#|\?|[a-z0-9._\-]+(\/|$))/i.test(trimmed);
|
|
699
|
+
return isSafeScheme || isRelative;
|
|
700
|
+
};
|
|
701
|
+
var decodeEntities = (input) => input.replace(/&#(\d+);?/g, (_, dec) => String.fromCodePoint(Number(dec))).replace(/&#x([0-9a-f]+);?/gi, (_, hex) => String.fromCodePoint(parseInt(hex, 16)));
|
|
702
|
+
var isProxyPSrcset = (srcset) => {
|
|
703
|
+
const base = trimTrailingSlash(getProxyBase());
|
|
704
|
+
const candidates = srcset.split(",").map((c) => c.trim().split(/\s+/)[0]).filter(Boolean);
|
|
705
|
+
return candidates.length > 0 && candidates.every((url) => url.startsWith(`${base}/p/`));
|
|
706
|
+
};
|
|
707
|
+
function sanitizeHtml(html) {
|
|
708
|
+
const cleaned = xss__default.default(html, {
|
|
709
|
+
whiteList: ALLOWED_ATTRIBUTES,
|
|
710
|
+
stripIgnoreTag: true,
|
|
711
|
+
stripIgnoreTagBody: ["style"],
|
|
712
|
+
css: false,
|
|
713
|
+
// block style attrs entirely for safety
|
|
714
|
+
onTagAttr: (tag, name, value) => {
|
|
715
|
+
const decoded = decodeEntities(value.trim());
|
|
716
|
+
const decodedLower = decoded.toLowerCase();
|
|
717
|
+
if (name.startsWith("on")) return "";
|
|
718
|
+
if (tag === "img" && name === "src" && !/^https?:\/\//.test(decodedLower)) return "";
|
|
719
|
+
if (tag === "img" && name === "srcset") {
|
|
720
|
+
const candidates = decoded.split(",").map((c) => c.trim().split(/\s+/)[0]);
|
|
721
|
+
if (candidates.some((url) => !/^https?:\/\//i.test(url))) return "";
|
|
722
|
+
}
|
|
723
|
+
if (tag === "source" && name === "srcset" && !isProxyPSrcset(decoded)) return "";
|
|
724
|
+
if (tag === "source" && name === "type" && decodedLower !== "image/avif" && decodedLower !== "image/webp") return "";
|
|
725
|
+
if (tag === "video" && ["src", "poster"].includes(name) && !/^https?:\/\//.test(decodedLower)) return "";
|
|
726
|
+
if (tag === "img" && ["dynsrc", "lowsrc"].includes(name)) return "";
|
|
727
|
+
if (tag === "span" && name === "class" && decoded.toLowerCase().trim() === "wr") return "";
|
|
728
|
+
if (EMBED_SRC_DATA_ATTRS.has(name) && !isAllowedEmbedSrc(decoded)) return "";
|
|
729
|
+
if (name === "data-href" && !isSafeNavValue(decoded)) return "";
|
|
730
|
+
if (name === "id") {
|
|
731
|
+
if (!ID_WHITELIST.test(decoded)) return "";
|
|
732
|
+
}
|
|
733
|
+
return void 0;
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
return cleaned.replace(
|
|
737
|
+
/<source\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi,
|
|
738
|
+
(t) => /\btype\s*=\s*["'](?:image\/avif|image\/webp)["']/i.test(t) ? t : ""
|
|
739
|
+
);
|
|
577
740
|
}
|
|
578
741
|
|
|
579
742
|
// src/methods/img.method.ts
|
|
580
743
|
var IMAGE_SIZES = "(max-width: 768px) 100vw, 700px";
|
|
581
|
-
function
|
|
744
|
+
function wrapInPicture(el, rawUrl) {
|
|
745
|
+
const parent = el.parentNode;
|
|
746
|
+
if (!parent) return;
|
|
747
|
+
if (parent.nodeName && parent.nodeName.toLowerCase() === "picture") return;
|
|
748
|
+
const sources = buildPictureSources(rawUrl);
|
|
749
|
+
if (!sources) return;
|
|
750
|
+
const doc = el.ownerDocument;
|
|
751
|
+
if (!doc) return;
|
|
752
|
+
const sizes = el.getAttribute("sizes") || IMAGE_SIZES;
|
|
753
|
+
const picture = doc.createElement("picture");
|
|
754
|
+
const avif = doc.createElement("source");
|
|
755
|
+
avif.setAttribute("type", "image/avif");
|
|
756
|
+
avif.setAttribute("srcset", sources.avif);
|
|
757
|
+
avif.setAttribute("sizes", sizes);
|
|
758
|
+
const webp = doc.createElement("source");
|
|
759
|
+
webp.setAttribute("type", "image/webp");
|
|
760
|
+
webp.setAttribute("srcset", sources.webp);
|
|
761
|
+
webp.setAttribute("sizes", sizes);
|
|
762
|
+
parent.insertBefore(picture, el);
|
|
763
|
+
picture.appendChild(avif);
|
|
764
|
+
picture.appendChild(webp);
|
|
765
|
+
picture.appendChild(el);
|
|
766
|
+
}
|
|
767
|
+
function img(el, state, forApp = true) {
|
|
582
768
|
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();
|
|
769
|
+
const decodedSrc = decodeImageSrc(src);
|
|
586
770
|
["onerror", "dynsrc", "lowsrc", "width", "height"].forEach((attr) => el.removeAttribute(attr));
|
|
587
771
|
const isInvalid = !src || decodedSrc.startsWith("javascript") || decodedSrc.startsWith("vbscript") || decodedSrc === "x";
|
|
588
772
|
if (isInvalid) {
|
|
@@ -621,6 +805,9 @@ function img(el, state) {
|
|
|
621
805
|
el.setAttribute("srcset", srcset);
|
|
622
806
|
el.setAttribute("sizes", IMAGE_SIZES);
|
|
623
807
|
}
|
|
808
|
+
if (!forApp) {
|
|
809
|
+
wrapInPicture(el, decodedSrc);
|
|
810
|
+
}
|
|
624
811
|
}
|
|
625
812
|
} else if (shouldReplace && hasAlreadyProxied) {
|
|
626
813
|
if (src.startsWith(`${base}/p/`)) {
|
|
@@ -632,16 +819,17 @@ function img(el, state) {
|
|
|
632
819
|
}
|
|
633
820
|
}
|
|
634
821
|
}
|
|
635
|
-
function createImageHTML(src, isLCP) {
|
|
636
|
-
const
|
|
822
|
+
function createImageHTML(src, isLCP, forApp = true) {
|
|
823
|
+
const decoded = decodeImageSrc(src);
|
|
824
|
+
const proxified = proxifyImageSrc(decoded, 0, 0, "match", { forceProxy: true });
|
|
637
825
|
if (!proxified) return "";
|
|
638
826
|
const base = trimTrailingSlash(getProxyBase());
|
|
639
|
-
const isAlreadyProxied =
|
|
640
|
-
const srcset = isAlreadyProxied ? "" : buildSrcSet(
|
|
827
|
+
const isAlreadyProxied = decoded.startsWith(`${base}/u/`) || new RegExp(`^${base.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/\\d+x\\d+/`).test(decoded);
|
|
828
|
+
const srcset = isAlreadyProxied ? "" : buildSrcSet(decoded);
|
|
641
829
|
const loading = isLCP ? "eager" : "lazy";
|
|
642
830
|
const fetch = isLCP ? 'fetchpriority="high"' : 'decoding="async"';
|
|
643
831
|
const srcsetAttr = srcset ? `srcset="${srcset}" sizes="${IMAGE_SIZES}"` : "";
|
|
644
|
-
|
|
832
|
+
const imgTag = `<img
|
|
645
833
|
class="markdown-img-link"
|
|
646
834
|
src="${proxified}"
|
|
647
835
|
${srcsetAttr}
|
|
@@ -649,6 +837,13 @@ function createImageHTML(src, isLCP) {
|
|
|
649
837
|
${fetch}
|
|
650
838
|
itemprop="image"
|
|
651
839
|
/>`;
|
|
840
|
+
if (!forApp) {
|
|
841
|
+
const sources = buildPictureSources(decoded);
|
|
842
|
+
if (sources) {
|
|
843
|
+
return `<picture><source type="image/avif" srcset="${sources.avif}" sizes="${IMAGE_SIZES}" /><source type="image/webp" srcset="${sources.webp}" sizes="${IMAGE_SIZES}" />${imgTag}</picture>`;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return imgTag;
|
|
652
847
|
}
|
|
653
848
|
|
|
654
849
|
// src/methods/a.method.ts
|
|
@@ -715,7 +910,7 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
|
715
910
|
}
|
|
716
911
|
if (href.match(IMG_REGEX) && href.trim().replace(/&/g, "&") === getSerializedInnerHTML(el).trim().replace(/&/g, "&")) {
|
|
717
912
|
const isLCP = false;
|
|
718
|
-
const imgHTML = createImageHTML(href, isLCP);
|
|
913
|
+
const imgHTML = createImageHTML(href, isLCP, forApp);
|
|
719
914
|
const doc = DOMParser.parseFromString(imgHTML, "text/html");
|
|
720
915
|
const replaceNode = doc.body?.firstChild || doc.firstChild;
|
|
721
916
|
if (replaceNode && el.parentNode) {
|
|
@@ -1554,7 +1749,7 @@ function linkify(content, forApp, renderOptions) {
|
|
|
1554
1749
|
content = content.replace(IMG_REGEX, (imglink) => {
|
|
1555
1750
|
const isLCP = !firstImageUsed;
|
|
1556
1751
|
firstImageUsed = true;
|
|
1557
|
-
return createImageHTML(imglink, isLCP);
|
|
1752
|
+
return createImageHTML(imglink, isLCP, forApp);
|
|
1558
1753
|
});
|
|
1559
1754
|
authorPlaceholders.forEach(({ placeholder, html }) => {
|
|
1560
1755
|
content = content.replace(placeholder, html);
|
|
@@ -1596,7 +1791,7 @@ function text(node, forApp, renderOptions) {
|
|
|
1596
1791
|
}
|
|
1597
1792
|
if (nodeValue.match(IMG_REGEX)) {
|
|
1598
1793
|
const isLCP = false;
|
|
1599
|
-
const imageHTML = createImageHTML(nodeValue, isLCP);
|
|
1794
|
+
const imageHTML = createImageHTML(nodeValue, isLCP, forApp);
|
|
1600
1795
|
const doc = DOMParser.parseFromString(imageHTML, "text/html");
|
|
1601
1796
|
const replaceNode = doc.body?.firstChild || doc.firstChild;
|
|
1602
1797
|
if (replaceNode) {
|
|
@@ -1672,7 +1867,7 @@ function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, p
|
|
|
1672
1867
|
text(child, forApp);
|
|
1673
1868
|
}
|
|
1674
1869
|
if (child.nodeName.toLowerCase() === "img") {
|
|
1675
|
-
img(child, state);
|
|
1870
|
+
img(child, state, forApp);
|
|
1676
1871
|
}
|
|
1677
1872
|
if (child.nodeName.toLowerCase() === "p") {
|
|
1678
1873
|
p(child);
|
|
@@ -1895,8 +2090,11 @@ var INLINE_CODE_RE = /`[^`\n]*`/g;
|
|
|
1895
2090
|
var INDENTED_CODE_RE = /^(?: {4}|\t).+$/gm;
|
|
1896
2091
|
var MD_IMAGE_RE = /!\[[^\]]*\]\(\s*([^)\s]+)(?:\s+["'][^"']*["'])?\s*\)/;
|
|
1897
2092
|
var HTML_IMAGE_RE = /<img\b[^>]*?\bsrc\s*=\s*["']([^"']+)["']/i;
|
|
2093
|
+
var BARE_IMAGE_RE = /(^|\s)(https?:\/\/[^\s<>"'()[\]]+\.(?:tiff?|jpe?g|gif|png|svg|ico|heic|webp|arw)(?:[?#][^\s<>"'()[\]]*)?)/im;
|
|
2094
|
+
var MD_LINK_RE = /\[([^\]]*)\]\(\s*([^)\s]+)(?:\s+["'][^"']*["'])?\s*\)/g;
|
|
2095
|
+
var IMG_HREF_RE = /https?:\/\/.*\.(?:tiff?|jpe?g|gif|png|svg|ico|heic|webp|arw)/i;
|
|
1898
2096
|
var SAFE_URL_RE = /^https?:\/\//i;
|
|
1899
|
-
function findFirstImageUrl(body) {
|
|
2097
|
+
function findFirstImageUrl(body, includeBareUrls = false) {
|
|
1900
2098
|
if (!body) return null;
|
|
1901
2099
|
const cleaned = body.replace(BACKTICK_FENCE_RE, "").replace(TILDE_FENCE_RE, "").replace(INLINE_CODE_RE, "").replace(INDENTED_CODE_RE, "");
|
|
1902
2100
|
const mdMatch = cleaned.match(MD_IMAGE_RE);
|
|
@@ -1907,17 +2105,33 @@ function findFirstImageUrl(body) {
|
|
|
1907
2105
|
return null;
|
|
1908
2106
|
}
|
|
1909
2107
|
}
|
|
1910
|
-
const
|
|
1911
|
-
|
|
1912
|
-
if (
|
|
1913
|
-
|
|
2108
|
+
const candidates = [];
|
|
2109
|
+
if (mdMatch) candidates.push({ url: mdMatch[1], pos: mdMatch.index ?? 0 });
|
|
2110
|
+
if (htmlMatch && htmlMatch[1] && SAFE_URL_RE.test(htmlMatch[1])) {
|
|
2111
|
+
candidates.push({ url: htmlMatch[1], pos: htmlMatch.index ?? 0 });
|
|
1914
2112
|
}
|
|
1915
|
-
if (
|
|
1916
|
-
|
|
1917
|
-
|
|
2113
|
+
if (includeBareUrls) {
|
|
2114
|
+
const bareMatch = cleaned.match(BARE_IMAGE_RE);
|
|
2115
|
+
if (bareMatch && bareMatch[2] && SAFE_URL_RE.test(bareMatch[2])) {
|
|
2116
|
+
candidates.push({ url: bareMatch[2], pos: (bareMatch.index ?? 0) + bareMatch[1].length });
|
|
2117
|
+
}
|
|
2118
|
+
const deAmp = (s) => s.trim().replace(/&/g, "&");
|
|
2119
|
+
for (const m of cleaned.matchAll(MD_LINK_RE)) {
|
|
2120
|
+
const idx = m.index ?? 0;
|
|
2121
|
+
if (idx > 0 && cleaned[idx - 1] === "!") continue;
|
|
2122
|
+
const href = m[2];
|
|
2123
|
+
if (href && SAFE_URL_RE.test(href) && IMG_HREF_RE.test(href) && deAmp(m[1]) === deAmp(href)) {
|
|
2124
|
+
candidates.push({ url: href, pos: idx });
|
|
2125
|
+
break;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
if (candidates.length === 0) return null;
|
|
2130
|
+
candidates.sort((a2, b) => a2.pos - b.pos);
|
|
2131
|
+
return candidates[0].url;
|
|
1918
2132
|
}
|
|
1919
2133
|
function proxifyFound(src, width, height, format) {
|
|
1920
|
-
const decoded =
|
|
2134
|
+
const decoded = he2__default.default.decode(src);
|
|
1921
2135
|
if (isGifLink(decoded)) {
|
|
1922
2136
|
return proxifyImageSrc(decoded, 0, 0, format);
|
|
1923
2137
|
}
|
|
@@ -1935,7 +2149,7 @@ function getImage(entry, width = 0, height = 0, format = "match") {
|
|
|
1935
2149
|
}
|
|
1936
2150
|
}
|
|
1937
2151
|
if (meta && typeof meta.image === "string" && meta.image.length > 0) {
|
|
1938
|
-
const decodedImage =
|
|
2152
|
+
const decodedImage = he2__default.default.decode(meta.image);
|
|
1939
2153
|
if (isGifLink(decodedImage)) {
|
|
1940
2154
|
return proxifyImageSrc(decodedImage, 0, 0, format);
|
|
1941
2155
|
}
|
|
@@ -1943,7 +2157,7 @@ function getImage(entry, width = 0, height = 0, format = "match") {
|
|
|
1943
2157
|
}
|
|
1944
2158
|
if (meta && meta.image && !!meta.image.length && meta.image[0]) {
|
|
1945
2159
|
if (typeof meta.image[0] === "string") {
|
|
1946
|
-
const decodedImage =
|
|
2160
|
+
const decodedImage = he2__default.default.decode(meta.image[0]);
|
|
1947
2161
|
if (isGifLink(decodedImage)) {
|
|
1948
2162
|
return proxifyImageSrc(decodedImage, 0, 0, format);
|
|
1949
2163
|
}
|
|
@@ -1973,6 +2187,30 @@ function getImage(entry, width = 0, height = 0, format = "match") {
|
|
|
1973
2187
|
}
|
|
1974
2188
|
return null;
|
|
1975
2189
|
}
|
|
2190
|
+
function getEntryImageRawUrl(obj) {
|
|
2191
|
+
if (typeof obj === "string") {
|
|
2192
|
+
const src = findFirstImageUrl(obj, true);
|
|
2193
|
+
return src ? decodeImageSrc(src) : null;
|
|
2194
|
+
}
|
|
2195
|
+
let meta;
|
|
2196
|
+
if (typeof obj.json_metadata === "object") {
|
|
2197
|
+
meta = obj.json_metadata;
|
|
2198
|
+
} else {
|
|
2199
|
+
try {
|
|
2200
|
+
meta = JSON.parse(obj.json_metadata);
|
|
2201
|
+
} catch (e) {
|
|
2202
|
+
meta = null;
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
if (meta && typeof meta.image === "string" && meta.image.length > 0) {
|
|
2206
|
+
return decodeImageSrc(meta.image);
|
|
2207
|
+
}
|
|
2208
|
+
if (meta && meta.image && !!meta.image.length && typeof meta.image[0] === "string" && meta.image[0].length > 0) {
|
|
2209
|
+
return decodeImageSrc(meta.image[0]);
|
|
2210
|
+
}
|
|
2211
|
+
const bodySrc = findFirstImageUrl(obj.body, true);
|
|
2212
|
+
return bodySrc ? decodeImageSrc(bodySrc) : null;
|
|
2213
|
+
}
|
|
1976
2214
|
function catchPostImage(obj, width = 0, height = 0, format = "match") {
|
|
1977
2215
|
if (typeof obj === "string") {
|
|
1978
2216
|
const fast = findFirstImageUrl(obj);
|
|
@@ -2074,7 +2312,7 @@ function postBodySummary(entryBody, length = 200, platform = "web") {
|
|
|
2074
2312
|
text2 = joint(text2.split(" "), length);
|
|
2075
2313
|
}
|
|
2076
2314
|
if (text2) {
|
|
2077
|
-
text2 =
|
|
2315
|
+
text2 = he2__default.default.decode(text2);
|
|
2078
2316
|
}
|
|
2079
2317
|
return text2;
|
|
2080
2318
|
}
|
|
@@ -2096,8 +2334,13 @@ function getPostBodySummary(obj, length, platform) {
|
|
|
2096
2334
|
|
|
2097
2335
|
exports.IMAGE_SIZES = IMAGE_SIZES;
|
|
2098
2336
|
exports.SECTION_LIST = SECTION_LIST;
|
|
2337
|
+
exports.buildPictureSources = buildPictureSources;
|
|
2099
2338
|
exports.buildSrcSet = buildSrcSet;
|
|
2339
|
+
exports.buildSrcSetForFormat = buildSrcSetForFormat;
|
|
2100
2340
|
exports.catchPostImage = catchPostImage;
|
|
2341
|
+
exports.getEntryImageRawUrl = getEntryImageRawUrl;
|
|
2342
|
+
exports.isAllowedEmbedSrc = isAllowedEmbedSrc;
|
|
2343
|
+
exports.isPictureEligibleRawUrl = isPictureEligibleRawUrl;
|
|
2101
2344
|
exports.isValidPermlink = isValidPermlink;
|
|
2102
2345
|
exports.postBodySummary = getPostBodySummary;
|
|
2103
2346
|
exports.proxifyImageSrc = proxifyImageSrc;
|