@ecency/render-helper 2.2.11 → 2.2.15

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 (46) hide show
  1. package/lib/catch-post-image.js +7 -7
  2. package/lib/catch-post-image.js.map +1 -1
  3. package/lib/consts/allowed-attributes.const.js +1 -0
  4. package/lib/consts/allowed-attributes.const.js.map +1 -1
  5. package/lib/consts/regexes.const.d.ts +1 -0
  6. package/lib/consts/regexes.const.js +3 -2
  7. package/lib/consts/regexes.const.js.map +1 -1
  8. package/lib/helper.d.ts +1 -0
  9. package/lib/helper.js +14 -2
  10. package/lib/helper.js.map +1 -1
  11. package/lib/markdown-2-html.js +7 -7
  12. package/lib/markdown-2-html.js.map +1 -1
  13. package/lib/methods/a.method.js +72 -54
  14. package/lib/methods/a.method.js.map +1 -1
  15. package/lib/methods/clean-reply.method.js +2 -2
  16. package/lib/methods/iframe.method.js +5 -5
  17. package/lib/methods/iframe.method.js.map +1 -1
  18. package/lib/methods/img.method.js +1 -1
  19. package/lib/methods/img.method.js.map +1 -1
  20. package/lib/methods/linkify.method.js +10 -10
  21. package/lib/methods/linkify.method.js.map +1 -1
  22. package/lib/methods/markdown-to-html.method.js +5 -5
  23. package/lib/methods/markdown-to-html.method.js.map +1 -1
  24. package/lib/methods/sanitize-html.method.js +1 -1
  25. package/lib/methods/sanitize-html.method.js.map +1 -1
  26. package/lib/methods/text.method.js +16 -10
  27. package/lib/methods/text.method.js.map +1 -1
  28. package/lib/methods/traverse.method.js +4 -4
  29. package/lib/methods/traverse.method.js.map +1 -1
  30. package/lib/post-body-summary.js +6 -5
  31. package/lib/post-body-summary.js.map +1 -1
  32. package/lib/proxify-image-src.js +12 -8
  33. package/lib/proxify-image-src.js.map +1 -1
  34. package/lib/render-helper.js +1 -1
  35. package/package.json +1 -1
  36. package/src/consts/allowed-attributes.const.ts +1 -0
  37. package/src/consts/regexes.const.ts +2 -1
  38. package/src/helper.ts +11 -0
  39. package/src/markdown-2-html.spec.ts +39 -3
  40. package/src/methods/a.method.ts +40 -13
  41. package/src/methods/clean-reply.method.ts +2 -2
  42. package/src/methods/markdown-to-html.method.ts +2 -2
  43. package/src/methods/text.method.ts +7 -1
  44. package/src/post-body-summary.ts +3 -1
  45. package/src/test/data/legacy/23.json +1 -1
  46. package/src/test/data/legacy/27.JSON +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecency/render-helper",
