@ecency/render-helper 2.3.17 → 2.4.2

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.
Files changed (155) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +171 -17
  3. package/dist/browser/index.d.ts +32 -0
  4. package/dist/browser/index.js +1556 -0
  5. package/dist/browser/index.js.map +1 -0
  6. package/dist/node/index.cjs +1575 -0
  7. package/dist/node/index.cjs.map +1 -0
  8. package/dist/node/index.mjs +1559 -0
  9. package/dist/node/index.mjs.map +1 -0
  10. package/package.json +49 -47
  11. package/lib/cache.d.ts +0 -3
  12. package/lib/cache.js +0 -21
  13. package/lib/cache.js.map +0 -1
  14. package/lib/catch-post-image.d.ts +0 -2
  15. package/lib/catch-post-image.js +0 -76
  16. package/lib/catch-post-image.js.map +0 -1
  17. package/lib/consts/allowed-attributes.const.d.ts +0 -2
  18. package/lib/consts/allowed-attributes.const.js +0 -71
  19. package/lib/consts/allowed-attributes.const.js.map +0 -1
  20. package/lib/consts/dom-parser.const.d.ts +0 -2
  21. package/lib/consts/dom-parser.const.js +0 -12
  22. package/lib/consts/dom-parser.const.js.map +0 -1
  23. package/lib/consts/index.d.ts +0 -5
  24. package/lib/consts/index.js +0 -22
  25. package/lib/consts/index.js.map +0 -1
  26. package/lib/consts/regexes.const.d.ts +0 -44
  27. package/lib/consts/regexes.const.js +0 -49
  28. package/lib/consts/regexes.const.js.map +0 -1
  29. package/lib/consts/section-list.const.d.ts +0 -1
  30. package/lib/consts/section-list.const.js +0 -24
  31. package/lib/consts/section-list.const.js.map +0 -1
  32. package/lib/consts/white-list.const.d.ts +0 -2
  33. package/lib/consts/white-list.const.js +0 -37
  34. package/lib/consts/white-list.const.js.map +0 -1
  35. package/lib/helper.d.ts +0 -6
  36. package/lib/helper.js +0 -72
  37. package/lib/helper.js.map +0 -1
  38. package/lib/index.d.ts +0 -8
  39. package/lib/index.js +0 -19
  40. package/lib/index.js.map +0 -1
  41. package/lib/markdown-2-html.d.ts +0 -2
  42. package/lib/markdown-2-html.js +0 -25
  43. package/lib/markdown-2-html.js.map +0 -1
  44. package/lib/methods/a.method.d.ts +0 -1
  45. package/lib/methods/a.method.js +0 -647
  46. package/lib/methods/a.method.js.map +0 -1
  47. package/lib/methods/clean-reply.method.d.ts +0 -1
  48. package/lib/methods/clean-reply.method.js +0 -37
  49. package/lib/methods/clean-reply.method.js.map +0 -1
  50. package/lib/methods/get-inner-html.method.d.ts +0 -1
  51. package/lib/methods/get-inner-html.method.js +0 -16
  52. package/lib/methods/get-inner-html.method.js.map +0 -1
  53. package/lib/methods/iframe.method.d.ts +0 -1
  54. package/lib/methods/iframe.method.js +0 -159
  55. package/lib/methods/iframe.method.js.map +0 -1
  56. package/lib/methods/img.method.d.ts +0 -4
  57. package/lib/methods/img.method.js +0 -50
  58. package/lib/methods/img.method.js.map +0 -1
  59. package/lib/methods/index.d.ts +0 -7
  60. package/lib/methods/index.js +0 -24
  61. package/lib/methods/index.js.map +0 -1
  62. package/lib/methods/linkify.method.d.ts +0 -1
  63. package/lib/methods/linkify.method.js +0 -56
  64. package/lib/methods/linkify.method.js.map +0 -1
  65. package/lib/methods/markdown-to-html.method.d.ts +0 -1
  66. package/lib/methods/markdown-to-html.method.js +0 -98
  67. package/lib/methods/markdown-to-html.method.js.map +0 -1
  68. package/lib/methods/noop.method.d.ts +0 -1
  69. package/lib/methods/noop.method.js +0 -6
  70. package/lib/methods/noop.method.js.map +0 -1
  71. package/lib/methods/p.method.d.ts +0 -1
  72. package/lib/methods/p.method.js +0 -11
  73. package/lib/methods/p.method.js.map +0 -1
  74. package/lib/methods/remove-child-nodes.method.d.ts +0 -1
  75. package/lib/methods/remove-child-nodes.method.js +0 -10
  76. package/lib/methods/remove-child-nodes.method.js.map +0 -1
  77. package/lib/methods/sanitize-html.method.d.ts +0 -1
  78. package/lib/methods/sanitize-html.method.js +0 -42
  79. package/lib/methods/sanitize-html.method.js.map +0 -1
  80. package/lib/methods/text.method.d.ts +0 -1
  81. package/lib/methods/text.method.js +0 -69
  82. package/lib/methods/text.method.js.map +0 -1
  83. package/lib/methods/traverse.method.d.ts +0 -3
  84. package/lib/methods/traverse.method.js +0 -38
  85. package/lib/methods/traverse.method.js.map +0 -1
  86. package/lib/post-body-summary.d.ts +0 -2
  87. package/lib/post-body-summary.js +0 -123
  88. package/lib/post-body-summary.js.map +0 -1
  89. package/lib/proxify-image-src.d.ts +0 -5
  90. package/lib/proxify-image-src.js +0 -86
  91. package/lib/proxify-image-src.js.map +0 -1
  92. package/lib/render-helper.js +0 -1
  93. package/lib/types/entry.interface.d.ts +0 -7
  94. package/lib/types/entry.interface.js +0 -3
  95. package/lib/types/entry.interface.js.map +0 -1
  96. package/lib/types/index.d.ts +0 -2
  97. package/lib/types/index.js +0 -19
  98. package/lib/types/index.js.map +0 -1
  99. package/lib/types/xss-white-list.interface.d.ts +0 -6
  100. package/lib/types/xss-white-list.interface.js +0 -3
  101. package/lib/types/xss-white-list.interface.js.map +0 -1
  102. package/src/cache.ts +0 -15
  103. package/src/catch-post-image.spec.ts +0 -144
  104. package/src/catch-post-image.ts +0 -78
  105. package/src/consts/allowed-attributes.const.ts +0 -69
  106. package/src/consts/dom-parser.const.ts +0 -6
  107. package/src/consts/index.ts +0 -5
  108. package/src/consts/regexes.const.ts +0 -46
  109. package/src/consts/section-list.const.ts +0 -20
  110. package/src/consts/white-list.const.ts +0 -33
  111. package/src/external-types/multihashes.d.ts +0 -3
  112. package/src/helper.spec.ts +0 -16
  113. package/src/helper.ts +0 -77
  114. package/src/index.ts +0 -18
  115. package/src/markdown-2-html.spec.ts +0 -1251
  116. package/src/markdown-2-html.ts +0 -25
  117. package/src/methods/a.method.ts +0 -791
  118. package/src/methods/clean-reply.method.ts +0 -32
  119. package/src/methods/get-inner-html.method.ts +0 -11
  120. package/src/methods/iframe.method.ts +0 -176
  121. package/src/methods/img.method.ts +0 -62
  122. package/src/methods/index.ts +0 -7
  123. package/src/methods/linkify.method.ts +0 -61
  124. package/src/methods/markdown-to-html.method.ts +0 -104
  125. package/src/methods/noop.method.ts +0 -1
  126. package/src/methods/p.method.ts +0 -6
  127. package/src/methods/remove-child-nodes.method.ts +0 -5
  128. package/src/methods/sanitize-html.method.ts +0 -32
  129. package/src/methods/text.method.ts +0 -77
  130. package/src/methods/traverse.method.ts +0 -33
  131. package/src/post-body-summary.spec.ts +0 -150
  132. package/src/post-body-summary.ts +0 -130
  133. package/src/proxify-image-src.spec.ts +0 -76
  134. package/src/proxify-image-src.ts +0 -77
  135. package/src/sanitize-html.spec.ts +0 -15
  136. package/src/test/data/index.ts +0 -12
  137. package/src/test/data/json/muratkbesiroglu____sci-fi-novel-underground-city-part-13.json +0 -2120
  138. package/src/test/data/json/steemitboard____steemitboard-notify-dunsky-20181210t153450000z.json +0 -47
  139. package/src/test/data/legacy/10.json +0 -5
  140. package/src/test/data/legacy/20.json +0 -5
  141. package/src/test/data/legacy/21.json +0 -5
  142. package/src/test/data/legacy/2112524.json +0 -5
  143. package/src/test/data/legacy/22.json +0 -5
  144. package/src/test/data/legacy/23.json +0 -5
  145. package/src/test/data/legacy/24.json +0 -5
  146. package/src/test/data/legacy/25.json +0 -5
  147. package/src/test/data/legacy/26.json +0 -5
  148. package/src/test/data/legacy/27.JSON +0 -5
  149. package/src/test/data/legacy/28.json +0 -5
  150. package/src/test/data/legacy/29.json +0 -5
  151. package/src/test/data/legacy/31.json +0 -5
  152. package/src/test/snaps.json +0 -8
  153. package/src/types/entry.interface.ts +0 -7
  154. package/src/types/index.ts +0 -2
  155. 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">![](https://i.imgur.com/XsrNmcl.png)</a></div>', '')
30
- .replace('<div><a href="https://engage.hivechain.app">![](https://i.imgur.com/XsrNmcl.png)</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,11 +0,0 @@
1
- import xmldom from 'xmldom'
2
-
3
- export function getSerializedInnerHTML(node: Node): string {
4
- const XMLSerializer = new xmldom.XMLSerializer()
5
-
6
- if (node.childNodes[0]) {
7
- return XMLSerializer.serializeToString(node.childNodes[0])
8
- }
9
-
10
- return ''
11
- }
@@ -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
- }
@@ -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 {}
@@ -1,6 +0,0 @@
1
- export function p(el: HTMLElement): void {
2
- const dir = el.getAttribute('dir')
3
- if (!dir) {
4
- el.setAttribute('dir', 'auto')
5
- }
6
- }
@@ -1,5 +0,0 @@
1
- export function removeChildNodes(node: Node): void {
2
- Array.from(Array(node.childNodes.length).keys()).forEach(x => {
3
- node.removeChild(node.childNodes[x])
4
- })
5
- }
@@ -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
- }