@ecency/render-helper 2.2.1 → 2.2.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecency/render-helper",
3
- "version": "2.2.1",
3
+ "version": "2.2.5",
4
4
  "description": "Markdown+Html Render helper",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -15,7 +15,8 @@ export const ALLOWED_ATTRIBUTES: XSSWhiteList = {
15
15
  'data-video-href',
16
16
  'data-proposal',
17
17
  'class',
18
- 'title'
18
+ 'title',
19
+ 'data-id'
19
20
  ],
20
21
  'img': ['src', 'alt', 'class'],
21
22
  'span': ['class'],
@@ -12,7 +12,7 @@ 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
14
  export const YOUTUBE_REGEX = /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([^& \n<]+)(?:[^ \n<]+)?/g
15
- export const YOUTUBE_EMBED_REGEX = /^(https?:)?\/\/www.youtube.com\/embed\/.*/i
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]+)/
18
18
  export const BITCHUTE_REGEX = /^(?:https?:\/\/)?(?:www\.)?bitchute.com\/(?:video|embed)\/([a-z0-9]+)/i
@@ -38,3 +38,4 @@ export const TWITCH_EMBED_REGEX = /^(https?:)?\/\/player.twitch.tv\/.*/i
38
38
  export const BRAND_NEW_TUBE_REGEX = /^https:\/\/brandnewtube\.com\/embed\/[a-z0-9]+$/i
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
+ export const AUREAL_EMBED_REGEX = /^(https?:\/\/)?(www\.)?(?:aureal-embed)\.web\.app\/([0-9]+)/i
@@ -22,5 +22,7 @@ export const WHITE_LIST = [
22
22
  'naturalmedicine.io',
23
23
  'bilpcoin.com',
24
24
  'stemgeeks.net',
25
- 'hiveblockexplorer.com'
25
+ 'hiveblockexplorer.com',
26
+ 'proofofbrain.blog',
27
+ 'liketu.com',
26
28
  ]
@@ -428,14 +428,14 @@ describe('Markdown2Html', () => {
428
428
  expect(markdown2Html(input)).toBe(expected)
429
429
  })
430
430
 
431
- it('28- Should handle peakd post links', () => {
431
+ it('28 - Should handle peakd post links', () => {
432
432
  const input = {
433
433
  author: 'foo3343',
434
434
  permlink: 'bar3243',
435
435
  last_update: '2019-05-10T09:15:21',
436
436
  body: 'https://peakd.com/@demo/tests'
437
437
  }
438
- const expected = '<p><a class="markdown-post-link" data-tag="post" data-author="demo" data-permlink="tests">/@demo/tests</a></p>'
438
+ const expected = '<p><a class=\"markdown-post-link\" data-tag=\"post\" data-author=\"demo\" data-permlink=\"tests\">@demo/tests</a></p>'
439
439
 
440
440
  expect(markdown2Html(input)).toBe(expected)
441
441
  })
@@ -663,7 +663,7 @@ describe('Markdown2Html', () => {
663
663
  last_update: '2019-05-10T09:15:21',
664
664
  body: '[click here](https://peakd.com/@praetoria-cartel/wallet) direct link https://peakd.com/@praetoria-cartel/posts'
665
665
  }
666
- const expected = '<p><a href="https://ecency.com/@praetoria-cartel/wallet" class="markdown-profile-link">click here</a> direct link <a href="https://ecency.com/@praetoria-cartel/posts" class="markdown-profile-link">/@praetoria-cartel/posts</a></p>'
666
+ const expected = '<p><a href=\"https://ecency.com/@praetoria-cartel/wallet\" class=\"markdown-profile-link\">click here</a> direct link <a href=\"https://ecency.com/@praetoria-cartel/posts\" class=\"markdown-profile-link\">@praetoria-cartel/posts</a></p>'
667
667
 
668
668
  expect(markdown2Html(input)).toBe(expected)
669
669
  })
@@ -675,12 +675,12 @@ describe('Markdown2Html', () => {
675
675
  last_update: '2019-05-10T09:15:21',
676
676
  body: 'for history refer to this fine post /@offgridlife/proofofbrain-golden-rule-do-to-others-what-you-want-them-to-do-to-you and while you are in the community'
677
677
  }
678
- const expected = '<p><span>for history refer to this fine post <a class=\"markdown-post-link\" data-author=\"offgridlife\" data-tag=\"post\" data-permlink=\"proofofbrain-golden-rule-do-to-others-what-you-want-them-to-do-to-you\">/@offgridlife/proofofbrain-golden-rule-do-to-others-what-you-want-them-to-do-to-you</a> and while you are in the community</span></p>'
678
+ const expected = '<p><span>for history refer to this fine post <a class=\"markdown-post-link\" data-author=\"offgridlife\" data-tag=\"post\" data-permlink=\"proofofbrain-golden-rule-do-to-others-what-you-want-them-to-do-to-you\">@offgridlife/proofofbrain-golden-rule-do-to-others-what-you-want-them-to-do-to-you</a> and while you are in the community</span></p>'
679
679
 
680
680
  expect(markdown2Html(input)).toBe(expected)
681
681
  })
