import { Inject , Injectable } from '@angular/core' ; import { HttpClient , HttpEvent } from '@angular/common/http' ; import { Observable } from 'rxjs' ; import { DOCUMENT } from '@angular/common' ; import { CustomClass } from './config' ; export interface UploadResponse { imageUrl : string ; } @ Injectable ( ) export class AngularEditorService { savedSelection : Range | null ; selectedText : string ; uploadUrl : string ; uploadWithCredentials : boolean ; constructor ( private http , @ Inject ( DOCUMENT ) private doc ) { } param command executeCommand ( command ) { const commands = [ 'h1' , 'h2' , 'h3' , 'h4' , 'h5' , 'h6' , 'p' , 'pre' ] ; if ( commands . includes ( command ) ) { this . doc . execCommand ( 'formatBlock' , false , command ) ; return ; } this . doc . execCommand ( command , false , null ) ; } param url createLink ( url ) { if ( ! url . includes ( 'http' ) ) { this . doc . execCommand ( 'createlink' , false , url ) ; } else { const newUrl = '<a href="' + url + '" target="_blank">' + this . selectedText + '</a>' ; this . insertHtml ( newUrl ) ; } } param color param where insertColor ( color , where ) { const restored = this . restoreSelection ( ) ; if ( restored ) { if ( where === 'textColor' ) { this . doc . execCommand ( 'foreColor' , false , color ) ; } else { this . doc . execCommand ( 'hiliteColor' , false , color ) ; } } } param fontName setFontName ( fontName ) { this . doc . execCommand ( 'fontName' , false , fontName ) ; } param fontSize setFontSize ( fontSize ) { this . doc . execCommand ( 'fontSize' , false , fontSize ) ; } param html insertHtml ( html ) { const isHTMLInserted = this . doc . execCommand ( 'insertHTML' , false , html ) ; if ( ! isHTMLInserted ) { throw new Error ( 'Unable to perform the operation' ) ; } } /**
     * save selection when the editor is focussed out
     */ public saveSelection = ( ) : void => { if ( this . doc . getSelection ) { const sel = this . doc . getSelection ( ) ; if ( sel . getRangeAt && sel . rangeCount ) { this . savedSelection = sel . getRangeAt ( 0 ) ; this . selectedText = sel . toString ( ) ; } } else if ( this . doc . getSelection && this . doc . createRange ) { this . savedSelection = document . createRange ( ) ; } else { this . savedSelection = null ; } } ; /**
     * restore selection when the editor is focused in
     *
     * saved selection when the editor is focused out
     */ restoreSelection ( ) { if ( this . savedSelection ) { if ( this . doc . getSelection ) { const sel = this . doc . getSelection ( ) ; sel . removeAllRanges ( ) ; sel . addRange ( this . savedSelection ) ; return true ; } else if ( this . doc . getSelection ) { return true ; } } else { return false ; } } /**
     * setTimeout used for execute 'saveSelection' method in next event loop iteration
     */ public executeInNextQueueIteration ( callbackFn , timeout = 1e2 ) { setTimeout ( callbackFn , timeout ) ; } /** check any selection is made or not */ private checkSelection ( ) { const selectedText = this . savedSelection . toString ( ) ; if ( selectedText . length === 0 ) { throw new Error ( 'No Selection Made' ) ; } return true ; } param file uploadImage ( file ) { const uploadData = new FormData ( ) ; uploadData . append ( 'file' , file , file . name ) ; return this . http . post < UploadResponse > ( this . uploadUrl , uploadData , { reportProgress : true , observe : 'events' , withCredentials : this . uploadWithCredentials , } ) ; } param imageUrl insertImage ( imageUrl ) { this . doc . execCommand ( 'insertImage' , false , imageUrl ) ; } setDefaultParagraphSeparator ( separator ) { this . doc . execCommand ( 'defaultParagraphSeparator' , false , separator ) ; } createCustomClass ( customClass ) { let newTag = this . selectedText ; if ( customClass ) { const tagName = customClass . tag ? customClass . tag : 'span' ; newTag = '<' + tagName + ' class="' + customClass . class + '">' + this . selectedText + '</' + tagName + '>' ; } this . insertHtml ( newTag ) ; } insertVideo ( videoUrl ) { if ( videoUrl . match ( 'www.youtube.com' ) ) { this . insertYouTubeVideoTag ( videoUrl ) ; } if ( videoUrl . match ( 'vimeo.com' ) ) { this . insertVimeoVideoTag ( videoUrl ) ; } } private insertYouTubeVideoTag ( videoUrl ) { const id = videoUrl . split ( 'v=' ) [ 1 ] ; const imageUrl = `https://img.youtube.com/vi/${ id }/0.jpg` ; const thumbnail = `
      <div style='position: relative'>
        <a href='${ videoUrl }' target='_blank'>
          <img src="${ imageUrl }" alt="click to watch"/>
          <img style='position: absolute; left:200px; top:140px'
          src="https://img.icons8.com/color/96/000000/youtube-play.png"/>
        </a>
      </div>` ; this . insertHtml ( thumbnail ) ; } private insertVimeoVideoTag ( videoUrl ) { const sub = this . http . get < any > ( `https://vimeo.com/api/oembed.json?url=${ videoUrl }` ) . subscribe ( data => { const imageUrl = data . thumbnail_url_with_play_button ; const thumbnail = `<div>
        <a href='${ videoUrl }' target='_blank'>
          <img src="${ imageUrl }" alt="${ data . title }"/>
        </a>
      </div>` ; this . insertHtml ( thumbnail ) ; sub . unsubscribe ( ) ; } ) ; } nextNode ( node ) { if ( node . hasChildNodes ( ) ) { return node . firstChild ; } else { while ( node && ! node . nextSibling ) { node = node . parentNode ; } if ( ! node ) { return null ; } return node . nextSibling ; } } getRangeSelectedNodes ( range , includePartiallySelectedContainers ) { let node = range . startContainer ; const endNode = range . endContainer ; let rangeNodes = [ ] ; if ( node === endNode ) { rangeNodes = [ node ] ; } else { while ( node && node !== endNode ) { rangeNodes . push ( node = this . nextNode ( node ) ) ; } node = range . startContainer ; while ( node && node !== range . commonAncestorContainer ) { rangeNodes . unshift ( node ) ; node = node . parentNode ; } } if ( includePartiallySelectedContainers ) { node = range . commonAncestorContainer ; while ( node ) { rangeNodes . push ( node ) ; node = node . parentNode ; } } return rangeNodes ; } getSelectedNodes ( ) { const nodes = [ ] ; if ( this . doc . getSelection ) { const sel = this . doc . getSelection ( ) ; for ( let i = 0 , len = sel . rangeCount ; i < len ; ++ i ) { nodes . push . apply ( nodes , this . getRangeSelectedNodes ( sel . getRangeAt ( i ) , true ) ) ; } } return nodes ; } replaceWithOwnChildren ( el ) { const parent = el . parentNode ; while ( el . hasChildNodes ( ) ) { parent . insertBefore ( el . firstChild , el ) ; } parent . removeChild ( el ) ; } removeSelectedElements ( tagNames ) { const tagNamesArray = tagNames . toLowerCase ( ) . split ( ',' ) ; this . getSelectedNodes ( ) . forEach ( ( node ) => { if ( node . nodeType === 1 && tagNamesArray . indexOf ( node . tagName . toLowerCase ( ) ) > - 1 ) { this . replaceWithOwnChildren ( node ) ; } } ) ; } }