Put "start at" at the top of the modal
[oweals/peertube.git] / client / src / app / videos / shared / markdown.service.ts
1 import { Injectable } from '@angular/core'
2
3 import * as MarkdownIt from 'markdown-it'
4
5 @Injectable()
6 export class MarkdownService {
7   static TEXT_RULES = [
8     'linkify',
9     'autolink',
10     'emphasis',
11     'link',
12     'newline',
13     'list'
14   ]
15   static ENHANCED_RULES = MarkdownService.TEXT_RULES.concat([ 'image' ])
16
17   private textMarkdownIt: MarkdownIt.MarkdownIt
18   private enhancedMarkdownIt: MarkdownIt.MarkdownIt
19
20   constructor () {
21     this.textMarkdownIt = this.createMarkdownIt(MarkdownService.TEXT_RULES)
22     this.enhancedMarkdownIt = this.createMarkdownIt(MarkdownService.ENHANCED_RULES)
23   }
24
25   textMarkdownToHTML (markdown: string) {
26     if (!markdown) return ''
27
28     const html = this.textMarkdownIt.render(markdown)
29     return this.avoidTruncatedLinks(html)
30   }
31
32   enhancedMarkdownToHTML (markdown: string) {
33     if (!markdown) return ''
34
35     const html = this.enhancedMarkdownIt.render(markdown)
36     return this.avoidTruncatedLinks(html)
37   }
38
39   private createMarkdownIt (rules: string[]) {
40     const markdownIt = new MarkdownIt('zero', { linkify: true, breaks: true })
41
42     for (let rule of rules) {
43       markdownIt.enable(rule)
44     }
45
46     this.setTargetToLinks(markdownIt)
47
48     return markdownIt
49   }
50
51   private setTargetToLinks (markdownIt: MarkdownIt.MarkdownIt) {
52     // Snippet from markdown-it documentation: https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer
53     const defaultRender = markdownIt.renderer.rules.link_open || function (tokens, idx, options, env, self) {
54       return self.renderToken(tokens, idx, options)
55     }
56
57     markdownIt.renderer.rules.link_open = function (tokens, index, options, env, self) {
58       const token = tokens[index]
59
60       const targetIndex = token.attrIndex('target')
61       if (targetIndex < 0) token.attrPush([ 'target', '_blank' ])
62       else token.attrs[targetIndex][1] = '_blank'
63
64       const relIndex = token.attrIndex('rel')
65       if (relIndex < 0) token.attrPush([ 'rel', 'noopener noreferrer' ])
66       else token.attrs[relIndex][1] = 'noopener noreferrer'
67
68       // pass token to default renderer.
69       return defaultRender(tokens, index, options, env, self)
70     }
71   }
72
73   private avoidTruncatedLinks (html: string) {
74     return html.replace(/<a[^>]+>([^<]+)<\/a>\s*...((<\/p>)|(<\/li>)|(<\/strong>))?$/mi, '$1...')
75       .replace(/\[[^\]]+\]?\(?([^\)]+)$/, '$1')
76   }
77 }