@ecency/render-helper 2.3.17 → 2.4.1
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/LICENSE +1 -1
- package/README.md +171 -17
- package/dist/browser/index.d.ts +32 -0
- package/dist/browser/index.js +1556 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/node/index.cjs +1575 -0
- package/dist/node/index.cjs.map +1 -0
- package/dist/node/index.mjs +1559 -0
- package/dist/node/index.mjs.map +1 -0
- package/package.json +49 -47
- package/lib/cache.d.ts +0 -3
- package/lib/cache.js +0 -21
- package/lib/cache.js.map +0 -1
- package/lib/catch-post-image.d.ts +0 -2
- package/lib/catch-post-image.js +0 -76
- package/lib/catch-post-image.js.map +0 -1
- package/lib/consts/allowed-attributes.const.d.ts +0 -2
- package/lib/consts/allowed-attributes.const.js +0 -71
- package/lib/consts/allowed-attributes.const.js.map +0 -1
- package/lib/consts/dom-parser.const.d.ts +0 -2
- package/lib/consts/dom-parser.const.js +0 -12
- package/lib/consts/dom-parser.const.js.map +0 -1
- package/lib/consts/index.d.ts +0 -5
- package/lib/consts/index.js +0 -22
- package/lib/consts/index.js.map +0 -1
- package/lib/consts/regexes.const.d.ts +0 -44
- package/lib/consts/regexes.const.js +0 -49
- package/lib/consts/regexes.const.js.map +0 -1
- package/lib/consts/section-list.const.d.ts +0 -1
- package/lib/consts/section-list.const.js +0 -24
- package/lib/consts/section-list.const.js.map +0 -1
- package/lib/consts/white-list.const.d.ts +0 -2
- package/lib/consts/white-list.const.js +0 -37
- package/lib/consts/white-list.const.js.map +0 -1
- package/lib/helper.d.ts +0 -6
- package/lib/helper.js +0 -72
- package/lib/helper.js.map +0 -1
- package/lib/index.d.ts +0 -8
- package/lib/index.js +0 -19
- package/lib/index.js.map +0 -1
- package/lib/markdown-2-html.d.ts +0 -2
- package/lib/markdown-2-html.js +0 -25
- package/lib/markdown-2-html.js.map +0 -1
- package/lib/methods/a.method.d.ts +0 -1
- package/lib/methods/a.method.js +0 -647
- package/lib/methods/a.method.js.map +0 -1
- package/lib/methods/clean-reply.method.d.ts +0 -1
- package/lib/methods/clean-reply.method.js +0 -37
- package/lib/methods/clean-reply.method.js.map +0 -1
- package/lib/methods/get-inner-html.method.d.ts +0 -1
- package/lib/methods/get-inner-html.method.js +0 -16
- package/lib/methods/get-inner-html.method.js.map +0 -1
- package/lib/methods/iframe.method.d.ts +0 -1
- package/lib/methods/iframe.method.js +0 -159
- package/lib/methods/iframe.method.js.map +0 -1
- package/lib/methods/img.method.d.ts +0 -4
- package/lib/methods/img.method.js +0 -50
- package/lib/methods/img.method.js.map +0 -1
- package/lib/methods/index.d.ts +0 -7
- package/lib/methods/index.js +0 -24
- package/lib/methods/index.js.map +0 -1
- package/lib/methods/linkify.method.d.ts +0 -1
- package/lib/methods/linkify.method.js +0 -56
- package/lib/methods/linkify.method.js.map +0 -1
- package/lib/methods/markdown-to-html.method.d.ts +0 -1
- package/lib/methods/markdown-to-html.method.js +0 -98
- package/lib/methods/markdown-to-html.method.js.map +0 -1
- package/lib/methods/noop.method.d.ts +0 -1
- package/lib/methods/noop.method.js +0 -6
- package/lib/methods/noop.method.js.map +0 -1
- package/lib/methods/p.method.d.ts +0 -1
- package/lib/methods/p.method.js +0 -11
- package/lib/methods/p.method.js.map +0 -1
- package/lib/methods/remove-child-nodes.method.d.ts +0 -1
- package/lib/methods/remove-child-nodes.method.js +0 -10
- package/lib/methods/remove-child-nodes.method.js.map +0 -1
- package/lib/methods/sanitize-html.method.d.ts +0 -1
- package/lib/methods/sanitize-html.method.js +0 -42
- package/lib/methods/sanitize-html.method.js.map +0 -1
- package/lib/methods/text.method.d.ts +0 -1
- package/lib/methods/text.method.js +0 -69
- package/lib/methods/text.method.js.map +0 -1
- package/lib/methods/traverse.method.d.ts +0 -3
- package/lib/methods/traverse.method.js +0 -38
- package/lib/methods/traverse.method.js.map +0 -1
- package/lib/post-body-summary.d.ts +0 -2
- package/lib/post-body-summary.js +0 -123
- package/lib/post-body-summary.js.map +0 -1
- package/lib/proxify-image-src.d.ts +0 -5
- package/lib/proxify-image-src.js +0 -86
- package/lib/proxify-image-src.js.map +0 -1
- package/lib/render-helper.js +0 -1
- package/lib/types/entry.interface.d.ts +0 -7
- package/lib/types/entry.interface.js +0 -3
- package/lib/types/entry.interface.js.map +0 -1
- package/lib/types/index.d.ts +0 -2
- package/lib/types/index.js +0 -19
- package/lib/types/index.js.map +0 -1
- package/lib/types/xss-white-list.interface.d.ts +0 -6
- package/lib/types/xss-white-list.interface.js +0 -3
- package/lib/types/xss-white-list.interface.js.map +0 -1
- package/src/cache.ts +0 -15
- package/src/catch-post-image.spec.ts +0 -144
- package/src/catch-post-image.ts +0 -78
- package/src/consts/allowed-attributes.const.ts +0 -69
- package/src/consts/dom-parser.const.ts +0 -6
- package/src/consts/index.ts +0 -5
- package/src/consts/regexes.const.ts +0 -46
- package/src/consts/section-list.const.ts +0 -20
- package/src/consts/white-list.const.ts +0 -33
- package/src/external-types/multihashes.d.ts +0 -3
- package/src/helper.spec.ts +0 -16
- package/src/helper.ts +0 -77
- package/src/index.ts +0 -18
- package/src/markdown-2-html.spec.ts +0 -1251
- package/src/markdown-2-html.ts +0 -25
- package/src/methods/a.method.ts +0 -791
- package/src/methods/clean-reply.method.ts +0 -32
- package/src/methods/get-inner-html.method.ts +0 -11
- package/src/methods/iframe.method.ts +0 -176
- package/src/methods/img.method.ts +0 -62
- package/src/methods/index.ts +0 -7
- package/src/methods/linkify.method.ts +0 -61
- package/src/methods/markdown-to-html.method.ts +0 -104
- package/src/methods/noop.method.ts +0 -1
- package/src/methods/p.method.ts +0 -6
- package/src/methods/remove-child-nodes.method.ts +0 -5
- package/src/methods/sanitize-html.method.ts +0 -32
- package/src/methods/text.method.ts +0 -77
- package/src/methods/traverse.method.ts +0 -33
- package/src/post-body-summary.spec.ts +0 -150
- package/src/post-body-summary.ts +0 -130
- package/src/proxify-image-src.spec.ts +0 -76
- package/src/proxify-image-src.ts +0 -77
- package/src/sanitize-html.spec.ts +0 -15
- package/src/test/data/index.ts +0 -12
- package/src/test/data/json/muratkbesiroglu____sci-fi-novel-underground-city-part-13.json +0 -2120
- package/src/test/data/json/steemitboard____steemitboard-notify-dunsky-20181210t153450000z.json +0 -47
- package/src/test/data/legacy/10.json +0 -5
- package/src/test/data/legacy/20.json +0 -5
- package/src/test/data/legacy/21.json +0 -5
- package/src/test/data/legacy/2112524.json +0 -5
- package/src/test/data/legacy/22.json +0 -5
- package/src/test/data/legacy/23.json +0 -5
- package/src/test/data/legacy/24.json +0 -5
- package/src/test/data/legacy/25.json +0 -5
- package/src/test/data/legacy/26.json +0 -5
- package/src/test/data/legacy/27.JSON +0 -5
- package/src/test/data/legacy/28.json +0 -5
- package/src/test/data/legacy/29.json +0 -5
- package/src/test/data/legacy/31.json +0 -5
- package/src/test/snaps.json +0 -8
- package/src/types/entry.interface.ts +0 -7
- package/src/types/index.ts +0 -2
- package/src/types/xss-white-list.interface.ts +0 -7
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export function cleanReply(s: string): string {
|
|
2
|
-
return (s ? s.split('\n')
|
|
3
|
-
.filter(item => item.toLowerCase().includes('posted using [partiko') === false)
|
|
4
|
-
.filter(item => item.toLowerCase().includes('posted using [dapplr') === false)
|
|
5
|
-
.filter(item => item.toLowerCase().includes('posted using [leofinance') === false)
|
|
6
|
-
.filter(item => item.toLowerCase().includes('posted via [neoxian') === false)
|
|
7
|
-
.filter(item => item.toLowerCase().includes('posted using [neoxian') === false)
|
|
8
|
-
.filter(item => item.toLowerCase().includes('posted with [stemgeeks') === false)
|
|
9
|
-
.filter(item => item.toLowerCase().includes('posted using [bilpcoin') === false)
|
|
10
|
-
.filter(item => item.toLowerCase().includes('posted using [inleo') === false)
|
|
11
|
-
.filter(item => item.toLowerCase().includes('posted using [sportstalksocial]') === false)
|
|
12
|
-
.filter(item => item.toLowerCase().includes('<center><sub>[posted using aeneas.blog') === false)
|
|
13
|
-
.filter(item => item.toLowerCase().includes('<center><sub>posted via [proofofbrain.io') === false)
|
|
14
|
-
.filter(item => item.toLowerCase().includes('<center>posted on [hypnochain') === false)
|
|
15
|
-
.filter(item => item.toLowerCase().includes('<center><sub>posted via [weedcash.network') === false)
|
|
16
|
-
.filter(item => item.toLowerCase().includes('<center>posted on [naturalmedicine.io') === false)
|
|
17
|
-
.filter(item => item.toLowerCase().includes('<center><sub>posted via [musicforlife.io') === false)
|
|
18
|
-
.filter(item => item.toLowerCase().includes('if the truvvl embed is unsupported by your current frontend, click this link to view this story') === false)
|
|
19
|
-
.filter(item => item.toLowerCase().includes('<center><em>posted from truvvl') === false)
|
|
20
|
-
.filter(item => item.toLowerCase().includes('view this post <a href="https://travelfeed.io/') === false)
|
|
21
|
-
.filter(item => item.toLowerCase().includes('read this post on travelfeed.io for the best experience') === false)
|
|
22
|
-
.filter(item => item.toLowerCase().includes('posted via <a href="https://www.dporn.co/"') === false)
|
|
23
|
-
.filter(item => item.toLowerCase().includes('▶️ [watch on 3speak](https://3speak') === false)
|
|
24
|
-
.filter(item => item.toLowerCase().includes('<sup><sub>posted via [inji.com]') === false)
|
|
25
|
-
.filter(item => item.toLowerCase().includes('view this post on [liketu]') === false)
|
|
26
|
-
.filter(item => item.toLowerCase().includes('[via Inbox]') === false)
|
|
27
|
-
.join('\n') : '')
|
|
28
|
-
.replace('Posted via <a href="https://d.buzz" data-link="promote-link">D.Buzz</a>', '')
|
|
29
|
-
.replace('<div class="pull-right"><a href="/@hive.engage"></a></div>', '')
|
|
30
|
-
.replace('<div><a href="https://engage.hivechain.app"></a></div>', '')
|
|
31
|
-
.replace(`<div class="text-center"><img src="https://cdn.steemitimages.com/DQmNp6YwAm2qwquALZw8PdcovDorwaBSFuxQ38TrYziGT6b/A-20.png"><a href="https://bit.ly/actifit-app"><img src="https://cdn.steemitimages.com/DQmQqfpSmcQtfrHAtzfBtVccXwUL9vKNgZJ2j93m8WNjizw/l5.png"></a><a href="https://bit.ly/actifit-ios"><img src="https://cdn.steemitimages.com/DQmbWy8KzKT1UvCvznUTaFPw6wBUcyLtBT5XL9wdbB7Hfmn/l6.png"></a></div>`, '')
|
|
32
|
-
}
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import { ARCH_REGEX, DAPPLR_REGEX, LBRY_REGEX, TRUVVL_REGEX, ODYSEE_REGEX, SKATEHIVE_IPFS_REGEX, BITCHUTE_REGEX, RUMBLE_REGEX, BRIGHTEON_REGEX, VIMEO_EMBED_REGEX, SPEAK_EMBED_REGEX, VIMM_EMBED_REGEX, D_TUBE_EMBED_REGEX, SPOTIFY_EMBED_REGEX, SOUNDCLOUD_EMBED_REGEX, TWITCH_EMBED_REGEX, YOUTUBE_EMBED_REGEX, BRAND_NEW_TUBE_REGEX, LOOM_EMBED_REGEX, AUREAL_EMBED_REGEX } from '../consts'
|
|
2
|
-
|
|
3
|
-
export function iframe(el: HTMLElement | null): void {
|
|
4
|
-
if (!el || !el.parentNode) {
|
|
5
|
-
return;
|
|
6
|
-
}
|
|
7
|
-
const src = el.getAttribute('src');
|
|
8
|
-
if (!src) {
|
|
9
|
-
el.parentNode.removeChild(el);
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Youtube
|
|
14
|
-
if (src.match(YOUTUBE_EMBED_REGEX)) {
|
|
15
|
-
// strip query string (yt: autoplay=1,controls=0,showinfo=0, etc)
|
|
16
|
-
const s = src.replace(/\?.+$/, '');
|
|
17
|
-
el.setAttribute('src', s);
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (src.match(BITCHUTE_REGEX)) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Vimeo
|
|
26
|
-
const m = src.match(VIMEO_EMBED_REGEX);
|
|
27
|
-
if (m && m.length === 2) {
|
|
28
|
-
const s = `https://player.vimeo.com/video/${m[1]}`;
|
|
29
|
-
el.setAttribute('src', s);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Twitch
|
|
34
|
-
if (src.match(TWITCH_EMBED_REGEX)) {
|
|
35
|
-
const parentDomain = 'ecency.com';
|
|
36
|
-
const s = `${src}&parent=${parentDomain}&autoplay=false`;
|
|
37
|
-
el.setAttribute('src', s);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// 3Speak
|
|
42
|
-
if (src.match(SPEAK_EMBED_REGEX)) {
|
|
43
|
-
const normalizedSrc = src.replace(/3speak\.[a-z]+/i, '3speak.tv');
|
|
44
|
-
const hasAutoplay = /[?&]autoplay=/.test(normalizedSrc);
|
|
45
|
-
const s = hasAutoplay
|
|
46
|
-
? normalizedSrc
|
|
47
|
-
: `${normalizedSrc}${normalizedSrc.includes('?') ? '&' : '?'}autoplay=true`;
|
|
48
|
-
el.setAttribute('src', s);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Spotify
|
|
53
|
-
if (src.match(SPOTIFY_EMBED_REGEX)) {
|
|
54
|
-
el.setAttribute('src', src);
|
|
55
|
-
el.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups');
|
|
56
|
-
el.setAttribute('frameborder', '0');
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Soundcloud
|
|
61
|
-
if (src.match(SOUNDCLOUD_EMBED_REGEX)) {
|
|
62
|
-
const match = src.match(/url=(.+?)&/);
|
|
63
|
-
if (match && match.length === 2) {
|
|
64
|
-
const s = `https://w.soundcloud.com/player/?url=${match[1]}&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&visual=true`;
|
|
65
|
-
el.setAttribute('src', s);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Dtube
|
|
71
|
-
if (src.match(D_TUBE_EMBED_REGEX)) {
|
|
72
|
-
el.setAttribute('src', src);
|
|
73
|
-
el.setAttribute('sandbox', 'allow-scripts allow-same-origin');
|
|
74
|
-
el.setAttribute('frameborder', '0');
|
|
75
|
-
el.setAttribute('allowfullscreen', 'true');
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// VIMM
|
|
80
|
-
if (src.match(VIMM_EMBED_REGEX)) {
|
|
81
|
-
el.setAttribute('src', src);
|
|
82
|
-
el.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups');
|
|
83
|
-
el.setAttribute('frameborder', '0');
|
|
84
|
-
el.setAttribute('allowfullscreen', 'true');
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Dapplr
|
|
89
|
-
if (src.match(DAPPLR_REGEX)) {
|
|
90
|
-
el.setAttribute('src', src);
|
|
91
|
-
el.setAttribute('sandbox', 'allow-scripts allow-same-origin');
|
|
92
|
-
el.setAttribute('frameborder', '0');
|
|
93
|
-
el.setAttribute('allowfullscreen', 'true');
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Truvvl
|
|
98
|
-
if (src.match(TRUVVL_REGEX)) {
|
|
99
|
-
el.setAttribute('src', src);
|
|
100
|
-
el.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups');
|
|
101
|
-
el.setAttribute('frameborder', '0');
|
|
102
|
-
el.setAttribute('class', 'portrait-embed');
|
|
103
|
-
el.setAttribute('allowfullscreen', 'true');
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// LBRY.tv
|
|
108
|
-
if (src.match(LBRY_REGEX)) {
|
|
109
|
-
el.setAttribute('src', src);
|
|
110
|
-
el.setAttribute('frameborder', '0');
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ODYSEE
|
|
115
|
-
if (src.match(ODYSEE_REGEX)) {
|
|
116
|
-
el.setAttribute('src', src);
|
|
117
|
-
el.setAttribute('frameborder', '0');
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// IPFS Skatehive
|
|
122
|
-
if (src.match(SKATEHIVE_IPFS_REGEX)) {
|
|
123
|
-
el.setAttribute('src', src);
|
|
124
|
-
el.setAttribute('allowfullscreen', 'true');
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// archive.org
|
|
129
|
-
if (src.match(ARCH_REGEX)) {
|
|
130
|
-
el.setAttribute('src', src);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Rumble
|
|
135
|
-
if (src.match(RUMBLE_REGEX)) {
|
|
136
|
-
el.setAttribute('src', src);
|
|
137
|
-
el.setAttribute('frameborder', '0');
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Brigtheon
|
|
142
|
-
if (src.match(BRIGHTEON_REGEX)) {
|
|
143
|
-
el.setAttribute('src', src);
|
|
144
|
-
el.setAttribute('frameborder', '0');
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Brandnew Tube
|
|
149
|
-
if (src.match(BRAND_NEW_TUBE_REGEX)) {
|
|
150
|
-
el.setAttribute('src', src);
|
|
151
|
-
el.setAttribute('frameborder', '0');
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Loom
|
|
156
|
-
if (src.match(LOOM_EMBED_REGEX)) {
|
|
157
|
-
el.setAttribute('src', src);
|
|
158
|
-
el.setAttribute('frameborder', '0');
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Aureal
|
|
163
|
-
if (src.match(AUREAL_EMBED_REGEX)) {
|
|
164
|
-
el.setAttribute('src', src);
|
|
165
|
-
el.setAttribute('frameborder', '0');
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const replaceNode = el.ownerDocument.createElement('div');
|
|
170
|
-
replaceNode.setAttribute('class', 'unsupported-iframe');
|
|
171
|
-
replaceNode.textContent = `(Unsupported ${src})`;
|
|
172
|
-
if (el.parentNode) {
|
|
173
|
-
el.parentNode.insertBefore(replaceNode, el);
|
|
174
|
-
el.parentNode.removeChild(el);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { proxifyImageSrc } from "../proxify-image-src";
|
|
2
|
-
|
|
3
|
-
export function img(el: HTMLElement, webp: boolean, state?: { firstImageFound: boolean }): void {
|
|
4
|
-
let src = el.getAttribute("src") || "";
|
|
5
|
-
|
|
6
|
-
// Normalize encoded characters
|
|
7
|
-
const decodedSrc = decodeURIComponent(
|
|
8
|
-
src.replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(dec))
|
|
9
|
-
.replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
10
|
-
).trim();
|
|
11
|
-
|
|
12
|
-
// ❌ Remove if javascript or empty/invalid
|
|
13
|
-
const isInvalid = !src || decodedSrc.startsWith("javascript") || decodedSrc.startsWith("vbscript") || decodedSrc === "x";
|
|
14
|
-
if (isInvalid) {
|
|
15
|
-
src = ""
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// ❌ Skip relative paths (e.g., `photo.jpg`, `./photo.png`, `assets/pic.jpeg`)
|
|
19
|
-
const isRelative = !/^https?:\/\//i.test(src) && !src.startsWith("/");
|
|
20
|
-
if (isRelative) {
|
|
21
|
-
//console.warn("Skipped relative image:", src);
|
|
22
|
-
src = ""
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Sanitize any dynamic or low-res src-like attributes
|
|
26
|
-
["onerror", "dynsrc", "lowsrc", "width", "height"].forEach(attr => el.removeAttribute(attr));
|
|
27
|
-
|
|
28
|
-
el.setAttribute("itemprop", "image");
|
|
29
|
-
const isLCP = state && !state.firstImageFound;
|
|
30
|
-
|
|
31
|
-
if (isLCP) {
|
|
32
|
-
el.setAttribute("loading", "eager");
|
|
33
|
-
el.setAttribute("fetchpriority", "high");
|
|
34
|
-
state.firstImageFound = true;
|
|
35
|
-
} else {
|
|
36
|
-
el.setAttribute("loading", "lazy");
|
|
37
|
-
el.setAttribute("decoding", "async");
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const cls = el.getAttribute("class") || "";
|
|
42
|
-
const shouldReplace = !cls.includes("no-replace");
|
|
43
|
-
const hasAlreadyProxied = src.startsWith("https://images.ecency.com");
|
|
44
|
-
|
|
45
|
-
if (shouldReplace && !hasAlreadyProxied) {
|
|
46
|
-
const proxified = proxifyImageSrc(src, 0, 0, webp ? "webp" : "match");
|
|
47
|
-
el.setAttribute("src", proxified);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function createImageHTML(src: string, isLCP: boolean, webp: boolean): string {
|
|
52
|
-
const loading = isLCP ? 'eager' : 'lazy';
|
|
53
|
-
const fetch = isLCP ? 'fetchpriority="high"' : 'decoding="async"';
|
|
54
|
-
const proxified = proxifyImageSrc(src, 0, 0, webp ? 'webp' : 'match');
|
|
55
|
-
return `<img
|
|
56
|
-
class="markdown-img-link"
|
|
57
|
-
src="${proxified}"
|
|
58
|
-
loading="${loading}"
|
|
59
|
-
${fetch}
|
|
60
|
-
itemprop="image"
|
|
61
|
-
/>`;
|
|
62
|
-
}
|
package/src/methods/index.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export * from './noop.method'
|
|
2
|
-
export * from './get-inner-html.method'
|
|
3
|
-
export * from './remove-child-nodes.method'
|
|
4
|
-
export * from './sanitize-html.method'
|
|
5
|
-
export * from './traverse.method'
|
|
6
|
-
export * from './clean-reply.method'
|
|
7
|
-
export * from './markdown-to-html.method'
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { IMG_REGEX, SECTION_LIST } from '../consts'
|
|
2
|
-
import { proxifyImageSrc } from '../proxify-image-src'
|
|
3
|
-
import { isValidPermlink, isValidUsername, sanitizePermlink } from "../helper";
|
|
4
|
-
import { createImageHTML } from "./img.method";
|
|
5
|
-
|
|
6
|
-
export function linkify(content: string, forApp: boolean, webp: boolean): string {
|
|
7
|
-
// Tags
|
|
8
|
-
content = content.replace(/(^|\s|>)(#[-a-z\d]+)/gi, tag => {
|
|
9
|
-
if (/#[\d]+$/.test(tag)) return tag // do not allow only numbers (like #1)
|
|
10
|
-
const preceding = /^\s|>/.test(tag) ? tag[0] : '' // space or closing tag (>)
|
|
11
|
-
tag = tag.replace('>', '') // remove closing tag
|
|
12
|
-
const tag2 = tag.trim().substring(1)
|
|
13
|
-
const tagLower = tag2.toLowerCase()
|
|
14
|
-
|
|
15
|
-
const attrs = forApp ? `data-tag="${tagLower}"` : `href="/trending/${tagLower}"`
|
|
16
|
-
return `${preceding}<a class="markdown-tag-link" ${attrs}>${tag.trim()}</a>`
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
// User mentions
|
|
20
|
-
content = content.replace(
|
|
21
|
-
/(^|[^a-zA-Z0-9_!#$%&*@@/]|(^|[^a-zA-Z0-9_+~.-/]))[@@]([a-z][-.a-z\d^/]+[a-z\d])/gi,
|
|
22
|
-
(match, preceeding1, preceeding2, user) => {
|
|
23
|
-
const userLower = user.toLowerCase()
|
|
24
|
-
const preceedings = (preceeding1 || '') + (preceeding2 || '')
|
|
25
|
-
if (userLower.indexOf('/') === -1 && isValidUsername(user)) {
|
|
26
|
-
const attrs = forApp ? `data-author="${userLower}"` : `href="/@${userLower}"`
|
|
27
|
-
return `${preceedings}<a class="markdown-author-link" ${attrs}>@${user}</a>`
|
|
28
|
-
} else {
|
|
29
|
-
return match
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
// internal links
|
|
35
|
-
content = content.replace(
|
|
36
|
-
/((^|\s)(\/|)@[\w.\d-]+)\/(\S+)/gi, (match, u, p1, p2, p3) => {
|
|
37
|
-
const uu = u.trim().toLowerCase().replace('/@', '').replace('@', '');
|
|
38
|
-
const permlink = sanitizePermlink(p3);
|
|
39
|
-
if (!isValidPermlink(permlink)) return match;
|
|
40
|
-
|
|
41
|
-
if (SECTION_LIST.some(v => p3.includes(v))) {
|
|
42
|
-
const attrs = forApp ? `https://ecency.com/@${uu}/${permlink}` : `href="/@${uu}/${permlink}"`
|
|
43
|
-
return ` <a class="markdown-profile-link" ${attrs}>@${uu}/${permlink}</a>`
|
|
44
|
-
} else {
|
|
45
|
-
const attrs = forApp ? `data-author="${uu}" data-tag="post" data-permlink="${permlink}"` : `href="/post/@${uu}/${permlink}"`
|
|
46
|
-
return ` <a class="markdown-post-link" ${attrs}>@${uu}/${permlink}</a>`
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
// Image links
|
|
52
|
-
let firstImageUsed = false;
|
|
53
|
-
|
|
54
|
-
content = content.replace(IMG_REGEX, (imglink) => {
|
|
55
|
-
const isLCP = !firstImageUsed;
|
|
56
|
-
firstImageUsed = true;
|
|
57
|
-
return createImageHTML(imglink, isLCP, webp);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
return content
|
|
61
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { traverse } from './traverse.method'
|
|
2
|
-
import { sanitizeHtml } from './sanitize-html.method'
|
|
3
|
-
import { DOMParser, ENTITY_REGEX } from '../consts'
|
|
4
|
-
import xmldom from 'xmldom'
|
|
5
|
-
|
|
6
|
-
const lolight = require('lolight')
|
|
7
|
-
const { Remarkable } = require('remarkable')
|
|
8
|
-
const { linkify } = require('remarkable/linkify')
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export function markdownToHTML(input: string, forApp: boolean, webp: boolean): string {
|
|
12
|
-
// Internalize leofinance.io links
|
|
13
|
-
input = input.replace(new RegExp("https://leofinance.io/threads/view/","g"), "/@");
|
|
14
|
-
input = input.replace(new RegExp("https://leofinance.io/posts/","g"), "/@");
|
|
15
|
-
input = input.replace(new RegExp("https://leofinance.io/threads/","g"), "/@");
|
|
16
|
-
input = input.replace(new RegExp("https://inleo.io/threads/view/","g"), "/@");
|
|
17
|
-
input = input.replace(new RegExp("https://inleo.io/posts/","g"), "/@");
|
|
18
|
-
input = input.replace(new RegExp("https://inleo.io/threads/","g"), "/@");
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const md = new Remarkable({
|
|
22
|
-
html: true,
|
|
23
|
-
breaks: true,
|
|
24
|
-
typographer: false,
|
|
25
|
-
highlight: function (str: string) {
|
|
26
|
-
try {
|
|
27
|
-
const tokens = lolight.tok(str);
|
|
28
|
-
return tokens.map(
|
|
29
|
-
(token: string[]) => `<span class="ll-${token[0]}">${token[1]}</span>`
|
|
30
|
-
).join('')
|
|
31
|
-
} catch (err) { console.error(err) }
|
|
32
|
-
|
|
33
|
-
return str
|
|
34
|
-
}
|
|
35
|
-
}).use(linkify)
|
|
36
|
-
md.core.ruler.enable([
|
|
37
|
-
'abbr'
|
|
38
|
-
]);
|
|
39
|
-
md.block.ruler.enable([
|
|
40
|
-
'footnote',
|
|
41
|
-
'deflist'
|
|
42
|
-
]);
|
|
43
|
-
md.inline.ruler.enable([
|
|
44
|
-
'footnote_inline',
|
|
45
|
-
'ins',
|
|
46
|
-
'mark',
|
|
47
|
-
'sub',
|
|
48
|
-
'sup'
|
|
49
|
-
]);
|
|
50
|
-
const XMLSerializer = new xmldom.XMLSerializer()
|
|
51
|
-
|
|
52
|
-
if (!input) {
|
|
53
|
-
return ''
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let output = '';
|
|
57
|
-
|
|
58
|
-
//encrypt entities
|
|
59
|
-
const entities = input.match(ENTITY_REGEX);
|
|
60
|
-
const encEntities:string[] = [];
|
|
61
|
-
|
|
62
|
-
try{
|
|
63
|
-
if(entities && forApp){
|
|
64
|
-
entities.forEach((entity)=>{
|
|
65
|
-
const CryptoJS = require("react-native-crypto-js");
|
|
66
|
-
const encData = CryptoJS.AES.encrypt(entity, 'key').toString();
|
|
67
|
-
const encyptedEntity = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(encData));
|
|
68
|
-
encEntities.push(encyptedEntity);
|
|
69
|
-
input = input.replace(entity, encyptedEntity);
|
|
70
|
-
})
|
|
71
|
-
}
|
|
72
|
-
} catch (err){
|
|
73
|
-
console.log("failed to encrypt entities, ignore if not using mobile");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
output = md.render(input)
|
|
79
|
-
const doc = DOMParser.parseFromString(`<body id="root">${output}</body>`, 'text/html')
|
|
80
|
-
|
|
81
|
-
traverse(doc, forApp, 0, webp)
|
|
82
|
-
|
|
83
|
-
output = XMLSerializer.serializeToString(doc)
|
|
84
|
-
} catch (error) {
|
|
85
|
-
output = ''
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
//decrypt and put back entiteis
|
|
89
|
-
if(forApp && output){
|
|
90
|
-
encEntities.forEach((encEntity)=>{
|
|
91
|
-
const CryptoJS = require("react-native-crypto-js");
|
|
92
|
-
const decData = CryptoJS.enc.Base64.parse(encEntity).toString(CryptoJS.enc.Utf8);
|
|
93
|
-
const entity = CryptoJS.AES.decrypt(decData, 'key').toString(CryptoJS.enc.Utf8);
|
|
94
|
-
output = output.replace(encEntity, entity);
|
|
95
|
-
})
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
output = output.replace(/ xmlns="http:\/\/www.w3.org\/1999\/xhtml"/g, '')
|
|
99
|
-
.replace('<body id="root">', '')
|
|
100
|
-
.replace('</body>', '')
|
|
101
|
-
.trim()
|
|
102
|
-
|
|
103
|
-
return sanitizeHtml(output)
|
|
104
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export function noop(): void {}
|
package/src/methods/p.method.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import xss from 'xss'
|
|
2
|
-
import {ALLOWED_ATTRIBUTES, ID_WHITELIST} from '../consts'
|
|
3
|
-
|
|
4
|
-
const decodeEntities = (input: string): string =>
|
|
5
|
-
input
|
|
6
|
-
.replace(/&#(\d+);?/g, (_, dec) => String.fromCharCode(dec))
|
|
7
|
-
.replace(/&#x([0-9a-f]+);?/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
8
|
-
|
|
9
|
-
export function sanitizeHtml(html: string): string {
|
|
10
|
-
return xss(html, {
|
|
11
|
-
whiteList: ALLOWED_ATTRIBUTES,
|
|
12
|
-
stripIgnoreTag: true,
|
|
13
|
-
stripIgnoreTagBody: ['style'],
|
|
14
|
-
css: false, // block style attrs entirely for safety
|
|
15
|
-
onTagAttr: (tag, name, value) => {
|
|
16
|
-
const decoded = decodeEntities(value.trim().toLowerCase());
|
|
17
|
-
|
|
18
|
-
if (name.startsWith('on')) return ''; // 🛡 event handlers
|
|
19
|
-
if (tag === 'img' && name === 'src' && (!/^https?:\/\//.test(decoded) || decoded.startsWith('javascript:'))) return '';
|
|
20
|
-
if (
|
|
21
|
-
tag === 'video' && ['src', 'poster'].includes(name) &&
|
|
22
|
-
(!/^https?:\/\//.test(decoded) || decoded.startsWith('javascript:'))
|
|
23
|
-
) return '';
|
|
24
|
-
if (tag === 'img' && ['dynsrc', 'lowsrc'].includes(name)) return '';
|
|
25
|
-
if (tag === 'span' && name === 'class' && value === 'wr') return '';
|
|
26
|
-
if (name === 'id') {
|
|
27
|
-
if (!ID_WHITELIST.test(decoded)) return '';
|
|
28
|
-
}
|
|
29
|
-
return undefined;
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { IMG_REGEX, YOUTUBE_REGEX, WHITE_LIST, DOMParser, POST_REGEX } from '../consts'
|
|
2
|
-
import { extractYtStartTime, isValidPermlink, isValidUsername, sanitizePermlink } from '../helper'
|
|
3
|
-
import { proxifyImageSrc } from '../proxify-image-src'
|
|
4
|
-
import { linkify } from './linkify.method'
|
|
5
|
-
import {createImageHTML} from "./img.method";
|
|
6
|
-
|
|
7
|
-
export function text(node: HTMLElement | null, forApp: boolean, webp: boolean): void {
|
|
8
|
-
if (!node || !node.parentNode) {
|
|
9
|
-
return
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
if (['a', 'code'].includes(node.parentNode.nodeName)) {
|
|
13
|
-
return
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const nodeValue = node.nodeValue || ''
|
|
17
|
-
const linkified = linkify(nodeValue, forApp, webp)
|
|
18
|
-
if (linkified !== nodeValue) {
|
|
19
|
-
const replaceNode = DOMParser.parseFromString(
|
|
20
|
-
`<span class="wr">${linkified}</span>`
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
node.parentNode.insertBefore(replaceNode, node)
|
|
24
|
-
node.parentNode.removeChild(node)
|
|
25
|
-
return
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (nodeValue.match(IMG_REGEX)) {
|
|
29
|
-
const isLCP = false; // Traverse handles LCP; no need to double-count
|
|
30
|
-
const imageHTML = createImageHTML(nodeValue, isLCP, webp);
|
|
31
|
-
const replaceNode = DOMParser.parseFromString(imageHTML);
|
|
32
|
-
node.parentNode.replaceChild(replaceNode, node);
|
|
33
|
-
}
|
|
34
|
-
// If a youtube video
|
|
35
|
-
if (nodeValue.match(YOUTUBE_REGEX)) {
|
|
36
|
-
const e = YOUTUBE_REGEX.exec(nodeValue)
|
|
37
|
-
if (e[1]) {
|
|
38
|
-
const vid = e[1]
|
|
39
|
-
const thumbnail = proxifyImageSrc(`https://img.youtube.com/vi/${vid.split('?')[0]}/hqdefault.jpg`, 0, 0, webp ? 'webp' : 'match')
|
|
40
|
-
const embedSrc = `https://www.youtube.com/embed/${vid}?autoplay=1`
|
|
41
|
-
|
|
42
|
-
let attrs = `class="markdown-video-link markdown-video-link-youtube" data-embed-src="${embedSrc}" data-youtube="${vid}"`
|
|
43
|
-
//extract start time if available
|
|
44
|
-
const startTime = extractYtStartTime(nodeValue);
|
|
45
|
-
if(startTime){
|
|
46
|
-
attrs = attrs.concat(` data-start-time="${startTime}"`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const thumbImg = node.ownerDocument.createElement('img')
|
|
50
|
-
thumbImg.setAttribute('class', 'no-replace video-thumbnail')
|
|
51
|
-
thumbImg.setAttribute('src', thumbnail)
|
|
52
|
-
|
|
53
|
-
const play = node.ownerDocument.createElement('span')
|
|
54
|
-
play.setAttribute('class', 'markdown-video-play')
|
|
55
|
-
|
|
56
|
-
const replaceNode = DOMParser.parseFromString(`<p><a ${attrs}>${thumbImg}${play}</a></p>`)
|
|
57
|
-
node.parentNode.replaceChild(replaceNode, node)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
if (nodeValue && typeof nodeValue === 'string') {
|
|
61
|
-
const postMatch = nodeValue.trim().match(POST_REGEX)
|
|
62
|
-
if (postMatch && WHITE_LIST.includes(postMatch[1].replace(/www./,''))) {
|
|
63
|
-
const tag = postMatch[2]
|
|
64
|
-
const author = postMatch[3].replace('@', '')
|
|
65
|
-
const permlink = sanitizePermlink(postMatch[4])
|
|
66
|
-
|
|
67
|
-
if (!isValidUsername(author)) return
|
|
68
|
-
if (!isValidPermlink(permlink)) return
|
|
69
|
-
|
|
70
|
-
const attrs = forApp ? `data-tag="${tag}" data-author="${author}" data-permlink="${permlink}" class="markdown-post-link"` : `class="markdown-post-link" href="/${tag}/@${author}/${permlink}"`
|
|
71
|
-
const replaceNode = DOMParser.parseFromString(
|
|
72
|
-
`<a ${attrs}>/@${author}/${permlink}</a>`
|
|
73
|
-
)
|
|
74
|
-
node.parentNode.replaceChild(replaceNode, node)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { a } from './a.method'
|
|
2
|
-
import { iframe } from './iframe.method'
|
|
3
|
-
import { img } from './img.method'
|
|
4
|
-
import { p } from './p.method'
|
|
5
|
-
import { text } from './text.method'
|
|
6
|
-
|
|
7
|
-
export function traverse(node: Node, forApp: boolean, depth = 0, webp = false, state = { firstImageFound: false }): void {
|
|
8
|
-
if (!node || !node.childNodes) {
|
|
9
|
-
return
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
Array.from(Array(node.childNodes.length).keys())
|
|
13
|
-
.map(i => node.childNodes[i])
|
|
14
|
-
.forEach(child => {
|
|
15
|
-
if (child.nodeName.toLowerCase() === 'a') {
|
|
16
|
-
a(<HTMLElement>child, forApp, webp)
|
|
17
|
-
}
|
|
18
|
-
if (child.nodeName.toLowerCase() === 'iframe') {
|
|
19
|
-
iframe(<HTMLElement>child)
|
|
20
|
-
}
|
|
21
|
-
if (child.nodeName === '#text') {
|
|
22
|
-
text(<HTMLElement>child, forApp, webp)
|
|
23
|
-
}
|
|
24
|
-
if (child.nodeName.toLowerCase() === 'img') {
|
|
25
|
-
img(<HTMLElement>child, webp, state)
|
|
26
|
-
}
|
|
27
|
-
if (child.nodeName.toLowerCase() === 'p') {
|
|
28
|
-
p(<HTMLElement>child)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
traverse(child, forApp, depth + 1, webp, state)
|
|
32
|
-
})
|
|
33
|
-
}
|