682
682
 
683
- it('47 - Should handle Bitchute links', () => {
683
+ it('48 - Should handle Bitchute links', () => {
684
684
  const input = {
685
685
  author: 'foo347',
686
686
  permlink: 'bar347',
@@ -805,6 +805,66 @@ describe('Markdown2Html', () => {
805
805
  const expected = '<p><strong>It\'s a Secret, But is it good to have secrets?</strong><br /><a data-tag=\"hive-123046\" data-author=\"ecotrain\" data-permlink=\"ecotrain-question-of-the-week-season-5-1tie-up-post-it-s-a-secret-but-is-it-good-to-have-secrets\" class=\"markdown-post-link\">/@ecotrain/ecotrain-question-of-the-week-season-5-1tie-up-post-it-s-a-secret-but-is-it-good-to-have-secrets</a></p>'
806
806
  expect(markdown2Html(input)).toBe(expected)
807
807
  })
808
+
809
+ it('59 - Should handle Aureal iframe', () => {
810
+ const input = {
811
+ author: 'foo359',
812
+ permlink: 'bar359',
813
+ last_update: '2021-10-23T09:15:21',
814
+ body: 'this is link <iframe loading="lazy" src="https://aureal-embed.web.app/535939" width="100%" height="200" frameborder="0" data-rocket-lazyload="fitvidscompatible" class="lazyloaded" data-ll-status="loaded"></iframe>'
815
+ }
816
+ const expected = '<p>this is link <iframe src=\"https://aureal-embed.web.app/535939\" frameborder=\"0\" class=\"lazyloaded\"></iframe></p>'
817
+
818
+ expect(markdown2Html(input)).toBe(expected)
819
+ })
820
+
821
+ it('60 - Should username with permlink', () => {
822
+ const input = {
823
+ author: 'foo360',
824
+ permlink: 'bar360',
825
+ last_update: '2021-10-23T09:15:21',
826
+ body: 'this is link @demo/test for internal'
827
+ }
828
+ const expected = '<p><span>this is link <a class=\"markdown-post-link\" data-author=\"demo\" data-tag=\"post\" data-permlink=\"test\">@demo/test</a> for internal</span></p>'
829
+
830
+ expect(markdown2Html(input)).toBe(expected)
831
+ })
832
+
833
+ it('61 - Should username with permlink with slash', () => {
834
+ const input = {
835
+ author: 'foo361',
836
+ permlink: 'bar361',
837
+ last_update: '2021-10-23T09:15:21',
838
+ body: 'this is link /@demo/test for internal'
839
+ }
840
+ const expected = '<p><span>this is link <a class=\"markdown-post-link\" data-author=\"demo\" data-tag=\"post\" data-permlink=\"test\">@demo/test</a> for internal</span></p>'
841
+
842
+ expect(markdown2Html(input)).toBe(expected)
843
+ })
844
+
845
+ it('62 - Should username with permlink new line', () => {
846
+ const input = {
847
+ author: 'foo362',
848
+ permlink: 'bar362',
849
+ last_update: '2021-10-23T09:15:21',
850
+ body: '@demo/test for internal'
851
+ }
852
+ const expected = '<p><span> <a class=\"markdown-post-link\" data-author=\"demo\" data-tag=\"post\" data-permlink=\"test\">@demo/test</a> for internal</span></p>'
853
+
854
+ expect(markdown2Html(input)).toBe(expected)
855
+ })
856
+
857
+ it('63 - Should username with permlink with slash new line', () => {
858
+ const input = {
859
+ author: 'foo363',
860
+ permlink: 'bar363',
861
+ last_update: '2021-10-23T09:15:21',
862
+ body: '/@demo/test for internal'
863
+ }
864
+ const expected = '<p><span> <a class=\"markdown-post-link\" data-author=\"demo\" data-tag=\"post\" data-permlink=\"test\">@demo/test</a> for internal</span></p>'
865
+
866
+ expect(markdown2Html(input)).toBe(expected)
867
+ })
808
868
  })
809
869
 
810
870
  describe("Rumble support", () => {
@@ -832,7 +892,7 @@ describe('Markdown2Html', () => {
832
892
  expect(markdown2Html(input)).toBe(expected)
833
893
  })
834
894
 
835
- // The following canot be done: Convert URLs to the video page like this one
895
+ // The following cannot be done: Convert URLs to the video page like this one
836
896
  // (https://rumble.com/vkhkzl-helping-my-girls-to-cool-down-in-the-heat.html)
837
897
  // to its corresponding embedded URL (https://rumble.com/embed/vhveub/?pub=4).
838
898
  // The relationship seems to be governed by a table.
@@ -119,16 +119,18 @@ export function a(el: HTMLElement, forApp: boolean, webp: boolean): void {
119
119
  if (mentionMatch && WHITE_LIST.includes(mentionMatch[1].replace(/www./,'')) && mentionMatch.length === 3) {
120
120
  el.setAttribute('class', 'markdown-author-link')
121
121
  const author = mentionMatch[2].replace('@', '').toLowerCase()
122
- if (el.textContent === href) {
123
- el.textContent = `@${author}`
124
- }
125
- if (forApp) {
126
- el.removeAttribute('href')
127
-
128
- el.setAttribute('data-author', author)
129
- } else {
130
- const h = `/@${author}`
131
- el.setAttribute('href', h)
122
+ if (author.indexOf('/')===-1) {
123
+ if (el.textContent === href) {
124
+ el.textContent = `@${author}`
125
+ }
126
+ if (forApp) {
127
+ el.removeAttribute('href')
128
+
129
+ el.setAttribute('data-author', author)
130
+ } else {
131
+ const h = `/@${author}`
132
+ el.setAttribute('href', h)
133
+ }
132
134
  }
133
135
  return
134
136
  }
@@ -138,19 +140,19 @@ export function a(el: HTMLElement, forApp: boolean, webp: boolean): void {
138
140
  if (
139
141
  (tpostMatch && WHITE_LIST.includes(tpostMatch[1].substring(1))) || (tpostMatch && tpostMatch.length === 4 && tpostMatch[1].indexOf('/') !== 0)
140
142
  ) {
141
- if (['wallet', 'feed', 'followers', 'following', 'points', 'communities', 'posts', 'blog', 'comments', 'replies', 'settings'].includes(tpostMatch[3])) {
143
+ if (['wallet', 'feed', 'followers', 'following', 'points', 'communities', 'posts', 'blog', 'comments', 'replies', 'settings', 'engine'].includes(tpostMatch[3])) {
142
144
  el.setAttribute('class', 'markdown-profile-link')
143
145
  const author = tpostMatch[2].replace('@', '').toLowerCase()
144
146
  const section = tpostMatch[3]
145
147
 
146
148
  if (el.textContent === href) {
147
- el.textContent = `/@${author}/${section}`
149
+ el.textContent = `@${author}/${section}`
148
150
  }
149
151
  if (forApp) {
150
152
  const ha = `https://ecency.com/@${author}/${section}`
151
153
  el.setAttribute('href', ha)
152
154
  } else {
153
- const h = `/@${author}/${section}`
155
+ const h = `@${author}/${section}`
154
156
  el.setAttribute('href', h)
155
157
  }
156
158
  return
@@ -165,7 +167,7 @@ export function a(el: HTMLElement, forApp: boolean, webp: boolean): void {
165
167
  const author = tpostMatch[2].replace('@', '')
166
168
  const permlink = tpostMatch[3]
167
169
  if (el.textContent === href) {
168
- el.textContent = `/@${author}/${permlink}`
170
+ el.textContent = `@${author}/${permlink}`
169
171
  }
170
172
  if (forApp) {
171
173
  el.removeAttribute('href')
@@ -186,16 +188,18 @@ export function a(el: HTMLElement, forApp: boolean, webp: boolean): void {
186
188
  if (imentionMatch) {
187
189
  el.setAttribute('class', 'markdown-author-link')
188
190
  const author = imentionMatch[0].replace('/@', '').toLowerCase()
189
- if (el.textContent === href) {
190
- el.textContent = `@${author}`
191
- }
192
- if (forApp) {
193
- el.removeAttribute('href')
191
+ if (author.indexOf('/')===-1) {
192
+ if (el.textContent === href) {
193
+ el.textContent = `@${author}`
194
+ }
195
+ if (forApp) {
196
+ el.removeAttribute('href')
194
197
 
195
- el.setAttribute('data-author', author)
196
- } else {
197
- const h = `/@${author}`
198
- el.setAttribute('href', h)
198
+ el.setAttribute('data-author', author)
199
+ } else {
200
+ const h = `/@${author}`
201
+ el.setAttribute('href', h)
202
+ }
199
203
  }
200
204
  return
201
205
  }
@@ -205,7 +209,7 @@ export function a(el: HTMLElement, forApp: boolean, webp: boolean): void {
205
209
  if (
206
210
  (cpostMatch && cpostMatch.length === 3 && cpostMatch[1].indexOf('@') === 0)
207
211
  ) {
208
- if (['wallet', 'feed', 'followers', 'following', 'points', 'communities', 'posts', 'blog', 'comments', 'replies', 'settings'].includes(cpostMatch[2])) {
212
+ if (['wallet', 'feed', 'followers', 'following', 'points', 'communities', 'posts', 'blog', 'comments', 'replies', 'settings', 'engine'].includes(cpostMatch[2])) {
209
213
  el.setAttribute('class', 'markdown-profile-link')
210
214
  const author = cpostMatch[1].replace('@', '').toLowerCase()
211
215
  const section = cpostMatch[2]
@@ -662,7 +666,12 @@ export function a(el: HTMLElement, forApp: boolean, webp: boolean): void {
662
666
  el.setAttribute('data-href', href)
663
667
  el.removeAttribute('href')
664
668
  } else {
665
- el.setAttribute('target', '_blank')
666
- el.setAttribute('rel', 'noopener')
669
+ const externalRegex =/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
670
+ if(externalRegex.test(href)) {
671
+ el.setAttribute('target', '_blank');
672
+ el.setAttribute('rel', 'noopener');
673
+ } else {
674
+ el.setAttribute('class', 'markdown-internal-link')
675
+ }
667
676
  }
668
677
  }
@@ -1,23 +1,25 @@
1
1
  export function cleanReply(s: string): string {
2
2
  return (s ? s.split('\n')
3
- .filter(item => item.includes('Posted using [Partiko') === false)
4
- .filter(item => item.includes('Posted using [Dapplr') === false)
5
- .filter(item => item.includes('Posted Using [LeoFinance') === false)
6
- .filter(item => item.includes('Posted via [neoxian') === false)
7
- .filter(item => item.includes('Posted with [STEMGeeks') === false)
8
- .filter(item => item.includes('Posted using [Bilpcoin') === false)
9
- .filter(item => item.includes('<center><sub>[Posted Using Aeneas.Blog') === false)
10
- .filter(item => item.includes('<center><sub>Posted via [proofofbrain.io') === false)
11
- .filter(item => item.includes('<center>Posted on [HypnoChain') === false)
12
- .filter(item => item.includes('<center><sub>Posted via [weedcash.network') === false)
13
- .filter(item => item.includes('<center>Posted on [NaturalMedicine.io') === false)
14
- .filter(item => item.includes('<center><sub>Posted via [MusicForLife.io') === false)
15
- .filter(item => item.includes('If the truvvl embed is unsupported by your current frontend, click this link to view this story') === false)
16
- .filter(item => item.includes('<center><em>Posted from Truvvl') === false)
17
- .filter(item => item.includes('View this post <a href="https://travelfeed.io/') === false)
18
- .filter(item => item.includes('Read this post on TravelFeed.io for the best experience') === false)
19
- .filter(item => item.includes('Posted via <a href="https://www.dporn.co/"') === false)
20
- .filter(item => item.includes('▶️ [Watch on 3Speak](https://3speak') === false)
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 with [stemgeeks') === false)
8
+ .filter(item => item.toLowerCase().includes('posted using [bilpcoin') === false)
9
+ .filter(item => item.toLowerCase().includes('<center><sub>[posted using aeneas.blog') === false)
10
+ .filter(item => item.toLowerCase().includes('<center><sub>posted via [proofofbrain.io') === false)
11
+ .filter(item => item.toLowerCase().includes('<center>posted on [hypnochain') === false)
12
+ .filter(item => item.toLowerCase().includes('<center><sub>posted via [weedcash.network') === false)
13
+ .filter(item => item.toLowerCase().includes('<center>posted on [naturalmedicine.io') === false)
14
+ .filter(item => item.toLowerCase().includes('<center><sub>posted via [musicforlife.io') === false)
15
+ .filter(item => item.toLowerCase().includes('if the truvvl embed is unsupported by your current frontend, click this link to view this story') === false)
16
+ .filter(item => item.toLowerCase().includes('<center><em>posted from truvvl') === false)
17
+ .filter(item => item.toLowerCase().includes('view this post <a href="https://travelfeed.io/') === false)
18
+ .filter(item => item.toLowerCase().includes('read this post on travelfeed.io for the best experience') === false)
19
+ .filter(item => item.toLowerCase().includes('posted via <a href="https://www.dporn.co/"') === false)
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
23
  .join('\n') : '')
22
24
  .replace('Posted via <a href="https://d.buzz" data-link="promote-link">D.Buzz</a>', '')
23
25
  .replace('<div class="pull-right"><a href="/@hive.engage">![](https://i.imgur.com/XsrNmcl.png)</a></div>', '')
@@ -1,4 +1,4 @@
1
- import { ARCH_REGEX, DAPPLR_REGEX, LBRY_REGEX, TRUVVL_REGEX, ODYSEE_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 } from '../consts'
1
+ import { ARCH_REGEX, DAPPLR_REGEX, LBRY_REGEX, TRUVVL_REGEX, ODYSEE_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
2
 
3
3
  export function iframe(el: HTMLElement): void {
4
4
  const src = el.getAttribute('src')
@@ -145,6 +145,13 @@ export function iframe(el: HTMLElement): void {
145
145
  return;
146
146
  }
147
147
 
148
+ // Aureal
149
+ if (src.match(AUREAL_EMBED_REGEX)) {
150
+ el.setAttribute('src', src)
151
+ el.setAttribute('frameborder', '0')
152
+ return;
153
+ }
154
+
148
155
  const replaceNode = el.ownerDocument.createElement('div')
149
156
  replaceNode.setAttribute('class', 'unsupported-iframe')
150
157
  replaceNode.textContent = `(Unsupported ${src})`
@@ -16,22 +16,26 @@ export function linkify(content: string, forApp: boolean, webp: boolean): string
16
16
 
17
17
  // User mentions
18
18
  content = content.replace(
19
- /(^|[^a-zA-Z0-9_!#$%&*@@/]|(^|[^a-zA-Z0-9_+~.-/]))[@@]([a-z][-.a-z\d]+[a-z\d])/gi,
19
+ /(^|[^a-zA-Z0-9_!#$%&*@@/]|(^|[^a-zA-Z0-9_+~.-/]))[@@]([a-z][-.a-z\d^/]+[a-z\d])/gi,
20
20
  (match, preceeding1, preceeding2, user) => {
21
21
  const userLower = user.toLowerCase()
22
22
  const preceedings = (preceeding1 || '') + (preceeding2 || '')
23
-
24
- const attrs = forApp ? `data-author="${userLower}"` : `href="/@${userLower}"`
25
- return `${preceedings}<a class="markdown-author-link" ${attrs}>@${user}</a>`
23
+ if (userLower.indexOf('/')===-1) {
24
+ const attrs = forApp ? `data-author="${userLower}"` : `href="/@${userLower}"`
25
+ return `${preceedings}<a class="markdown-author-link" ${attrs}>@${user}</a>`
26
+ } else {
27
+ return match
28
+ }
26
29
  }
27
30
  )
28
31
 
29
32
  // internal links
30
33
  content = content.replace(
31
- /(\s\/@[\w.\d-]+)\/(\S+)/gi, (match, u, p) => {
32
- const uu = u.trim().toLowerCase().replace('/@','');
33
- const attrs = forApp ? `data-author="${uu}" data-tag="post" data-permlink="${p.trim()}"` : `href="/post/@${uu}/${p.trim()}"`
34
- return ` <a class="markdown-post-link" ${attrs}>/@${uu}/${p.trim()}</a>`
34
+ /((^|\s)(\/|)@[\w.\d-]+)\/(\S+)/gi, (match, u, p1, p2, p3) => {
35
+ const uu = u.trim().toLowerCase().replace('/@','').replace('@','');
36
+ const perm = p3;
37
+ const attrs = forApp ? `data-author="${uu}" data-tag="post" data-permlink="${perm}"` : `href="/post/@${uu}/${perm}"`
38
+ return ` <a class="markdown-post-link" ${attrs}>@${uu}/${perm}</a>`
35
39
  }
36
40
  )
37
41