import { Directive , ElementRef , OnInit , Input , Output , EventEmitter } from '@angular/core' ; import { ThyMarkdownParserService } from './thy-markdown-parser.service' ; import { $ , liteMarked , mermaid , katex } from 'ngx-tethys/types' ; import { coerceBooleanProperty } from 'ngx-tethys/util' ; @ Directive ( { selector : '[thyMarkdownParser]' } ) export class ThyMarkdownParserDirective implements OnInit { public value : string ; private bypassSecurityTrustHtml = false ; private liteMarkedOptions : any = { gfm : true , tables : true , breaks : true , pedantic : false , sanitize : false , smartLists : true , smartypants : true , heading : true , link : true , list : true , wtlink : true , wthexcolor : true , wthexcolorRender : { className : 'msg-inline-color' } , wtat : false , wthash : false , wtentity : true , wtentityRender : { className : 'slide-trigger' } , wthashRender : { chlPrefix : '/messages/groups/' } , wtexclamation : true , wtemoji : false , isParagraphDefault : true , isImageDefault : true , isBlockquoteDefault : true , isHrDefault : true , isStrongDefault : true , isEmDefault : true , isCodespanDefault : true , isCodeDefault : true , isDelDefault : true , isHtmlDefault : true , isTextEscape : true , isDef : true , isImgPreview : true } ; @ Input ( ) set thyMarkdownParser ( value ) { if ( value ) { this . value = value ; this . translateHTML ( ) ; } } @ Input ( ) set thyBypassSecurityTrustHtml ( value ) { this . bypassSecurityTrustHtml = coerceBooleanProperty ( value ) ; } constructor ( private elementRef , private thyMarkdownParserService ) { } initGantt ( ) { if ( mermaid ) { mermaid . parseError = function ( err , hash ) { mermaid . error = err ; } ; mermaid . ganttConfig = { numberSectionStyles : 4 , axisFormatter : [ [ '%I:%M' , function ( d ) { return d . getHours ( ) ; } ] , [ 'w. %U' , function ( d ) { return d . getDay ( ) === 1 ; } ] , [ '%a %d' , function ( d ) { return d . getDay ( ) && d . getDate ( ) !== 1 ; } ] , [ '%b %d' , function ( d ) { return d . getDate ( ) !== 1 ; } ] , [ '%m-%y' , function ( d ) { return d . getMonth ( ) ; } ] ] } ; } } initMarked ( ) { const renderer = new liteMarked . Renderer ( ) ; renderer . listitem = function ( text ) { if ( ! /^\[[ x]\]\s/ . test ( text ) ) { return liteMarked . Renderer . prototype . listitem ( text ) ; } const checkbox = $ ( '<input type="checkbox" disabled/>' ) ; if ( /^\[x\]\s/ . test ( text ) ) { checkbox . attr ( 'checked' , true ) ; } return $ ( liteMarked . Renderer . prototype . listitem ( text . substring ( 3 ) ) ) . addClass ( 'task-list-item' ) . prepend ( checkbox ) [ 0 ] . outerHTML ; } ; renderer . codespan = function ( text ) { if ( /^\$.+\$$/ . test ( text ) ) { const raw = /^\$(.+)\$$/ . exec ( text ) [ 1 ] ; const line = raw . replace ( /&lt;/g , '<' ) . replace ( /&gt;/g , '>' ) . replace ( /&amp;/g , '&' ) . replace ( /&quot;/g , '"' ) . replace ( /&#39;/g , "'" ) ; try { return katex . renderToString ( line , { displayMode : false } ) ; } catch ( err ) { return '<code>' + err + '</code>' ; } } return liteMarked . Renderer . prototype . codespan . apply ( this , arguments ) ; } ; renderer . code = function ( code , language , escaped , line_number ) { code = code . trim ( ) ; const firstLine = code . split ( /\n/ ) [ 0 ] . trim ( ) ; if ( language === 'math' ) { let tex = '' ; code . split ( /\n\n/ ) . forEach ( function ( line ) { line = line . trim ( ) ; if ( line . length > 0 ) { try { tex += katex . renderToString ( line , { displayMode : true } ) ; } catch ( err ) { tex += '<pre>' + err + '</pre>' ; } } } ) ; return '<div data-line="' + line_number + '">' + tex + '</div>' ; } else if ( firstLine === 'gantt' || firstLine === 'sequenceDiagram' || firstLine . match ( /^graph (?:TB|BT|RL|LR|TD);?$/ ) ) { if ( firstLine === 'sequenceDiagram' ) { code += '\n' ; } if ( mermaid && mermaid . parse ( code ) ) { return '<div class="mermaid" data-line="' + line_number + '">' + code + '</div>' ; } else { if ( mermaid && mermaid . error ) { return '<pre data-line="' + line_number + '">' + mermaid . error + '</pre>' ; } } } else { return liteMarked . Renderer . prototype . code . apply ( this , arguments ) ; } } ; renderer . html = function ( html ) { const result = liteMarked . Renderer . prototype . html . apply ( this , arguments ) ; const h = $ ( result . bold ( ) ) ; return h . html ( ) ; } ; renderer . paragraph = function ( text ) { const result = liteMarked . Renderer . prototype . paragraph . apply ( this , arguments ) ; const h = $ ( result . bold ( ) ) ; return h . html ( ) ; } ; liteMarked . setOptions ( this . liteMarkedOptions ) ; } initComponent ( ) { this . initGantt ( ) ; this . initMarked ( ) ; } parseMarked ( _value ) { if ( liteMarked && _value ) { return liteMarked ( _value ) ; } else { return _value ; } } parseMermaid ( ) { if ( mermaid ) { mermaid . init ( ) ; } } translateHTML ( ) { this . initComponent ( ) ; let _value = this . thyMarkdownParserService . filterHTML ( this . value ) ; _value = this . parseMarked ( _value ) ; if ( ! this . bypassSecurityTrustHtml ) { _value = this . thyMarkdownParserService . sanitizeHTML ( _value ) ; } setTimeout ( ( ) => { this . parseMermaid ( ) ; } , 100 ) ; this . elementRef . nativeElement . innerHTML = _value ; $ ( this . elementRef . nativeElement ) . find ( 'a' ) . attr ( 'target' , function ( ) { if ( this . host !== location . host ) { return '_blank' ; } else { let outer_path = [ 'shared/' , 'share/' , 'club' , 'videos' , 'blog' , 'plan' , 'tour' , 'mobile' , 'security' , 'uservoice' , 'customers' , 'press' , 'help' , 'guide' , 'feedback' , 'about' , 'contact' , 'privacy' , 'terms' ] . join ( ')|(/' ) ; outer_path = new RegExp ( '^(/' + outer_path + ')' ) ; if ( outer_path . test ( this . pathname ) ) { return '_blank' ; } } } ) ; } ngOnInit ( ) { const emojisRender = this . thyMarkdownParserService . getEmojisRender ( ) ; if ( emojisRender ) { this . liteMarkedOptions . wtemoji = true ; this . liteMarkedOptions . wtemojiRender = emojisRender ; } this . translateHTML ( ) ; } }