@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.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"],
|
|
@@ -169,6 +176,96 @@ var ALLOWED_ATTRIBUTES = {
|
|
|
169
176
|
"del": [],
|
|
170
177
|
"ins": []
|
|
171
178
|
};
|
|
179
|
+
|
|
180
|
+
// src/consts/embed-hosts.const.ts
|
|
181
|
+
var ALLOWED_EMBED_HOSTS = /* @__PURE__ */ new Set([
|
|
182
|
+
// YouTube
|
|
183
|
+
"www.youtube.com",
|
|
184
|
+
"youtube.com",
|
|
185
|
+
"www.youtube-nocookie.com",
|
|
186
|
+
"youtube-nocookie.com",
|
|
187
|
+
// Vimeo
|
|
188
|
+
"player.vimeo.com",
|
|
189
|
+
// Twitch
|
|
190
|
+
"player.twitch.tv",
|
|
191
|
+
// DTube
|
|
192
|
+
"emb.d.tube",
|
|
193
|
+
// 3Speak (video + audio)
|
|
194
|
+
"play.3speak.tv",
|
|
195
|
+
"3speak.tv",
|
|
196
|
+
"audio.3speak.tv",
|
|
197
|
+
// Loom
|
|
198
|
+
"www.loom.com",
|
|
199
|
+
// Spotify
|
|
200
|
+
"open.spotify.com",
|
|
201
|
+
// SoundCloud
|
|
202
|
+
"w.soundcloud.com",
|
|
203
|
+
// BitChute
|
|
204
|
+
"www.bitchute.com",
|
|
205
|
+
"bitchute.com",
|
|
206
|
+
// Rumble
|
|
207
|
+
"www.rumble.com",
|
|
208
|
+
"rumble.com",
|
|
209
|
+
// Brighteon
|
|
210
|
+
"www.brighteon.com",
|
|
211
|
+
"brighteon.com",
|
|
212
|
+
// VIMM
|
|
213
|
+
"www.vimm.tv",
|
|
214
|
+
// BrandNewTube
|
|
215
|
+
"brandnewtube.com",
|
|
216
|
+
// LBRY / Odysee
|
|
217
|
+
"lbry.tv",
|
|
218
|
+
"odysee.com",
|
|
219
|
+
// Skatehive / Skatehype
|
|
220
|
+
"ipfs.skatehive.app",
|
|
221
|
+
"www.skatehype.com",
|
|
222
|
+
"skatehype.com",
|
|
223
|
+
// archive.org
|
|
224
|
+
"archive.org",
|
|
225
|
+
// Truvvl
|
|
226
|
+
"embed.truvvl.com",
|
|
227
|
+
// Aureal
|
|
228
|
+
"aureal-embed.web.app",
|
|
229
|
+
"www.aureal-embed.web.app"
|
|
230
|
+
// Dapplr (player.*.dapplr.in / *.dapplr.in) — host suffix, handled below
|
|
231
|
+
]);
|
|
232
|
+
var ALLOWED_EMBED_HOST_SUFFIXES = [".dapplr.in"];
|
|
233
|
+
var EMBED_HOST_PATH_PATTERNS = {
|
|
234
|
+
"www.youtube.com": /^\/embed\//,
|
|
235
|
+
"youtube.com": /^\/embed\//,
|
|
236
|
+
"www.youtube-nocookie.com": /^\/embed\//,
|
|
237
|
+
"youtube-nocookie.com": /^\/embed\//,
|
|
238
|
+
"player.vimeo.com": /^\/video\//,
|
|
239
|
+
"player.twitch.tv": /^\/$/,
|
|
240
|
+
// channel/video carried in the query string
|
|
241
|
+
"emb.d.tube": /^\/$/,
|
|
242
|
+
// dtube carries the ref in the #! fragment
|
|
243
|
+
"play.3speak.tv": /^\/(watch|embed)/,
|
|
244
|
+
"open.spotify.com": /^\/embed\//,
|
|
245
|
+
"www.loom.com": /^\/embed\//,
|
|
246
|
+
"www.bitchute.com": /^\/embed\//,
|
|
247
|
+
"bitchute.com": /^\/embed\//,
|
|
248
|
+
"www.rumble.com": /^\/embed\//,
|
|
249
|
+
"rumble.com": /^\/embed\//,
|
|
250
|
+
"www.brighteon.com": /^\/embed\//,
|
|
251
|
+
"brighteon.com": /^\/embed\//
|
|
252
|
+
};
|
|
253
|
+
function isAllowedEmbedSrc(value) {
|
|
254
|
+
if (!value) return false;
|
|
255
|
+
let url;
|
|
256
|
+
try {
|
|
257
|
+
url = new URL(value.trim());
|
|
258
|
+
} catch {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
if (url.protocol !== "https:") return false;
|
|
262
|
+
const host = url.hostname.toLowerCase();
|
|
263
|
+
const hostAllowed = ALLOWED_EMBED_HOSTS.has(host) || ALLOWED_EMBED_HOST_SUFFIXES.some((suffix) => host.endsWith(suffix));
|
|
264
|
+
if (!hostAllowed) return false;
|
|
265
|
+
const pathPattern = EMBED_HOST_PATH_PATTERNS[host];
|
|
266
|
+
if (pathPattern && !pathPattern.test(url.pathname)) return false;
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
172
269
|
function createParser() {
|
|
173
270
|
return new DOMParser$1({
|
|
174
271
|
onError(level, msg) {
|
|
@@ -176,8 +273,14 @@ function createParser() {
|
|
|
176
273
|
});
|
|
177
274
|
}
|
|
178
275
|
var DOMParser = createParser();
|
|
179
|
-
|
|
180
|
-
|
|
276
|
+
function decodeImageSrc(src) {
|
|
277
|
+
const entityDecoded = he2.decode(src);
|
|
278
|
+
try {
|
|
279
|
+
return decodeURIComponent(entityDecoded).trim();
|
|
280
|
+
} catch {
|
|
281
|
+
return entityDecoded.trim();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
181
284
|
function isSpaceChar(c) {
|
|
182
285
|
return c === 32 || c === 9 || c === 10 || c === 13 || c === 12;
|
|
183
286
|
}
|
|
@@ -433,33 +536,6 @@ function removeChildNodes(node) {
|
|
|
433
536
|
node.removeChild(node.firstChild);
|
|
434
537
|
}
|
|
435
538
|
}
|
|
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
539
|
var proxyBase = "https://i.ecency.com";
|
|
464
540
|
var urlHashCache = new LRUCache({ max: 500 });
|
|
465
541
|
function getUrlHash(url) {
|
|
@@ -495,7 +571,7 @@ function getLatestUrl(str) {
|
|
|
495
571
|
const [last] = [...str.replace(/https?:\/\//g, "\n$&").trim().split("\n")].reverse();
|
|
496
572
|
return last;
|
|
497
573
|
}
|
|
498
|
-
function
|
|
574
|
+
function proxifyForFormat(url, width = 0, height = 0, format = "match", opts = {}) {
|
|
499
575
|
if (!url || typeof url !== "string" || !isValidUrl(url)) {
|
|
500
576
|
return "";
|
|
501
577
|
}
|
|
@@ -512,7 +588,7 @@ function proxifyImageSrc(url, width = 0, height = 0, _format = "match", opts = {
|
|
|
512
588
|
const realUrl = getLatestUrl(url);
|
|
513
589
|
const pHash = extractPHash(realUrl);
|
|
514
590
|
const options = {
|
|
515
|
-
format
|
|
591
|
+
format,
|
|
516
592
|
mode: "fit"
|
|
517
593
|
};
|
|
518
594
|
if (width > 0) {
|
|
@@ -531,29 +607,137 @@ function proxifyImageSrc(url, width = 0, height = 0, _format = "match", opts = {
|
|
|
531
607
|
const b58url = getUrlHash(realUrl.toString());
|
|
532
608
|
return `${proxyBase}/p/${b58url}?${qs}`;
|
|
533
609
|
}
|
|
610
|
+
function proxifyImageSrc(url, width = 0, height = 0, _format = "match", opts = {}) {
|
|
611
|
+
return proxifyForFormat(url, width, height, "match", opts);
|
|
612
|
+
}
|
|
534
613
|
var SRCSET_WIDTHS = [320, 600, 800, 1024, 1280];
|
|
535
614
|
function buildSrcSet(url) {
|
|
615
|
+
return buildSrcSetForFormat(url, "match");
|
|
616
|
+
}
|
|
617
|
+
function buildSrcSetForFormat(url, format = "match") {
|
|
536
618
|
if (!url || typeof url !== "string") return "";
|
|
537
619
|
const proxyPrefix = `${proxyBase}/p/`;
|
|
620
|
+
let result;
|
|
538
621
|
if (url.startsWith(proxyPrefix)) {
|
|
539
622
|
const rest = url.slice(proxyPrefix.length);
|
|
540
623
|
const q = rest.indexOf("?");
|
|
541
624
|
const phash = extractPHash(url) || (q >= 0 ? rest.slice(0, q) : rest);
|
|
542
|
-
|
|
625
|
+
result = SRCSET_WIDTHS.map((w) => `${proxyBase}/p/${phash}?format=${format}&mode=fit&width=${w} ${w}w`).join(", ");
|
|
626
|
+
} else {
|
|
627
|
+
result = SRCSET_WIDTHS.map((w) => {
|
|
628
|
+
const proxied = proxifyForFormat(url, w, 0, format);
|
|
629
|
+
return proxied ? `${proxied} ${w}w` : "";
|
|
630
|
+
}).filter(Boolean).join(", ");
|
|
631
|
+
}
|
|
632
|
+
if (format !== "match" && result && !result.split(",").every((c) => c.includes(`format=${format}`))) {
|
|
633
|
+
return "";
|
|
543
634
|
}
|
|
544
|
-
return
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
635
|
+
return result;
|
|
636
|
+
}
|
|
637
|
+
var STATIC_RASTER_PATH_EXT = /\.(?:jpe?g|png|webp)$/i;
|
|
638
|
+
var SIZED_PROXY_PATH = /^\/\d+x\d+\//;
|
|
639
|
+
function isPictureEligibleRawUrl(rawUrl) {
|
|
640
|
+
if (!rawUrl || typeof rawUrl !== "string") return false;
|
|
641
|
+
let u;
|
|
642
|
+
try {
|
|
643
|
+
u = new URL(rawUrl);
|
|
644
|
+
} catch {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return false;
|
|
648
|
+
const host = `${u.protocol}//${u.host}`;
|
|
649
|
+
const isProxyHost = host === proxyBase || host === "https://images.ecency.com";
|
|
650
|
+
if (isProxyHost && (u.pathname.startsWith("/p/") || u.pathname.startsWith("/u/") || SIZED_PROXY_PATH.test(u.pathname))) {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
return STATIC_RASTER_PATH_EXT.test(u.pathname);
|
|
654
|
+
}
|
|
655
|
+
function buildPictureSources(rawUrl) {
|
|
656
|
+
if (!isPictureEligibleRawUrl(rawUrl)) return null;
|
|
657
|
+
const avif = buildSrcSetForFormat(rawUrl, "avif");
|
|
658
|
+
const webp = buildSrcSetForFormat(rawUrl, "webp");
|
|
659
|
+
if (!avif || !webp) return null;
|
|
660
|
+
return { avif, webp };
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// src/methods/sanitize-html.method.ts
|
|
664
|
+
var EMBED_SRC_DATA_ATTRS = /* @__PURE__ */ new Set(["data-embed-src", "data-video-href"]);
|
|
665
|
+
var isSafeNavValue = (value) => {
|
|
666
|
+
const trimmed = value.trim().replace(/[\t\n\r\f\v\0]/g, "").toLowerCase();
|
|
667
|
+
if (!trimmed) return false;
|
|
668
|
+
const isSafeScheme = /^(https?|mailto|hive|tel|web\+[a-z0-9.+-]+):/i.test(trimmed);
|
|
669
|
+
const isRelative = /^(\/\/|\/[^/]?|#|\?|[a-z0-9._\-]+(\/|$))/i.test(trimmed);
|
|
670
|
+
return isSafeScheme || isRelative;
|
|
671
|
+
};
|
|
672
|
+
var decodeEntities = (input) => input.replace(/&#(\d+);?/g, (_, dec) => String.fromCodePoint(Number(dec))).replace(/&#x([0-9a-f]+);?/gi, (_, hex) => String.fromCodePoint(parseInt(hex, 16)));
|
|
673
|
+
var isProxyPSrcset = (srcset) => {
|
|
674
|
+
const base = trimTrailingSlash(getProxyBase());
|
|
675
|
+
const candidates = srcset.split(",").map((c) => c.trim().split(/\s+/)[0]).filter(Boolean);
|
|
676
|
+
return candidates.length > 0 && candidates.every((url) => url.startsWith(`${base}/p/`));
|
|
677
|
+
};
|
|
678
|
+
function sanitizeHtml(html) {
|
|
679
|
+
const cleaned = xss(html, {
|
|
680
|
+
whiteList: ALLOWED_ATTRIBUTES,
|
|
681
|
+
stripIgnoreTag: true,
|
|
682
|
+
stripIgnoreTagBody: ["style"],
|
|
683
|
+
css: false,
|
|
684
|
+
// block style attrs entirely for safety
|
|
685
|
+
onTagAttr: (tag, name, value) => {
|
|
686
|
+
const decoded = decodeEntities(value.trim());
|
|
687
|
+
const decodedLower = decoded.toLowerCase();
|
|
688
|
+
if (name.startsWith("on")) return "";
|
|
689
|
+
if (tag === "img" && name === "src" && !/^https?:\/\//.test(decodedLower)) return "";
|
|
690
|
+
if (tag === "img" && name === "srcset") {
|
|
691
|
+
const candidates = decoded.split(",").map((c) => c.trim().split(/\s+/)[0]);
|
|
692
|
+
if (candidates.some((url) => !/^https?:\/\//i.test(url))) return "";
|
|
693
|
+
}
|
|
694
|
+
if (tag === "source" && name === "srcset" && !isProxyPSrcset(decoded)) return "";
|
|
695
|
+
if (tag === "source" && name === "type" && decodedLower !== "image/avif" && decodedLower !== "image/webp") return "";
|
|
696
|
+
if (tag === "video" && ["src", "poster"].includes(name) && !/^https?:\/\//.test(decodedLower)) return "";
|
|
697
|
+
if (tag === "img" && ["dynsrc", "lowsrc"].includes(name)) return "";
|
|
698
|
+
if (tag === "span" && name === "class" && decoded.toLowerCase().trim() === "wr") return "";
|
|
699
|
+
if (EMBED_SRC_DATA_ATTRS.has(name) && !isAllowedEmbedSrc(decoded)) return "";
|
|
700
|
+
if (name === "data-href" && !isSafeNavValue(decoded)) return "";
|
|
701
|
+
if (name === "id") {
|
|
702
|
+
if (!ID_WHITELIST.test(decoded)) return "";
|
|
703
|
+
}
|
|
704
|
+
return void 0;
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
return cleaned.replace(
|
|
708
|
+
/<source\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi,
|
|
709
|
+
(t) => /\btype\s*=\s*["'](?:image\/avif|image\/webp)["']/i.test(t) ? t : ""
|
|
710
|
+
);
|
|
548
711
|
}
|
|
549
712
|
|
|
550
713
|
// src/methods/img.method.ts
|
|
551
714
|
var IMAGE_SIZES = "(max-width: 768px) 100vw, 700px";
|
|
552
|
-
function
|
|
715
|
+
function wrapInPicture(el, rawUrl) {
|
|
716
|
+
const parent = el.parentNode;
|
|
717
|
+
if (!parent) return;
|
|
718
|
+
if (parent.nodeName && parent.nodeName.toLowerCase() === "picture") return;
|
|
719
|
+
const sources = buildPictureSources(rawUrl);
|
|
720
|
+
if (!sources) return;
|
|
721
|
+
const doc = el.ownerDocument;
|
|
722
|
+
if (!doc) return;
|
|
723
|
+
const sizes = el.getAttribute("sizes") || IMAGE_SIZES;
|
|
724
|
+
const picture = doc.createElement("picture");
|
|
725
|
+
const avif = doc.createElement("source");
|
|
726
|
+
avif.setAttribute("type", "image/avif");
|
|
727
|
+
avif.setAttribute("srcset", sources.avif);
|
|
728
|
+
avif.setAttribute("sizes", sizes);
|
|
729
|
+
const webp = doc.createElement("source");
|
|
730
|
+
webp.setAttribute("type", "image/webp");
|
|
731
|
+
webp.setAttribute("srcset", sources.webp);
|
|
732
|
+
webp.setAttribute("sizes", sizes);
|
|
733
|
+
parent.insertBefore(picture, el);
|
|
734
|
+
picture.appendChild(avif);
|
|
735
|
+
picture.appendChild(webp);
|
|
736
|
+
picture.appendChild(el);
|
|
737
|
+
}
|
|
738
|
+
function img(el, state, forApp = true) {
|
|
553
739
|
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();
|
|
740
|
+
const decodedSrc = decodeImageSrc(src);
|
|
557
741
|
["onerror", "dynsrc", "lowsrc", "width", "height"].forEach((attr) => el.removeAttribute(attr));
|
|
558
742
|
const isInvalid = !src || decodedSrc.startsWith("javascript") || decodedSrc.startsWith("vbscript") || decodedSrc === "x";
|
|
559
743
|
if (isInvalid) {
|
|
@@ -592,6 +776,9 @@ function img(el, state) {
|
|
|
592
776
|
el.setAttribute("srcset", srcset);
|
|
593
777
|
el.setAttribute("sizes", IMAGE_SIZES);
|
|
594
778
|
}
|
|
779
|
+
if (!forApp) {
|
|
780
|
+
wrapInPicture(el, decodedSrc);
|
|
781
|
+
}
|
|
595
782
|
}
|
|
596
783
|
} else if (shouldReplace && hasAlreadyProxied) {
|
|
597
784
|
if (src.startsWith(`${base}/p/`)) {
|
|
@@ -603,16 +790,17 @@ function img(el, state) {
|
|
|
603
790
|
}
|
|
604
791
|
}
|
|
605
792
|
}
|
|
606
|
-
function createImageHTML(src, isLCP) {
|
|
607
|
-
const
|
|
793
|
+
function createImageHTML(src, isLCP, forApp = true) {
|
|
794
|
+
const decoded = decodeImageSrc(src);
|
|
795
|
+
const proxified = proxifyImageSrc(decoded, 0, 0, "match", { forceProxy: true });
|
|
608
796
|
if (!proxified) return "";
|
|
609
797
|
const base = trimTrailingSlash(getProxyBase());
|
|
610
|
-
const isAlreadyProxied =
|
|
611
|
-
const srcset = isAlreadyProxied ? "" : buildSrcSet(
|
|
798
|
+
const isAlreadyProxied = decoded.startsWith(`${base}/u/`) || new RegExp(`^${base.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/\\d+x\\d+/`).test(decoded);
|
|
799
|
+
const srcset = isAlreadyProxied ? "" : buildSrcSet(decoded);
|
|
612
800
|
const loading = isLCP ? "eager" : "lazy";
|
|
613
801
|
const fetch = isLCP ? 'fetchpriority="high"' : 'decoding="async"';
|
|
614
802
|
const srcsetAttr = srcset ? `srcset="${srcset}" sizes="${IMAGE_SIZES}"` : "";
|
|
615
|
-
|
|
803
|
+
const imgTag = `<img
|
|
616
804
|
class="markdown-img-link"
|
|
617
805
|
src="${proxified}"
|
|
618
806
|
${srcsetAttr}
|
|
@@ -620,6 +808,13 @@ function createImageHTML(src, isLCP) {
|
|
|
620
808
|
${fetch}
|
|
621
809
|
itemprop="image"
|
|
622
810
|
/>`;
|
|
811
|
+
if (!forApp) {
|
|
812
|
+
const sources = buildPictureSources(decoded);
|
|
813
|
+
if (sources) {
|
|
814
|
+
return `<picture><source type="image/avif" srcset="${sources.avif}" sizes="${IMAGE_SIZES}" /><source type="image/webp" srcset="${sources.webp}" sizes="${IMAGE_SIZES}" />${imgTag}</picture>`;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return imgTag;
|
|
623
818
|
}
|
|
624
819
|
|
|
625
820
|
// src/methods/a.method.ts
|
|
@@ -686,7 +881,7 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
|
|
|
686
881
|
}
|
|
687
882
|
if (href.match(IMG_REGEX) && href.trim().replace(/&/g, "&") === getSerializedInnerHTML(el).trim().replace(/&/g, "&")) {
|
|
688
883
|
const isLCP = false;
|
|
689
|
-
const imgHTML = createImageHTML(href, isLCP);
|
|
884
|
+
const imgHTML = createImageHTML(href, isLCP, forApp);
|
|
690
885
|
const doc = DOMParser.parseFromString(imgHTML, "text/html");
|
|
691
886
|
const replaceNode = doc.body?.firstChild || doc.firstChild;
|
|
692
887
|
if (replaceNode && el.parentNode) {
|
|
@@ -1525,7 +1720,7 @@ function linkify(content, forApp, renderOptions) {
|
|
|
1525
1720
|
content = content.replace(IMG_REGEX, (imglink) => {
|
|
1526
1721
|
const isLCP = !firstImageUsed;
|
|
1527
1722
|
firstImageUsed = true;
|
|
1528
|
-
return createImageHTML(imglink, isLCP);
|
|
1723
|
+
return createImageHTML(imglink, isLCP, forApp);
|
|
1529
1724
|
});
|
|
1530
1725
|
authorPlaceholders.forEach(({ placeholder, html }) => {
|
|
1531
1726
|
content = content.replace(placeholder, html);
|
|
@@ -1567,7 +1762,7 @@ function text(node, forApp, renderOptions) {
|
|
|
1567
1762
|
}
|
|
1568
1763
|
if (nodeValue.match(IMG_REGEX)) {
|
|
1569
1764
|
const isLCP = false;
|
|
1570
|
-
const imageHTML = createImageHTML(nodeValue, isLCP);
|
|
1765
|
+
const imageHTML = createImageHTML(nodeValue, isLCP, forApp);
|
|
1571
1766
|
const doc = DOMParser.parseFromString(imageHTML, "text/html");
|
|
1572
1767
|
const replaceNode = doc.body?.firstChild || doc.firstChild;
|
|
1573
1768
|
if (replaceNode) {
|
|
@@ -1643,7 +1838,7 @@ function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, p
|
|
|
1643
1838
|
text(child, forApp);
|
|
1644
1839
|
}
|
|
1645
1840
|
if (child.nodeName.toLowerCase() === "img") {
|
|
1646
|
-
img(child, state);
|
|
1841
|
+
img(child, state, forApp);
|
|
1647
1842
|
}
|
|
1648
1843
|
if (child.nodeName.toLowerCase() === "p") {
|
|
1649
1844
|
p(child);
|
|
@@ -1866,8 +2061,11 @@ var INLINE_CODE_RE = /`[^`\n]*`/g;
|
|
|
1866
2061
|
var INDENTED_CODE_RE = /^(?: {4}|\t).+$/gm;
|
|
1867
2062
|
var MD_IMAGE_RE = /!\[[^\]]*\]\(\s*([^)\s]+)(?:\s+["'][^"']*["'])?\s*\)/;
|
|
1868
2063
|
var HTML_IMAGE_RE = /<img\b[^>]*?\bsrc\s*=\s*["']([^"']+)["']/i;
|
|
2064
|
+
var BARE_IMAGE_RE = /(^|\s)(https?:\/\/[^\s<>"'()[\]]+\.(?:tiff?|jpe?g|gif|png|svg|ico|heic|webp|arw)(?:[?#][^\s<>"'()[\]]*)?)/im;
|
|
2065
|
+
var MD_LINK_RE = /\[([^\]]*)\]\(\s*([^)\s]+)(?:\s+["'][^"']*["'])?\s*\)/g;
|
|
2066
|
+
var IMG_HREF_RE = /https?:\/\/.*\.(?:tiff?|jpe?g|gif|png|svg|ico|heic|webp|arw)/i;
|
|
1869
2067
|
var SAFE_URL_RE = /^https?:\/\//i;
|
|
1870
|
-
function findFirstImageUrl(body) {
|
|
2068
|
+
function findFirstImageUrl(body, includeBareUrls = false) {
|
|
1871
2069
|
if (!body) return null;
|
|
1872
2070
|
const cleaned = body.replace(BACKTICK_FENCE_RE, "").replace(TILDE_FENCE_RE, "").replace(INLINE_CODE_RE, "").replace(INDENTED_CODE_RE, "");
|
|
1873
2071
|
const mdMatch = cleaned.match(MD_IMAGE_RE);
|
|
@@ -1878,17 +2076,33 @@ function findFirstImageUrl(body) {
|
|
|
1878
2076
|
return null;
|
|
1879
2077
|
}
|
|
1880
2078
|
}
|
|
1881
|
-
const
|
|
1882
|
-
|
|
1883
|
-
if (
|
|
1884
|
-
|
|
2079
|
+
const candidates = [];
|
|
2080
|
+
if (mdMatch) candidates.push({ url: mdMatch[1], pos: mdMatch.index ?? 0 });
|
|
2081
|
+
if (htmlMatch && htmlMatch[1] && SAFE_URL_RE.test(htmlMatch[1])) {
|
|
2082
|
+
candidates.push({ url: htmlMatch[1], pos: htmlMatch.index ?? 0 });
|
|
1885
2083
|
}
|
|
1886
|
-
if (
|
|
1887
|
-
|
|
1888
|
-
|
|
2084
|
+
if (includeBareUrls) {
|
|
2085
|
+
const bareMatch = cleaned.match(BARE_IMAGE_RE);
|
|
2086
|
+
if (bareMatch && bareMatch[2] && SAFE_URL_RE.test(bareMatch[2])) {
|
|
2087
|
+
candidates.push({ url: bareMatch[2], pos: (bareMatch.index ?? 0) + bareMatch[1].length });
|
|
2088
|
+
}
|
|
2089
|
+
const deAmp = (s) => s.trim().replace(/&/g, "&");
|
|
2090
|
+
for (const m of cleaned.matchAll(MD_LINK_RE)) {
|
|
2091
|
+
const idx = m.index ?? 0;
|
|
2092
|
+
if (idx > 0 && cleaned[idx - 1] === "!") continue;
|
|
2093
|
+
const href = m[2];
|
|
2094
|
+
if (href && SAFE_URL_RE.test(href) && IMG_HREF_RE.test(href) && deAmp(m[1]) === deAmp(href)) {
|
|
2095
|
+
candidates.push({ url: href, pos: idx });
|
|
2096
|
+
break;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
if (candidates.length === 0) return null;
|
|
2101
|
+
candidates.sort((a2, b) => a2.pos - b.pos);
|
|
2102
|
+
return candidates[0].url;
|
|
1889
2103
|
}
|
|
1890
2104
|
function proxifyFound(src, width, height, format) {
|
|
1891
|
-
const decoded =
|
|
2105
|
+
const decoded = he2.decode(src);
|
|
1892
2106
|
if (isGifLink(decoded)) {
|
|
1893
2107
|
return proxifyImageSrc(decoded, 0, 0, format);
|
|
1894
2108
|
}
|
|
@@ -1906,7 +2120,7 @@ function getImage(entry, width = 0, height = 0, format = "match") {
|
|
|
1906
2120
|
}
|
|
1907
2121
|
}
|
|
1908
2122
|
if (meta && typeof meta.image === "string" && meta.image.length > 0) {
|
|
1909
|
-
const decodedImage =
|
|
2123
|
+
const decodedImage = he2.decode(meta.image);
|
|
1910
2124
|
if (isGifLink(decodedImage)) {
|
|
1911
2125
|
return proxifyImageSrc(decodedImage, 0, 0, format);
|
|
1912
2126
|
}
|
|
@@ -1914,7 +2128,7 @@ function getImage(entry, width = 0, height = 0, format = "match") {
|
|
|
1914
2128
|
}
|
|
1915
2129
|
if (meta && meta.image && !!meta.image.length && meta.image[0]) {
|
|
1916
2130
|
if (typeof meta.image[0] === "string") {
|
|
1917
|
-
const decodedImage =
|
|
2131
|
+
const decodedImage = he2.decode(meta.image[0]);
|
|
1918
2132
|
if (isGifLink(decodedImage)) {
|
|
1919
2133
|
return proxifyImageSrc(decodedImage, 0, 0, format);
|
|
1920
2134
|
}
|
|
@@ -1944,6 +2158,30 @@ function getImage(entry, width = 0, height = 0, format = "match") {
|
|
|
1944
2158
|
}
|
|
1945
2159
|
return null;
|
|
1946
2160
|
}
|
|
2161
|
+
function getEntryImageRawUrl(obj) {
|
|
2162
|
+
if (typeof obj === "string") {
|
|
2163
|
+
const src = findFirstImageUrl(obj, true);
|
|
2164
|
+
return src ? decodeImageSrc(src) : null;
|
|
2165
|
+
}
|
|
2166
|
+
let meta;
|
|
2167
|
+
if (typeof obj.json_metadata === "object") {
|
|
2168
|
+
meta = obj.json_metadata;
|
|
2169
|
+
} else {
|
|
2170
|
+
try {
|
|
2171
|
+
meta = JSON.parse(obj.json_metadata);
|
|
2172
|
+
} catch (e) {
|
|
2173
|
+
meta = null;
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
if (meta && typeof meta.image === "string" && meta.image.length > 0) {
|
|
2177
|
+
return decodeImageSrc(meta.image);
|
|
2178
|
+
}
|
|
2179
|
+
if (meta && meta.image && !!meta.image.length && typeof meta.image[0] === "string" && meta.image[0].length > 0) {
|
|
2180
|
+
return decodeImageSrc(meta.image[0]);
|
|
2181
|
+
}
|
|
2182
|
+
const bodySrc = findFirstImageUrl(obj.body, true);
|
|
2183
|
+
return bodySrc ? decodeImageSrc(bodySrc) : null;
|
|
2184
|
+
}
|
|
1947
2185
|
function catchPostImage(obj, width = 0, height = 0, format = "match") {
|
|
1948
2186
|
if (typeof obj === "string") {
|
|
1949
2187
|
const fast = findFirstImageUrl(obj);
|
|
@@ -2045,7 +2283,7 @@ function postBodySummary(entryBody, length = 200, platform = "web") {
|
|
|
2045
2283
|
text2 = joint(text2.split(" "), length);
|
|
2046
2284
|
}
|
|
2047
2285
|
if (text2) {
|
|
2048
|
-
text2 =
|
|
2286
|
+
text2 = he2.decode(text2);
|
|
2049
2287
|
}
|
|
2050
2288
|
return text2;
|
|
2051
2289
|
}
|
|
@@ -2065,6 +2303,6 @@ function getPostBodySummary(obj, length, platform) {
|
|
|
2065
2303
|
return res;
|
|
2066
2304
|
}
|
|
2067
2305
|
|
|
2068
|
-
export { IMAGE_SIZES, SECTION_LIST, buildSrcSet, catchPostImage, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase, setSlowRenderThresholdMs, simpleMarkdownToHTML };
|
|
2306
|
+
export { IMAGE_SIZES, SECTION_LIST, buildPictureSources, buildSrcSet, buildSrcSetForFormat, catchPostImage, getEntryImageRawUrl, isAllowedEmbedSrc, isPictureEligibleRawUrl, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase, setSlowRenderThresholdMs, simpleMarkdownToHTML };
|
|
2069
2307
|
//# sourceMappingURL=index.mjs.map
|
|
2070
2308
|
//# sourceMappingURL=index.mjs.map
|