3
- "version": "2.2.11",
3
+ "version": "2.2.15",
4
4
  "description": "Markdown+Html Render helper",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -13,6 +13,7 @@ export const ALLOWED_ATTRIBUTES: XSSWhiteList = {
13
13
  'data-filter',
14
14
  'data-embed-src',
15
15
  'data-youtube',
16
+ 'data-start-time',
16
17
  'data-video-href',
17
18
  'data-proposal',
18
19
  'class',
@@ -11,7 +11,7 @@ export const INTERNAL_TOPIC_REGEX = /^\/(trending|hot|created|promoted|muted|pay
11
11
  export const INTERNAL_POST_TAG_REGEX = /\/(.*)\/(@[\w.\d-]+)\/(.*)/i
12
12
  export const INTERNAL_POST_REGEX = /^\/(@[\w.\d-]+)\/(.*)$/i
13
13
  export const CUSTOM_COMMUNITY_REGEX = /^https?:\/\/(.*)\/c\/(hive-\d+)(.*)/i
14
- export const YOUTUBE_REGEX = /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([^& \n<]+)(?:[^ \n<]+)?/g
14
+ export const YOUTUBE_REGEX = /(?:youtube.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu.be\/)([^"&?\/\s]{11})/g
15
15
  export const YOUTUBE_EMBED_REGEX = /^(https?:)?\/\/www.youtube.com\/(embed|shorts)\/.*/i
16
16
  export const VIMEO_REGEX = /(https?:\/\/)?(www\.)?(?:vimeo)\.com.*(?:videos|video|channels|)\/([\d]+)/i
17
17
  export const VIMEO_EMBED_REGEX = /https:\/\/player\.vimeo\.com\/video\/([0-9]+)/
@@ -39,3 +39,4 @@ export const BRAND_NEW_TUBE_REGEX = /^https:\/\/brandnewtube\.com\/embed\/[a-z0-
39
39
  export const LOOM_REGEX = /^(https?:)?\/\/www.loom.com\/share\/(.*)/i
40
40
  export const LOOM_EMBED_REGEX = /^(https?:)?\/\/www.loom.com\/embed\/(.*)/i
41
41
  export const AUREAL_EMBED_REGEX = /^(https?:\/\/)?(www\.)?(?:aureal-embed)\.web\.app\/([0-9]+)/i
42
+ export const ENTITY_REGEX = /&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-fA-F]{1,6});/ig;
package/src/helper.ts CHANGED
@@ -14,3 +14,14 @@ export function makeEntryCacheKey(entry: any): string {
14
14
  return `${entry.author}-${entry.permlink}-${entry.last_update}`
15
15
  }
16
16
 
17
+ export function extractYtStartTime(url:string):string {
18
+ const urlObj = new URL(url);
19
+ const params = new URLSearchParams(urlObj.search);
20
+ if(params.has('t')){
21
+ return '' + parseInt(params.get('t')); //parsing is important as sometimes t is famated '123s';
22
+ }else if (params.has('start')){
23
+ return params.get('start');
24
+ }
25
+
26
+ return '';
27
+ }
@@ -99,6 +99,30 @@ describe('Markdown2Html', () => {
99
99
  expect(markdown2Html(input)).toBe(expected)
100
100
  })
101
101
 
102
+ it('7.1- Should handle raw d.tube videos without thumbnail', () => {
103
+ const input = {
104
+ author: 'foo37.1',
105
+ permlink: 'bar37.1',
106
+ last_update: '2020-05-10T09:15:21',
107
+ body: 'https://d.tube/#!/v/techcoderx/QmVdEYicJwiTxSk2U9ER1Yc8Rumb1Nek4KynqAYGyQs7ga'
108
+ }
109
+ const expected = '<p><a class="markdown-video-link markdown-video-link-dtube" data-embed-src="https://emb.d.tube/#!/techcoderx/QmVdEYicJwiTxSk2U9ER1Yc8Rumb1Nek4KynqAYGyQs7ga"><span class="markdown-video-play"></span></a></p>'
110
+
111
+ expect(markdown2Html(input)).toBe(expected)
112
+ })
113
+
114
+ it('7.2- Should handle raw d.tube videos different format', () => {
115
+ const input = {
116
+ author: 'foo37.2',
117
+ permlink: 'bar37.2',
118
+ last_update: '2020-05-10T09:15:21',
119
+ body: 'https://d.tube/v/techcoderx/QmVdEYicJwiTxSk2U9ER1Yc8Rumb1Nek4KynqAYGyQs7ga'
120
+ }
121
+ const expected = '<p><a class="markdown-video-link markdown-video-link-dtube" data-embed-src="https://emb.d.tube/#!/techcoderx/QmVdEYicJwiTxSk2U9ER1Yc8Rumb1Nek4KynqAYGyQs7ga"><span class="markdown-video-play"></span></a></p>'
122
+
123
+ expect(markdown2Html(input)).toBe(expected)
124
+ })
125
+
102
126
  it('9- Should handle witnesses links', () => {
103
127
  const input = {
104
128
  author: 'foo39',
@@ -142,7 +166,7 @@ describe('Markdown2Html', () => {
142
166
  last_update: '2019-05-10T09:15:21',
143
167
  body: '<iframe width="560" height="315" src="https://www.youtube.com/embed/I3f9ixg59no?foo=bar&baz=000" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>'
144
168
  }
145
- const expected = '<iframe src=\"https://www.youtube.com/embed/I3f9ixg59no\" allowfullscreen=\"allowfullscreen\"></iframe>'
169
+ const expected = '<iframe src=\"https://www.youtube.com/embed/I3f9ixg59no\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"></iframe>'
146
170
 
147
171
  expect(markdown2Html(input)).toBe(expected)
148
172
  })
@@ -445,9 +469,9 @@ describe('Markdown2Html', () => {
445
469
  author: 'foo329',
446
470
  permlink: 'bar329',
447
471
  last_update: '2019-05-10T09:15:21',
448
- body: 'https://youtu.be/_-6hJ8sltdI'
472
+ body: 'https://youtu.be/UuyS7YAkECA?t=295s'
449
473
  }
450
- const expected = '<p><a class="markdown-video-link markdown-video-link-youtube" data-embed-src="https://www.youtube.com/embed/_-6hJ8sltdI?autoplay=1" data-youtube="_-6hJ8sltdI"><img class="no-replace video-thumbnail" src="https://images.ecency.com/p/S5Eokt4BcQdk7EHeT1aYjzebg2hC7hkthT45eEGc5AMBA14JMjkkxrUAj3mV5QR9D6zfstr.png?format=match&amp;mode=fit" /><span class="markdown-video-play"></span></a></p>'
474
+ const expected = '<p><a class="markdown-video-link markdown-video-link-youtube" data-embed-src="https://www.youtube.com/embed/UuyS7YAkECA?autoplay=1" data-youtube="UuyS7YAkECA" data-start-time="295"><img class="no-replace video-thumbnail" src="https://images.ecency.com/p/S5Eokt4BcQdk7EHeT1aYjzebg2hC7hkthT45eAMp88bZ44hfAQDm6BtJw2H53aq1Tpn1cu4.png?format=match&amp;mode=fit" /><span class="markdown-video-play"></span></a></p>'
451
475
 
452
476
  expect(markdown2Html(input)).toBe(expected)
453
477
  })
@@ -877,6 +901,18 @@ describe('Markdown2Html', () => {
877
901
 
878
902
  expect(markdown2Html(input, false)).toBe(expected)
879
903
  })
904
+
905
+ it('65- Should handle youtube.com/embed videos', () => {
906
+ const input = {
907
+ author: 'foo329',
908
+ permlink: 'bar329',
909
+ last_update: '2019-05-10T09:15:21',
910
+ body: 'https://www.youtube.com/embed/UuyS7YAkECA?start=295&autoplay=1'
911
+ }
912
+ const expected = '<p><a class="markdown-video-link markdown-video-link-youtube" data-embed-src="https://www.youtube.com/embed/UuyS7YAkECA?autoplay=1" data-youtube="UuyS7YAkECA" data-start-time="295"><img class="no-replace video-thumbnail" src="https://images.ecency.com/p/S5Eokt4BcQdk7EHeT1aYjzebg2hC7hkthT45eAMp88bZ44hfAQDm6BtJw2H53aq1Tpn1cu4.png?format=match&amp;mode=fit" /><span class="markdown-video-play"></span></a></p>'
913
+
914
+ expect(markdown2Html(input)).toBe(expected)
915
+ })
880
916
  })
881
917
 
882
918
  describe("Rumble support", () => {
@@ -28,6 +28,7 @@ import {
28
28
  import { getSerializedInnerHTML } from './get-inner-html.method'
29
29
  import { proxifyImageSrc } from '../proxify-image-src'
30
30
  import { removeChildNodes } from './remove-child-nodes.method'
31
+ import { extractYtStartTime } from '../helper'
31
32
 
32
33
  export function a(el: HTMLElement, forApp: boolean, webp: boolean): void {
33
34
  let href = el.getAttribute('href')
@@ -416,6 +417,12 @@ export function a(el: HTMLElement, forApp: boolean, webp: boolean): void {
416
417
  el.setAttribute('data-embed-src', embedSrc);
417
418
  el.setAttribute('data-youtube', vid);
418
419
 
420
+ //extract start time if available
421
+ const startTime = extractYtStartTime(href);
422
+ if(startTime){
423
+ el.setAttribute('data-start-time', startTime);
424
+ }
425
+
419
426
  const thumbImg = el.ownerDocument.createElement('img')
420
427
  thumbImg.setAttribute('class', 'no-replace video-thumbnail')
421
428
  thumbImg.setAttribute('itemprop', 'thumbnailUrl')
@@ -532,37 +539,46 @@ export function a(el: HTMLElement, forApp: boolean, webp: boolean): void {
532
539
  // If a d.tube video
533
540
  match = href.match(D_TUBE_REGEX)
534
541
  if (match) {
535
- // Only d.tube links contains an image
536
- const imgEls = el.getElementsByTagName('img')
537
542
 
538
- if (imgEls.length === 1) {
543
+ // Only d.tube links contains an image
544
+ const imgEls = el.getElementsByTagName('img')
545
+
546
+ if (imgEls.length === 1 || el.textContent.trim() === href) {
539
547
  const e = D_TUBE_REGEX.exec(href)
540
548
  // e[2] = username, e[3] object id
541
549
  if (e[2] && e[3]) {
542
550
  el.setAttribute('class', 'markdown-video-link markdown-video-link-dtube')
543
551
  el.removeAttribute('href')
552
+
544
553
 
545
- const thumbnail = proxifyImageSrc(imgEls[0].getAttribute('src').replace(/\s+/g, ''), 0, 0, webp ? 'webp' : 'match')
546
554
  const videoHref = `https://emb.d.tube/#!/${e[2]}/${e[3]}`
547
555
 
548
556
  // el.setAttribute('data-video-href', videoHref)
549
557
  el.setAttribute('data-embed-src', videoHref)
550
558
 
551
- const thumbImg = el.ownerDocument.createElement('img')
552
- thumbImg.setAttribute('class', 'no-replace video-thumbnail')
553
- thumbImg.setAttribute('itemprop', 'thumbnailUrl')
554
-
555
- thumbImg.setAttribute('src', thumbnail)
559
+ //process thumb img element
560
+ if (imgEls.length === 1) {
561
+ const thumbnail = proxifyImageSrc(imgEls[0].getAttribute('src').replace(/\s+/g, ''), 0, 0, webp ? 'webp' : 'match')
562
+ const thumbImg = el.ownerDocument.createElement('img')
563
+
564
+ thumbImg.setAttribute('class', 'no-replace video-thumbnail')
565
+ thumbImg.setAttribute('itemprop', 'thumbnailUrl')
566
+
567
+ thumbImg.setAttribute('src', thumbnail)
568
+ el.appendChild(thumbImg)
569
+
570
+ // Remove image.
571
+ el.removeChild(imgEls[0])
572
+ } else {
573
+ el.textContent = '';
574
+ }
556
575
 
557
576
  const play = el.ownerDocument.createElement('span')
558
577
  play.setAttribute('class', 'markdown-video-play')
559
578
 
560
- el.appendChild(thumbImg)
579
+
561
580
  el.appendChild(play)
562
581
 
563
- // Remove image.
564
- el.removeChild(imgEls[0])
565
-
566
582
  return
567
583
  }
568
584
  }
@@ -574,6 +590,7 @@ export function a(el: HTMLElement, forApp: boolean, webp: boolean): void {
574
590
  if (e[2] && e[3]) {
575
591
  el.setAttribute('class', 'markdown-video-link markdown-video-link-dtube')
576
592
  el.removeAttribute('href')
593
+ el.textContent = '';
577
594
 
578
595
  const videoHref = `https://emb.d.tube/#!/${e[2]}/${e[3]}`
579
596
 
@@ -583,6 +600,7 @@ export function a(el: HTMLElement, forApp: boolean, webp: boolean): void {
583
600
  play.setAttribute('class', 'markdown-video-play')
584
601
 
585
602
  el.appendChild(play)
603
+
586
604
 
587
605
  return
588
606
  }
@@ -671,6 +689,12 @@ export function a(el: HTMLElement, forApp: boolean, webp: boolean): void {
671
689
  if (e[1]) {
672
690
  const vid = e[1]
673
691
  el.setAttribute('data-youtube', vid);
692
+
693
+ //extract start time if available
694
+ const startTime = extractYtStartTime(href);
695
+ if(startTime){
696
+ el.setAttribute('data-start-time', startTime);
697
+ }
674
698
  }
675
699
  }
676
700
  el.removeAttribute('href')
@@ -684,3 +708,6 @@ export function a(el: HTMLElement, forApp: boolean, webp: boolean): void {
684
708
  }
685
709
  }
686
710
  }
711
+
712
+
713
+
@@ -18,8 +18,8 @@ export function cleanReply(s: string): string {
18
18
  .filter(item => item.toLowerCase().includes('read this post on travelfeed.io for the best experience') === false)
19
19
  .filter(item => item.toLowerCase().includes('posted via <a href="https://www.dporn.co/"') === false)
20
20
  .filter(item => item.toLowerCase().includes('▶️ [watch on 3speak](https://3speak') === false)
21
- .filter(item => item.toLowerCase().includes('<sup><sub>Posted via [inji.com]') === false)
22
- .filter(item => item.toLowerCase().includes('view this post on [Liketu]') === false)
21
+ .filter(item => item.toLowerCase().includes('<sup><sub>posted via [inji.com]') === false)
22
+ .filter(item => item.toLowerCase().includes('view this post on [liketu]') === false)
23
23
  .join('\n') : '')
24
24
  .replace('Posted via <a href="https://d.buzz" data-link="promote-link">D.Buzz</a>', '')
25
25
  .replace('<div class="pull-right"><a href="/@hive.engage">![](https://i.imgur.com/XsrNmcl.png)</a></div>', '')
@@ -1,6 +1,6 @@
1
1
  import { traverse } from './traverse.method'
2
2
  import { sanitizeHtml } from './sanitize-html.method'
3
- import { DOMParser } from '../consts'
3
+ import { DOMParser, ENTITY_REGEX } from '../consts'
4
4
  import xmldom from 'xmldom'
5
5
 
6
6
  const lolight = require('lolight')
@@ -47,7 +47,7 @@ export function markdownToHTML(input: string, forApp: boolean, webp: boolean): s
47
47
  let output = '';
48
48
 
49
49
  //encrypt entities
50
- const entities = input.match(/&(.*?);/g);
50
+ const entities = input.match(ENTITY_REGEX);
51
51
  const encEntities:string[] = [];
52
52
 
53
53
  try{
@@ -1,4 +1,5 @@
1
1
  import { IMG_REGEX, YOUTUBE_REGEX, WHITE_LIST, DOMParser, POST_REGEX, } from '../consts'
2
+ import { extractYtStartTime } from '../helper'
2
3
  import { proxifyImageSrc } from '../proxify-image-src'
3
4
  import { linkify } from './linkify.method'
4
5
 
@@ -34,7 +35,12 @@ export function text(node: HTMLElement, forApp: boolean, webp: boolean): void {
34
35
  const thumbnail = proxifyImageSrc(`https://img.youtube.com/vi/${vid.split('?')[0]}/hqdefault.jpg`, 0, 0, webp ? 'webp' : 'match')
35
36
  const embedSrc = `https://www.youtube.com/embed/${vid}?autoplay=1`
36
37
 
37
- const attrs = `class="markdown-video-link markdown-video-link-youtube" data-embed-src="${embedSrc}" data-youtube="${vid}"`
38
+ let attrs = `class="markdown-video-link markdown-video-link-youtube" data-embed-src="${embedSrc}" data-youtube="${vid}"`
39
+ //extract start time if available
40
+ const startTime = extractYtStartTime(node.nodeValue);
41
+ if(startTime){
42
+ attrs = attrs.concat(` data-start-time="${startTime}"`);
43
+ }
38
44
 
39
45
  const thumbImg = node.ownerDocument.createElement('img')
40
46
  thumbImg.setAttribute('class', 'no-replace video-thumbnail')
@@ -3,6 +3,8 @@ import { makeEntryCacheKey } from './helper'
3
3
  import { cacheGet, cacheSet } from './cache'
4
4
  import { Entry } from './types'
5
5
  import { cleanReply } from './methods'
6
+ import { ENTITY_REGEX } from './consts'
7
+
6
8
 
7
9
  const { Remarkable } = require('remarkable')
8
10
  const { linkify } = require('remarkable/linkify')
@@ -58,7 +60,7 @@ function postBodySummary(entryBody: string, length?: number, platform:'ios'|'and
58
60
  ]);
59
61
 
60
62
  //encrypt entities
61
- const entities = entryBody.match(/&(.*?);/g);
63
+ const entities = entryBody.match(ENTITY_REGEX);
62
64
  const encEntities:string[] = [];
63
65
  if(entities && platform !== 'web'){
64
66
  entities.forEach((entity)=>{
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "id": 23,
3
3
  "input": "<IMG SRC=/ onerror=\"alert(String.fromCharCode(88,83,83))\"></img> <img src=x onerror=\"&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041\"> <IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;> <IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29> <IMG SRC=\" &#14; javascript:alert('XSS');\">",
4
- "result": "<p>&lt;IMG SRC=/ onerror=\"alert(String.fromCharCode(88,83,83))\"&gt; <img src=\"https://images.ecency.com/p/35.png?format=match&amp;mode=fit\"> <img> <img> <img /></p>"
4
+ "result": "<p>&lt;IMG SRC=/ onerror=\"alert(String.fromCharCode(88,83,83))\"&gt; <img src=\"https://images.ecency.com/p/35.png?format=match&amp;mode=fit\" /> &lt;IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;&gt; <img /> <img /></p>"
5
5
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "id": 27,
3
3
  "input": "<BR SIZE=\"&{alert('XSS')}\"> <LINK REL=\"stylesheet\" HREF=\"javascript:alert('XSS');\"> <STYLE>body{}</STYLE> <META HTTP-EQUIV=\"Link\" Content=\"<http://xss.rocks/xss.css>; REL=stylesheet\"> <IFRAME SRC=\"javascript:alert('XSS');\"></IFRAME> foo <IFRAME SRC=# onmouseover=\"alert(document.cookie)\"></IFRAME> bar <FRAMESET><FRAME SRC=\"javascript:alert('XSS');\"></FRAMESET> baz ",
4
- "result": "<p><br> foo bar baz\n</p>"
4
+ "result": "<p><br /> foo bar baz\n</p>"
5
5
  }