import { AfterViewInit , Attribute , ChangeDetectorRef , Component , ElementRef , EventEmitter , forwardRef , HostBinding , HostListener , Inject , Input , OnDestroy , OnInit , Output , Renderer2 , SecurityContext , ViewChild } from '@angular/core' ; import { ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms' ; import { AngularEditorConfig , angularEditorConfig } from './config' ; import { AngularEditorToolbarComponent } from './angular-editor-toolbar.component' ; import { AngularEditorService } from './angular-editor.service' ; import { DOCUMENT } from '@angular/common' ; import { DomSanitizer } from '@angular/platform-browser' ; import { isDefined } from './utils' ; @ Component ( { selector : 'angular-editor' , templateUrl : './angular-editor.component.html' , styleUrls : [ './angular-editor.component.scss' ] , providers : [ { provide : NG_VALUE_ACCESSOR , useExisting : forwardRef ( ( ) => AngularEditorComponent ) , multi : true } , AngularEditorService ] } ) export class AngularEditorComponent implements OnInit , ControlValueAccessor , AfterViewInit , OnDestroy { private onChange : ( value : string ) => void ; private onTouched : ( ) => void ; modeVisual = true ; showPlaceholder = false ; disabled = false ; focused = false ; touched = false ; changed = false ; focusInstance : any ; blurInstance : any ; @ Input ( ) id = '' ; @ Input ( ) config : AngularEditorConfig = angularEditorConfig ; @ Input ( ) placeholder = '' ; @ Input ( ) tabIndex : number | null ; @ Output ( ) html ; @ ViewChild ( 'editor' , { static : true } ) textArea : ElementRef ; @ ViewChild ( 'editorWrapper' , { static : true } ) editorWrapper : ElementRef ; @ ViewChild ( 'editorToolbar' ) editorToolbar : AngularEditorToolbarComponent ; @ Output ( ) viewMode = new EventEmitter < boolean > ( ) ; /** emits `blur` event when focused out from the textarea */ @ Output ( 'blur' ) blurEvent : EventEmitter < FocusEvent > = new EventEmitter < FocusEvent > ( ) ; /** emits `focus` event when focused in to the textarea */ @ Output ( 'focus' ) focusEvent : EventEmitter < FocusEvent > = new EventEmitter < FocusEvent > ( ) ; @ HostBinding ( 'attr.tabindex' ) tabindex = - 1 ; @ HostListener ( 'focus' ) onFocus ( ) { this . focus ( ) ; } constructor ( private r , private editorService , @ Inject ( DOCUMENT ) private doc , private sanitizer , private cdRef , @ Attribute ( 'tabindex' ) defaultTabIndex , @ Attribute ( 'autofocus' ) private autoFocus ) { const parsedTabIndex = Number ( defaultTabIndex ) ; this . tabIndex = ( parsedTabIndex || parsedTabIndex === 0 ) ? parsedTabIndex : null ; } ngOnInit ( ) { this . config . toolbarPosition = this . config . toolbarPosition ? this . config . toolbarPosition : angularEditorConfig . toolbarPosition ; } ngAfterViewInit ( ) { if ( isDefined ( this . autoFocus ) ) { this . focus ( ) ; } } onPaste ( event ) { if ( this . config . rawPaste ) { event . preventDefault ( ) ; const text = event . clipboardData . getData ( 'text/plain' ) ; document . execCommand ( 'insertHTML' , false , text ) ; return text ; } } param command executeCommand ( command ) { this . focus ( ) ; if ( command === 'focus' ) { return ; } if ( command === 'toggleEditorMode' ) { this . toggleEditorMode ( this . modeVisual ) ; } else if ( command !== '' ) { if ( command === 'clear' ) { this . editorService . removeSelectedElements ( this . getCustomTags ( ) ) ; this . onContentChange ( this . textArea . nativeElement ) ; } else if ( command === 'default' ) { this . editorService . removeSelectedElements ( 'h1,h2,h3,h4,h5,h6,p,pre' ) ; this . onContentChange ( this . textArea . nativeElement ) ; } else { this . editorService . executeCommand ( command ) ; } this . exec ( ) ; } } /**
     * focus event
     */ onTextAreaFocus ( event ) { if ( this . focused ) { event . stopPropagation ( ) ; return ; } this . focused = true ; this . focusEvent . emit ( event ) ; if ( ! this . touched || ! this . changed ) { this . editorService . executeInNextQueueIteration ( ( ) => { this . configure ( ) ; this . touched = true ; } ) ; } } description public onTextAreaMouseOut ( event ) { this . editorService . saveSelection ( ) ; } /**
     * blur event
     */ onTextAreaBlur ( event ) { /**
         * save selection if focussed out
         */ this . editorService . executeInNextQueueIteration ( this . editorService . saveSelection ) ; if ( typeof this . onTouched === 'function' ) { this . onTouched ( ) ; } if ( event . relatedTarget !== null ) { const parent = ( event . relatedTarget as HTMLElement ) . parentElement ; if ( ! parent . classList . contains ( 'angular-editor-toolbar-set' ) && ! parent . classList . contains ( 'ae-picker' ) ) { this . blurEvent . emit ( event ) ; this . focused = false ; } } } /**
     *  focus the text area when the editor is focused
     */ focus ( ) { if ( this . modeVisual ) { this . textArea . nativeElement . focus ( ) ; } else { const sourceText = this . doc . getElementById ( 'sourceText' + this . id ) ; sourceText . focus ( ) ; this . focused = true ; } } param element onContentChange ( element ) { let html = '' ; if ( this . modeVisual ) { html = element . innerHTML ; } else { html = element . innerText ; } if ( ( ! html || html === '<br>' ) ) { html = '' ; } if ( typeof this . onChange === 'function' ) { this . onChange ( this . config . sanitize || this . config . sanitize === undefined ? this . sanitizer . sanitize ( SecurityContext . HTML , html ) : html ) ; if ( ( ! html ) !== this . showPlaceholder ) { this . togglePlaceholder ( this . showPlaceholder ) ; } } this . changed = true ; } param fn registerOnChange ( fn ) { this . onChange = e => ( e === '<br>' ? fn ( '' ) : fn ( e ) ) ; } param fn registerOnTouched ( fn ) { this . onTouched = fn ; } param value writeValue ( value ) { if ( ( ! value || value === '<br>' || value === '' ) !== this . showPlaceholder ) { this . togglePlaceholder ( this . showPlaceholder ) ; } if ( value === undefined || value === '' || value === '<br>' ) { value = null ; } this . refreshView ( value ) ; } param value refreshView ( value ) { const normalizedValue = value === null ? '' : value ; this . r . setProperty ( this . textArea . nativeElement , 'innerHTML' , normalizedValue ) ; return ; } param value togglePlaceholder ( value ) { if ( ! value ) { this . r . addClass ( this . editorWrapper . nativeElement , 'show-placeholder' ) ; this . showPlaceholder = true ; } else { this . r . removeClass ( this . editorWrapper . nativeElement , 'show-placeholder' ) ; this . showPlaceholder = false ; } } param isDisabled setDisabledState ( isDisabled ) { const div = this . textArea . nativeElement ; const action = isDisabled ? 'addClass' : 'removeClass' ; this . r [ action ] ( div , 'disabled' ) ; this . disabled = isDisabled ; } param bToSource toggleEditorMode ( bToSource ) { let oContent ; const editableElement = this . textArea . nativeElement ; if ( bToSource ) { oContent = this . r . createText ( editableElement . innerHTML ) ; this . r . setProperty ( editableElement , 'innerHTML' , '' ) ; this . r . setProperty ( editableElement , 'contentEditable' , false ) ; const oPre = this . r . createElement ( 'pre' ) ; this . r . setStyle ( oPre , 'margin' , '0' ) ; this . r . setStyle ( oPre , 'outline' , 'none' ) ; const oCode = this . r . createElement ( 'code' ) ; this . r . setProperty ( oCode , 'id' , 'sourceText' + this . id ) ; this . r . setStyle ( oCode , 'display' , 'block' ) ; this . r . setStyle ( oCode , 'white-space' , 'pre-wrap' ) ; this . r . setStyle ( oCode , 'word-break' , 'keep-all' ) ; this . r . setStyle ( oCode , 'outline' , 'none' ) ; this . r . setStyle ( oCode , 'margin' , '0' ) ; this . r . setStyle ( oCode , 'background-color' , '#fff5b9' ) ; this . r . setProperty ( oCode , 'contentEditable' , true ) ; this . r . appendChild ( oCode , oContent ) ; this . focusInstance = this . r . listen ( oCode , 'focus' , ( event ) => this . onTextAreaFocus ( event ) ) ; this . blurInstance = this . r . listen ( oCode , 'blur' , ( event ) => this . onTextAreaBlur ( event ) ) ; this . r . appendChild ( oPre , oCode ) ; this . r . appendChild ( editableElement , oPre ) ; this . doc . execCommand ( 'defaultParagraphSeparator' , false , 'div' ) ; this . modeVisual = false ; this . viewMode . emit ( false ) ; oCode . focus ( ) ; } else { if ( this . doc . querySelectorAll ) { this . r . setProperty ( editableElement , 'innerHTML' , editableElement . innerText ) ; } else { oContent = this . doc . createRange ( ) ; oContent . selectNodeContents ( editableElement . firstChild ) ; this . r . setProperty ( editableElement , 'innerHTML' , oContent . toString ( ) ) ; } this . r . setProperty ( editableElement , 'contentEditable' , true ) ; this . modeVisual = true ; this . viewMode . emit ( true ) ; this . onContentChange ( editableElement ) ; editableElement . focus ( ) ; } this . editorToolbar . setEditorMode ( ! this . modeVisual ) ; } /**
     * toggles editor buttons when cursor moved or positioning
     *
     * Send a node array from the contentEditable of the editor
     */ exec ( ) { this . editorToolbar . triggerButtons ( ) ; let userSelection ; if ( this . doc . getSelection ) { userSelection = this . doc . getSelection ( ) ; this . editorService . executeInNextQueueIteration ( this . editorService . saveSelection ) ; } let a = userSelection . focusNode ; const els = [ ] ; while ( a && a . id !== 'editor' ) { els . unshift ( a ) ; a = a . parentNode ; } this . editorToolbar . triggerBlocks ( els ) ; } private configure ( ) { this . editorService . uploadUrl = this . config . uploadUrl ; this . editorService . uploadWithCredentials = this . config . uploadWithCredentials ; if ( this . config . defaultParagraphSeparator ) { this . editorService . setDefaultParagraphSeparator ( this . config . defaultParagraphSeparator ) ; } if ( this . config . defaultFontName ) { this . editorService . setFontName ( this . config . defaultFontName ) ; } if ( this . config . defaultFontSize ) { this . editorService . setFontSize ( this . config . defaultFontSize ) ; } } getFonts ( ) { const fonts = this . config . fonts ? this . config . fonts : angularEditorConfig . fonts ; return fonts . map ( x => { return { label : x . name , value : x . name } ; } ) ; } getCustomTags ( ) { const tags = [ 'span' ] ; this . config . customClasses . forEach ( x => { if ( x . tag !== undefined ) { if ( ! tags . includes ( x . tag ) ) { tags . push ( x . tag ) ; } } } ) ; return tags . join ( ',' ) ; } ngOnDestroy ( ) { if ( this . blurInstance ) { this . blurInstance ( ) ; } if ( this . focusInstance ) { this . focusInstance ( ) ; } } filterStyles ( html ) { html = html . replace ( 'position: fixed;' , '' ) ; return html ; } }