UNPKG

@kolkov/angular-editor

Version:

A simple native WYSIWYG editor for Angular 13+. Rich Text editor component for Angular.

1 lines 94.7 kB
{"version":3,"file":"kolkov-angular-editor.mjs","sources":["../../../projects/angular-editor/src/lib/angular-editor.service.ts","../../../projects/angular-editor/src/lib/config.ts","../../../projects/angular-editor/src/lib/utils.ts","../../../projects/angular-editor/src/lib/ae-toolbar-set/ae-toolbar-set.component.ts","../../../projects/angular-editor/src/lib/ae-toolbar-set/ae-toolbar-set.component.html","../../../projects/angular-editor/src/lib/ae-button/ae-button.component.ts","../../../projects/angular-editor/src/lib/ae-button/ae-button.component.html","../../../projects/angular-editor/src/lib/ae-select/ae-select.component.ts","../../../projects/angular-editor/src/lib/ae-select/ae-select.component.html","../../../projects/angular-editor/src/lib/ae-toolbar/ae-toolbar.component.ts","../../../projects/angular-editor/src/lib/ae-toolbar/ae-toolbar.component.html","../../../projects/angular-editor/src/lib/editor/angular-editor.component.ts","../../../projects/angular-editor/src/lib/editor/angular-editor.component.html","../../../projects/angular-editor/src/lib/angular-editor.module.ts","../../../projects/angular-editor/src/public-api.ts","../../../projects/angular-editor/src/kolkov-angular-editor.ts"],"sourcesContent":["import {Inject, Injectable} from '@angular/core';\nimport {HttpClient, HttpEvent} from '@angular/common/http';\nimport {Observable} from 'rxjs';\nimport {DOCUMENT} from '@angular/common';\nimport {CustomClass} from './config';\n\nexport interface UploadResponse {\n imageUrl: string;\n}\n\n@Injectable()\nexport class AngularEditorService {\n\n savedSelection: Range | null;\n selectedText: string;\n uploadUrl: string;\n uploadWithCredentials: boolean;\n\n constructor(\n private http: HttpClient,\n @Inject(DOCUMENT) private doc: any\n ) { }\n\n /**\n * Executed command from editor header buttons exclude toggleEditorMode\n * @param command string from triggerCommand\n * @param value\n */\n executeCommand(command: string, value?: string) {\n const commands = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'pre'];\n if (commands.includes(command)) {\n this.doc.execCommand('formatBlock', false, command);\n return;\n }\n this.doc.execCommand(command, false, value);\n }\n\n /**\n * Create URL link\n * @param url string from UI prompt\n */\n createLink(url: string) {\n if (!url.includes('http')) {\n this.doc.execCommand('createlink', false, url);\n } else {\n const newUrl = '<a href=\"' + url + '\" target=\"_blank\">' + this.selectedText + '</a>';\n this.insertHtml(newUrl);\n }\n }\n\n /**\n * insert color either font or background\n *\n * @param color color to be inserted\n * @param where where the color has to be inserted either text/background\n */\n insertColor(color: string, where: string): void {\n const restored = this.restoreSelection();\n if (restored) {\n if (where === 'textColor') {\n this.doc.execCommand('foreColor', false, color);\n } else {\n this.doc.execCommand('hiliteColor', false, color);\n }\n }\n }\n\n /**\n * Set font name\n * @param fontName string\n */\n setFontName(fontName: string) {\n this.doc.execCommand('fontName', false, fontName);\n }\n\n /**\n * Set font size\n * @param fontSize string\n */\n setFontSize(fontSize: string) {\n this.doc.execCommand('fontSize', false, fontSize);\n }\n\n /**\n * Create raw HTML\n * @param html HTML string\n */\n insertHtml(html: string): void {\n\n const isHTMLInserted = this.doc.execCommand('insertHTML', false, html);\n\n if (!isHTMLInserted) {\n throw new Error('Unable to perform the operation');\n }\n }\n\n /**\n * save selection when the editor is focussed out\n */\n public saveSelection = (): void => {\n if (this.doc.getSelection) {\n const sel = this.doc.getSelection();\n if (sel.getRangeAt && sel.rangeCount) {\n this.savedSelection = sel.getRangeAt(0);\n this.selectedText = sel.toString();\n }\n } else if (this.doc.getSelection && this.doc.createRange) {\n this.savedSelection = document.createRange();\n } else {\n this.savedSelection = null;\n }\n }\n\n /**\n * restore selection when the editor is focused in\n *\n * saved selection when the editor is focused out\n */\n restoreSelection(): boolean {\n if (this.savedSelection) {\n if (this.doc.getSelection) {\n const sel = this.doc.getSelection();\n sel.removeAllRanges();\n sel.addRange(this.savedSelection);\n return true;\n } else if (this.doc.getSelection /*&& this.savedSelection.select*/) {\n // this.savedSelection.select();\n return true;\n }\n } else {\n return false;\n }\n }\n\n /**\n * setTimeout used for execute 'saveSelection' method in next event loop iteration\n */\n public executeInNextQueueIteration(callbackFn: (...args: any[]) => any, timeout = 1e2): void {\n setTimeout(callbackFn, timeout);\n }\n\n /** check any selection is made or not */\n private checkSelection(): any {\n\n const selectedText = this.savedSelection.toString();\n\n if (selectedText.length === 0) {\n throw new Error('No Selection Made');\n }\n return true;\n }\n\n /**\n * Upload file to uploadUrl\n * @param file The file\n */\n uploadImage(file: File): Observable<HttpEvent<UploadResponse>> {\n\n const uploadData: FormData = new FormData();\n\n uploadData.append('file', file, file.name);\n\n return this.http.post<UploadResponse>(this.uploadUrl, uploadData, {\n reportProgress: true,\n observe: 'events',\n withCredentials: this.uploadWithCredentials,\n });\n }\n\n /**\n * Insert image with Url\n * @param imageUrl The imageUrl.\n */\n insertImage(imageUrl: string) {\n this.doc.execCommand('insertImage', false, imageUrl);\n }\n\n setDefaultParagraphSeparator(separator: string) {\n this.doc.execCommand('defaultParagraphSeparator', false, separator);\n }\n\n createCustomClass(customClass: CustomClass) {\n let newTag = this.selectedText;\n if (customClass) {\n const tagName = customClass.tag ? customClass.tag : 'span';\n newTag = '<' + tagName + ' class=\"' + customClass.class + '\">' + this.selectedText + '</' + tagName + '>';\n }\n this.insertHtml(newTag);\n }\n\n insertVideo(videoUrl: string) {\n if (videoUrl.match('www.youtube.com')) {\n this.insertYouTubeVideoTag(videoUrl);\n }\n if (videoUrl.match('vimeo.com')) {\n this.insertVimeoVideoTag(videoUrl);\n }\n }\n\n private insertYouTubeVideoTag(videoUrl: string): void {\n const id = videoUrl.split('v=')[1];\n const imageUrl = `https://img.youtube.com/vi/${id}/0.jpg`;\n const thumbnail = `\n <div style='position: relative'>\n <a href='${videoUrl}' target='_blank'>\n <img src=\"${imageUrl}\" alt=\"click to watch\"/>\n <img style='position: absolute; left:200px; top:140px'\n src=\"https://img.icons8.com/color/96/000000/youtube-play.png\"/>\n </a>\n </div>`;\n this.insertHtml(thumbnail);\n }\n\n private insertVimeoVideoTag(videoUrl: string): void {\n const sub = this.http.get<any>(`https://vimeo.com/api/oembed.json?url=${videoUrl}`).subscribe(data => {\n const imageUrl = data.thumbnail_url_with_play_button;\n const thumbnail = `<div>\n <a href='${videoUrl}' target='_blank'>\n <img src=\"${imageUrl}\" alt=\"${data.title}\"/>\n </a>\n </div>`;\n this.insertHtml(thumbnail);\n sub.unsubscribe();\n });\n }\n\n nextNode(node) {\n if (node.hasChildNodes()) {\n return node.firstChild;\n } else {\n while (node && !node.nextSibling) {\n node = node.parentNode;\n }\n if (!node) {\n return null;\n }\n return node.nextSibling;\n }\n }\n\n getRangeSelectedNodes(range, includePartiallySelectedContainers) {\n let node = range.startContainer;\n const endNode = range.endContainer;\n let rangeNodes = [];\n\n // Special case for a range that is contained within a single node\n if (node === endNode) {\n rangeNodes = [node];\n } else {\n // Iterate nodes until we hit the end container\n while (node && node !== endNode) {\n rangeNodes.push( node = this.nextNode(node) );\n }\n\n // Add partially selected nodes at the start of the range\n node = range.startContainer;\n while (node && node !== range.commonAncestorContainer) {\n rangeNodes.unshift(node);\n node = node.parentNode;\n }\n }\n\n // Add ancestors of the range container, if required\n if (includePartiallySelectedContainers) {\n node = range.commonAncestorContainer;\n while (node) {\n rangeNodes.push(node);\n node = node.parentNode;\n }\n }\n\n return rangeNodes;\n }\n\n getSelectedNodes() {\n const nodes = [];\n if (this.doc.getSelection) {\n const sel = this.doc.getSelection();\n for (let i = 0, len = sel.rangeCount; i < len; ++i) {\n nodes.push.apply(nodes, this.getRangeSelectedNodes(sel.getRangeAt(i), true));\n }\n }\n return nodes;\n }\n\n replaceWithOwnChildren(el) {\n const parent = el.parentNode;\n while (el.hasChildNodes()) {\n parent.insertBefore(el.firstChild, el);\n }\n parent.removeChild(el);\n }\n\n removeSelectedElements(tagNames) {\n const tagNamesArray = tagNames.toLowerCase().split(',');\n this.getSelectedNodes().forEach((node) => {\n if (node.nodeType === 1 &&\n tagNamesArray.indexOf(node.tagName.toLowerCase()) > -1) {\n // Remove the node and replace it with its children\n this.replaceWithOwnChildren(node);\n }\n });\n }\n}\n","import { UploadResponse } from './angular-editor.service';\nimport { HttpEvent } from '@angular/common/http';\nimport { Observable } from 'rxjs';\n\nexport interface CustomClass {\n name: string;\n class: string;\n tag?: string;\n}\n\nexport interface Font {\n name: string;\n class: string;\n}\n\nexport interface AngularEditorConfig {\n editable?: boolean;\n spellcheck?: boolean;\n height?: 'auto' | string;\n minHeight?: '0' | string;\n maxHeight?: 'auto' | string;\n width?: 'auto' | string;\n minWidth?: '0' | string;\n translate?: 'yes' | 'now' | string;\n enableToolbar?: boolean;\n showToolbar?: boolean;\n placeholder?: string;\n defaultParagraphSeparator?: string;\n defaultFontName?: string;\n defaultFontSize?: '1' | '2' | '3' | '4' | '5' | '6' | '7' | string;\n uploadUrl?: string;\n upload?: (file: File) => Observable<HttpEvent<UploadResponse>>;\n uploadWithCredentials?: boolean;\n fonts?: Font[];\n customClasses?: CustomClass[];\n sanitize?: boolean;\n toolbarPosition?: 'top' | 'bottom';\n outline?: boolean;\n toolbarHiddenButtons?: string[][];\n rawPaste?: boolean;\n}\n\nexport const angularEditorConfig: AngularEditorConfig = {\n editable: true,\n spellcheck: true,\n height: 'auto',\n minHeight: '0',\n maxHeight: 'auto',\n width: 'auto',\n minWidth: '0',\n translate: 'yes',\n enableToolbar: true,\n showToolbar: true,\n placeholder: 'Enter text here...',\n defaultParagraphSeparator: '',\n defaultFontName: '',\n defaultFontSize: '',\n fonts: [\n {class: 'arial', name: 'Arial'},\n {class: 'times-new-roman', name: 'Times New Roman'},\n {class: 'calibri', name: 'Calibri'},\n {class: 'comic-sans-ms', name: 'Comic Sans MS'}\n ],\n uploadUrl: 'v1/image',\n uploadWithCredentials: false,\n sanitize: true,\n toolbarPosition: 'top',\n outline: true,\n /*toolbarHiddenButtons: [\n ['bold', 'italic', 'underline', 'strikeThrough', 'superscript', 'subscript'],\n ['heading', 'fontName', 'fontSize', 'color'],\n ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull', 'indent', 'outdent'],\n ['cut', 'copy', 'delete', 'removeFormat', 'undo', 'redo'],\n ['paragraph', 'blockquote', 'removeBlockquote', 'horizontalLine', 'orderedList', 'unorderedList'],\n ['link', 'unlink', 'image', 'video']\n ]*/\n};\n","export function isDefined(value: any) {\n return value !== undefined && value !== null;\n}\n","import {Component, ViewEncapsulation} from '@angular/core';\n\n@Component({\n selector: 'ae-toolbar-set, [aeToolbarSet]',\n templateUrl: './ae-toolbar-set.component.html',\n styleUrls: ['./ae-toolbar-set.component.scss'],\n //encapsulation: ViewEncapsulation.None,\n host: {\n 'class': 'angular-editor-toolbar-set'\n }\n})\nexport class AeToolbarSetComponent {\n\n constructor() {\n }\n\n}\n","<ng-content></ng-content>\n","import {Component, Input, ViewEncapsulation} from '@angular/core';\n\n@Component({\n selector: 'ae-button, button[aeButton]',\n templateUrl: './ae-button.component.html',\n styleUrls: ['./ae-button.component.scss'],\n //encapsulation: ViewEncapsulation.None,\n host: {\n 'class': 'angular-editor-button',\n '[tabIndex]': '-1',\n '[type]': '\"button\"',\n }\n})\nexport class AeButtonComponent {\n\n @Input() iconName = '';\n\n constructor() {\n }\n\n}\n","<ng-container *ngIf=\"iconName; else contentTemplate\">\n <svg>\n <use [attr.href]=\"'assets/ae-icons/icons.svg#' + iconName\" [attr.xlink:href]=\"'assets/ae-icons/icons.svg#' + iconName\"></use>\n </svg>\n</ng-container>\n<ng-template #contentTemplate>\n <ng-content></ng-content>\n</ng-template>\n","import {\n Component,\n ElementRef,\n EventEmitter,\n forwardRef,\n HostBinding,\n HostListener,\n Input,\n OnInit,\n Output,\n Renderer2,\n ViewChild,\n ViewEncapsulation\n} from '@angular/core';\nimport {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';\nimport {isDefined} from '../utils';\n\nexport interface SelectOption {\n label: string;\n value: string;\n}\n\n@Component({\n selector: 'ae-select',\n templateUrl: './ae-select.component.html',\n styleUrls: ['./ae-select.component.scss'],\n //encapsulation: ViewEncapsulation.None,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => AeSelectComponent),\n multi: true,\n }\n ]\n})\nexport class AeSelectComponent implements OnInit, ControlValueAccessor {\n @Input() options: SelectOption[] = [];\n // eslint-disable-next-line @angular-eslint/no-input-rename\n @Input('hidden') isHidden: boolean;\n\n selectedOption: SelectOption;\n disabled = false;\n optionId = 0;\n\n get label(): string {\n return this.selectedOption && this.selectedOption.hasOwnProperty('label') ? this.selectedOption.label : 'Select';\n }\n\n opened = false;\n\n get value(): string {\n return this.selectedOption.value;\n }\n\n @HostBinding('style.display') hidden = 'inline-block';\n\n // eslint-disable-next-line @angular-eslint/no-output-native, @angular-eslint/no-output-rename\n @Output('change') changeEvent = new EventEmitter();\n\n @ViewChild('labelButton', {static: true}) labelButton: ElementRef;\n\n constructor(private elRef: ElementRef,\n private r: Renderer2,\n ) {\n }\n\n ngOnInit() {\n this.selectedOption = this.options[0];\n if (isDefined(this.isHidden) && this.isHidden) {\n this.hide();\n }\n }\n\n hide() {\n this.hidden = 'none';\n }\n\n optionSelect(option: SelectOption, event: MouseEvent) {\n event.stopPropagation();\n this.setValue(option.value);\n this.onChange(this.selectedOption.value);\n this.changeEvent.emit(this.selectedOption.value);\n this.onTouched();\n this.opened = false;\n }\n\n toggleOpen(event: MouseEvent) {\n // event.stopPropagation();\n if (this.disabled) {\n return;\n }\n this.opened = !this.opened;\n }\n\n @HostListener('document:click', ['$event'])\n onClick($event: MouseEvent) {\n if (!this.elRef.nativeElement.contains($event.target)) {\n this.close();\n }\n }\n\n close() {\n this.opened = false;\n }\n\n get isOpen(): boolean {\n return this.opened;\n }\n\n writeValue(value) {\n if (!value || typeof value !== 'string') {\n return;\n }\n this.setValue(value);\n }\n\n setValue(value) {\n let index = 0;\n const selectedEl = this.options.find((el, i) => {\n index = i;\n return el.value === value;\n });\n if (selectedEl) {\n this.selectedOption = selectedEl;\n this.optionId = index;\n }\n }\n\n onChange: any = () => {\n }\n onTouched: any = () => {\n }\n\n registerOnChange(fn) {\n this.onChange = fn;\n }\n\n registerOnTouched(fn) {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.labelButton.nativeElement.disabled = isDisabled;\n const div = this.labelButton.nativeElement;\n const action = isDisabled ? 'addClass' : 'removeClass';\n this.r[action](div, 'disabled');\n this.disabled = isDisabled;\n }\n\n @HostListener('keydown', ['$event'])\n handleKeyDown($event: KeyboardEvent) {\n if (!this.opened) {\n return;\n }\n // console.log($event.key);\n // if (KeyCode[$event.key]) {\n switch ($event.key) {\n case 'ArrowDown':\n this._handleArrowDown($event);\n break;\n case 'ArrowUp':\n this._handleArrowUp($event);\n break;\n case 'Space':\n this._handleSpace($event);\n break;\n case 'Enter':\n this._handleEnter($event);\n break;\n case 'Tab':\n this._handleTab($event);\n break;\n case 'Escape':\n this.close();\n $event.preventDefault();\n break;\n case 'Backspace':\n this._handleBackspace();\n break;\n }\n // } else if ($event.key && $event.key.length === 1) {\n // this._keyPress$.next($event.key.toLocaleLowerCase());\n // }\n }\n\n _handleArrowDown($event) {\n if (this.optionId < this.options.length - 1) {\n this.optionId++;\n }\n }\n\n _handleArrowUp($event) {\n if (this.optionId >= 1) {\n this.optionId--;\n }\n }\n\n _handleSpace($event) {\n\n }\n\n _handleEnter($event) {\n this.optionSelect(this.options[this.optionId], $event);\n }\n\n _handleTab($event) {\n\n }\n\n _handleBackspace() {\n\n }\n}\n","<span class=\"ae-picker\" [ngClass]=\"{'ae-expanded':isOpen}\">\n <button [tabIndex]=\"-1\" #labelButton tabindex=\"-1\" type=\"button\" role=\"button\" class=\"ae-picker-label\" (click)=\"toggleOpen($event);\">{{label}}\n <svg>\n <use [attr.href]=\"'assets/ae-icons/icons.svg#sort'\" [attr.xlink:href]=\"'assets/ae-icons/icons.svg#sort'\"></use>\n </svg>\n </button>\n <span class=\"ae-picker-options\">\n <button tabindex=\"-1\" type=\"button\" role=\"button\" class=\"ae-picker-item\"\n *ngFor=\"let item of options; let i = index\"\n [ngClass]=\"{'selected': item.value === value, 'focused': i === optionId}\"\n (click)=\"optionSelect(item, $event)\">\n {{item.label}}\n </button>\n <span class=\"dropdown-item\" *ngIf=\"!options.length\">No items for select</span>\n </span>\n</span>\n","import {\n Component,\n ElementRef,\n EventEmitter,\n Inject,\n Input,\n Output,\n Renderer2,\n ViewChild,\n ViewEncapsulation\n} from '@angular/core';\nimport {AngularEditorService, UploadResponse} from '../angular-editor.service';\nimport {HttpEvent, HttpResponse} from '@angular/common/http';\nimport {DOCUMENT} from '@angular/common';\nimport {CustomClass} from '../config';\nimport {SelectOption} from '../ae-select/ae-select.component';\nimport {Observable} from 'rxjs';\n\n@Component({\n selector: 'angular-editor-toolbar, ae-toolbar, div[aeToolbar]',\n templateUrl: './ae-toolbar.component.html',\n styleUrls: ['./ae-toolbar.component.scss'],\n //encapsulation: ViewEncapsulation.None,\n})\n\nexport class AeToolbarComponent {\n htmlMode = false;\n linkSelected = false;\n block = 'default';\n fontName = 'Times New Roman';\n fontSize = '3';\n foreColour;\n backColor;\n\n headings: SelectOption[] = [\n {\n label: 'Heading 1',\n value: 'h1',\n },\n {\n label: 'Heading 2',\n value: 'h2',\n },\n {\n label: 'Heading 3',\n value: 'h3',\n },\n {\n label: 'Heading 4',\n value: 'h4',\n },\n {\n label: 'Heading 5',\n value: 'h5',\n },\n {\n label: 'Heading 6',\n value: 'h6',\n },\n {\n label: 'Heading 7',\n value: 'h7',\n },\n {\n label: 'Paragraph',\n value: 'p',\n },\n {\n label: 'Predefined',\n value: 'pre'\n },\n {\n label: 'Standard',\n value: 'div'\n },\n {\n label: 'default',\n value: 'default'\n }\n ];\n\n fontSizes: SelectOption[] = [\n {\n label: '1',\n value: '1',\n },\n {\n label: '2',\n value: '2',\n },\n {\n label: '3',\n value: '3',\n },\n {\n label: '4',\n value: '4',\n },\n {\n label: '5',\n value: '5',\n },\n {\n label: '6',\n value: '6',\n },\n {\n label: '7',\n value: '7',\n }\n ];\n\n customClassId = '-1';\n // eslint-disable-next-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match\n _customClasses: CustomClass[];\n customClassList: SelectOption[] = [{label: '', value: ''}];\n // uploadUrl: string;\n\n tagMap = {\n BLOCKQUOTE: 'indent',\n A: 'link'\n };\n\n select = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'PRE', 'DIV'];\n\n buttons = ['bold', 'italic', 'underline', 'strikeThrough', 'subscript', 'superscript', 'justifyLeft', 'justifyCenter',\n 'justifyRight', 'justifyFull', 'indent', 'outdent', 'insertUnorderedList', 'insertOrderedList', 'link'];\n\n @Input() id: string;\n @Input() uploadUrl: string;\n @Input() upload: (file: File) => Observable<HttpEvent<UploadResponse>>;\n @Input() showToolbar: boolean;\n @Input() fonts: SelectOption[] = [{label: '', value: ''}];\n\n @Input()\n set customClasses(classes: CustomClass[]) {\n if (classes) {\n this._customClasses = classes;\n this.customClassList = this._customClasses.map((x, i) => ({label: x.name, value: i.toString()}));\n this.customClassList.unshift({label: 'Clear Class', value: '-1'});\n }\n }\n\n @Input()\n set defaultFontName(value: string) {\n if (value) {\n this.fontName = value;\n }\n }\n\n @Input()\n set defaultFontSize(value: string) {\n if (value) {\n this.fontSize = value;\n }\n }\n\n @Input() hiddenButtons: string[][];\n\n @Output() execute: EventEmitter<string> = new EventEmitter<string>();\n\n @ViewChild('fileInput', {static: true}) myInputFile: ElementRef;\n\n public get isLinkButtonDisabled(): boolean {\n return this.htmlMode || !Boolean(this.editorService.selectedText);\n }\n\n constructor(\n private r: Renderer2,\n private editorService: AngularEditorService,\n private er: ElementRef,\n @Inject(DOCUMENT) private doc: any\n ) {\n }\n\n /**\n * Trigger command from editor header buttons\n * @param command string from toolbar buttons\n */\n triggerCommand(command: string) {\n this.execute.emit(command);\n }\n\n /**\n * highlight editor buttons when cursor moved or positioning\n */\n triggerButtons() {\n if (!this.showToolbar) {\n return;\n }\n this.buttons.forEach(e => {\n const result = this.doc.queryCommandState(e);\n const elementById = this.doc.getElementById(e + '-' + this.id);\n if (result) {\n this.r.addClass(elementById, 'active');\n } else {\n this.r.removeClass(elementById, 'active');\n }\n });\n }\n\n /**\n * trigger highlight editor buttons when cursor moved or positioning in block\n */\n triggerBlocks(nodes: Node[]) {\n if (!this.showToolbar) {\n return;\n }\n this.linkSelected = nodes.findIndex(x => x.nodeName === 'A') > -1;\n let found = false;\n this.select.forEach(y => {\n const node = nodes.find(x => x.nodeName === y);\n if (node !== undefined && y === node.nodeName) {\n if (found === false) {\n this.block = node.nodeName.toLowerCase();\n found = true;\n }\n } else if (found === false) {\n this.block = 'default';\n }\n });\n\n found = false;\n if (this._customClasses) {\n this._customClasses.forEach((y, index) => {\n const node = nodes.find(x => {\n if (x instanceof Element) {\n return x.className === y.class;\n }\n });\n if (node !== undefined) {\n if (found === false) {\n this.customClassId = index.toString();\n found = true;\n }\n } else if (found === false) {\n this.customClassId = '-1';\n }\n });\n }\n\n Object.keys(this.tagMap).map(e => {\n const elementById = this.doc.getElementById(this.tagMap[e] + '-' + this.id);\n const node = nodes.find(x => x.nodeName === e);\n if (node !== undefined && e === node.nodeName) {\n this.r.addClass(elementById, 'active');\n } else {\n this.r.removeClass(elementById, 'active');\n }\n });\n\n this.foreColour = this.doc.queryCommandValue('ForeColor');\n this.fontSize = this.doc.queryCommandValue('FontSize');\n this.fontName = this.doc.queryCommandValue('FontName').replace(/\"/g, '');\n this.backColor = this.doc.queryCommandValue('backColor');\n }\n\n /**\n * insert URL link\n */\n insertUrl() {\n let url = 'https:\\/\\/';\n const selection = this.editorService.savedSelection;\n if (selection && selection.commonAncestorContainer.parentElement.nodeName === 'A') {\n const parent = selection.commonAncestorContainer.parentElement as HTMLAnchorElement;\n if (parent.href !== '') {\n url = parent.href;\n }\n }\n url = prompt('Insert URL link', url);\n if (url && url !== '' && url !== 'https://') {\n this.editorService.createLink(url);\n }\n }\n\n /**\n * insert Video link\n */\n insertVideo() {\n this.execute.emit('');\n const url = prompt('Insert Video link', `https://`);\n if (url && url !== '' && url !== `https://`) {\n this.editorService.insertVideo(url);\n }\n }\n\n /** insert color */\n insertColor(color: string, where: string) {\n this.editorService.insertColor(color, where);\n this.execute.emit('');\n }\n\n /**\n * set font Name/family\n * @param foreColor string\n */\n setFontName(foreColor: string): void {\n this.editorService.setFontName(foreColor);\n this.execute.emit('');\n }\n\n /**\n * set font Size\n * @param fontSize string\n */\n setFontSize(fontSize: string): void {\n this.editorService.setFontSize(fontSize);\n this.execute.emit('');\n }\n\n /**\n * toggle editor mode (WYSIWYG or SOURCE)\n * @param m boolean\n */\n setEditorMode(m: boolean) {\n const toggleEditorModeButton = this.doc.getElementById('toggleEditorMode' + '-' + this.id);\n if (m) {\n this.r.addClass(toggleEditorModeButton, 'active');\n } else {\n this.r.removeClass(toggleEditorModeButton, 'active');\n }\n this.htmlMode = m;\n }\n\n /**\n * Upload image when file is selected.\n */\n onFileChanged(event) {\n const file = event.target.files[0];\n if (file.type.includes('image/')) {\n if (this.upload) {\n this.upload(file).subscribe((response: HttpResponse<UploadResponse>) => this.watchUploadImage(response, event));\n } else if (this.uploadUrl) {\n this.editorService.uploadImage(file).subscribe((response: HttpResponse<UploadResponse>) => this.watchUploadImage(response, event));\n } else {\n const reader = new FileReader();\n reader.onload = (e: ProgressEvent) => {\n const fr = e.currentTarget as FileReader;\n this.editorService.insertImage(fr.result.toString());\n };\n reader.readAsDataURL(file);\n }\n }\n }\n\n watchUploadImage(response: HttpResponse<{ imageUrl: string }>, event) {\n const {imageUrl} = response.body;\n this.editorService.insertImage(imageUrl);\n event.srcElement.value = null;\n }\n\n /**\n * Set custom class\n */\n setCustomClass(classId: string) {\n if (classId === '-1') {\n this.execute.emit('clear');\n } else {\n this.editorService.createCustomClass(this._customClasses[+classId]);\n }\n }\n\n isButtonHidden(name: string): boolean {\n if (!name) {\n return false;\n }\n if (!(this.hiddenButtons instanceof Array)) {\n return false;\n }\n let result: any;\n for (const arr of this.hiddenButtons) {\n if (arr instanceof Array) {\n result = arr.find(item => item === name);\n }\n if (result) {\n break;\n }\n }\n return result !== undefined;\n }\n\n focus() {\n this.execute.emit('focus');\n console.log('focused');\n }\n}\n","<div class=\"angular-editor-toolbar\" *ngIf=\"showToolbar\">\n <div aeToolbarSet>\n <button aeButton title=\"Undo\" iconName=\"undo\" (click)=\"triggerCommand('undo')\" [hidden]=\"isButtonHidden('undo')\">\n </button>\n <button aeButton title=\"Redo\" iconName=\"redo\" (click)=\"triggerCommand('redo')\"\n [hidden]=\"isButtonHidden('redo')\">\n </button>\n </div>\n <div aeToolbarSet>\n <button [id]=\"'bold-'+id\" aeButton title=\"Bold\" iconName=\"bold\" (click)=\"triggerCommand('bold')\"\n [disabled]=\"htmlMode\" [hidden]=\"isButtonHidden('bold')\">\n </button>\n <button [id]=\"'italic-'+id\" aeButton iconName=\"italic\" title=\"Italic\" (click)=\"triggerCommand('italic')\"\n [disabled]=\"htmlMode\" [hidden]=\"isButtonHidden('italic')\">\n </button>\n <button [id]=\"'underline-'+id\" aeButton title=\"Underline\" iconName=\"underline\"\n (click)=\"triggerCommand('underline')\" [disabled]=\"htmlMode\" [hidden]=\"isButtonHidden('underline')\">\n </button>\n <button [id]=\"'strikeThrough-'+id\" aeButton iconName=\"strikeThrough\" title=\"Strikethrough\"\n (click)=\"triggerCommand('strikeThrough')\" [disabled]=\"htmlMode\"\n [hidden]=\"isButtonHidden('strikeThrough')\">\n </button>\n <button [id]=\"'subscript-'+id\" aeButton title=\"Subscript\" iconName=\"subscript\"\n (click)=\"triggerCommand('subscript')\" [disabled]=\"htmlMode\" [hidden]=\"isButtonHidden('subscript')\">\n </button>\n <button [id]=\"'superscript-'+id\" aeButton iconName=\"superscript\" title=\"Superscript\"\n (click)=\"triggerCommand('superscript')\" [disabled]=\"htmlMode\"\n [hidden]=\"isButtonHidden('superscript')\">\n </button>\n </div>\n <div aeToolbarSet>\n <button [id]=\"'justifyLeft-'+id\" aeButton iconName=\"justifyLeft\" title=\"Justify Left\"\n (click)=\"triggerCommand('justifyLeft')\" [disabled]=\"htmlMode\"\n [hidden]=\"isButtonHidden('justifyLeft')\">\n </button>\n <button [id]=\"'justifyCenter-'+id\" aeButton iconName=\"justifyCenter\" title=\"Justify Center\"\n (click)=\"triggerCommand('justifyCenter')\" [disabled]=\"htmlMode\"\n [hidden]=\"isButtonHidden('justifyCenter')\">\n </button>\n <button [id]=\"'justifyRight-'+id\" aeButton iconName=\"justifyRight\" title=\"Justify Right\"\n (click)=\"triggerCommand('justifyRight')\" [disabled]=\"htmlMode\"\n [hidden]=\"isButtonHidden('justifyRight')\">\n </button>\n <button [id]=\"'justifyFull-'+id\" aeButton iconName=\"justifyFull\" title=\"Justify Full\"\n (click)=\"triggerCommand('justifyFull')\" [disabled]=\"htmlMode\"\n [hidden]=\"isButtonHidden('justifyFull')\">\n </button>\n </div>\n <div aeToolbarSet>\n <button [id]=\"'indent-'+id\" aeButton iconName=\"indent\" title=\"Indent\"\n (click)=\"triggerCommand('indent')\"\n [disabled]=\"htmlMode\" [hidden]=\"isButtonHidden('indent')\">\n </button>\n <button [id]=\"'outdent-'+id\" aeButton iconName=\"outdent\" title=\"Outdent\"\n (click)=\"triggerCommand('outdent')\"\n [disabled]=\"htmlMode\" [hidden]=\"isButtonHidden('outdent')\">\n </button>\n </div>\n <div aeToolbarSet>\n <button [id]=\"'insertUnorderedList-'+id\" aeButton iconName=\"unorderedList\" title=\"Unordered List\"\n (click)=\"triggerCommand('insertUnorderedList')\" [disabled]=\"htmlMode\"\n [hidden]=\"isButtonHidden('insertUnorderedList')\">\n </button>\n <button [id]=\"'insertOrderedList-'+id\" aeButton iconName=\"orderedList\" title=\"Ordered List\"\n (click)=\"triggerCommand('insertOrderedList')\" [disabled]=\"htmlMode\"\n [hidden]=\"isButtonHidden('insertOrderedList')\">\n </button>\n </div>\n <div aeToolbarSet>\n <ae-select class=\"select-heading\" [options]=\"headings\"\n [(ngModel)]=\"block\"\n (change)=\"triggerCommand(block)\"\n [disabled]=\"htmlMode\"\n [hidden]=\"isButtonHidden('heading')\"\n tabindex=\"-1\"></ae-select>\n </div>\n <div aeToolbarSet>\n <ae-select class=\"select-font\" [options]=\"fonts\"\n [(ngModel)]=\"fontName\"\n (change)=\"setFontName(fontName)\"\n [disabled]=\"htmlMode\"\n [hidden]=\"isButtonHidden('fontName')\"\n tabindex=\"-1\"></ae-select>\n </div>\n <div aeToolbarSet>\n <ae-select class=\"select-font-size\" [options]=\"fontSizes\"\n [(ngModel)]=\"fontSize\"\n (change)=\"setFontSize(fontSize)\"\n [disabled]=\"htmlMode\"\n [hidden]=\"isButtonHidden('fontSize')\"\n tabindex=\"-1\">\n </ae-select>\n </div>\n <div aeToolbarSet>\n <input\n style=\"display: none\"\n type=\"color\" (change)=\"insertColor(fgInput.value, 'textColor')\"\n #fgInput>\n <button [id]=\"'foregroundColorPicker-'+id\" aeButton iconName=\"textColor\"\n (click)=\"focus(); ; fgInput.click()\"\n title=\"Text Color\"\n [disabled]=\"htmlMode\" [hidden]=\"isButtonHidden('textColor')\">\n </button>\n <input\n style=\"display: none\"\n type=\"color\" (change)=\"insertColor(bgInput.value, 'backgroundColor')\"\n #bgInput>\n <button [id]=\"'backgroundColorPicker-'+id\" aeButton iconName=\"backgroundColor\"\n (click)=\"focus(); ; bgInput.click()\"\n title=\"Background Color\"\n [disabled]=\"htmlMode\" [hidden]=\"isButtonHidden('backgroundColor')\">\n </button>\n </div>\n <div *ngIf=\"_customClasses\" aeToolbarSet>\n <ae-select class=\"select-custom-style\" [options]=\"customClassList\"\n [(ngModel)]=\"customClassId\"\n (change)=\"setCustomClass(customClassId)\"\n [disabled]=\"htmlMode\"\n [hidden]=\"isButtonHidden('customClasses')\"\n tabindex=\"-1\"></ae-select>\n </div>\n <div aeToolbarSet>\n <button [id]=\"'link-'+id\" aeButton iconName=\"link\" (click)=\"insertUrl()\"\n title=\"Insert Link\" [disabled]=\"isLinkButtonDisabled\" [hidden]=\"isButtonHidden('link')\">\n </button>\n <button [id]=\"'unlink-'+id\" aeButton iconName=\"unlink\" (click)=\"triggerCommand('unlink')\"\n title=\"Unlink\" [disabled]=\"htmlMode || !linkSelected\" [hidden]=\"isButtonHidden('unlink')\">\n </button>\n <input\n style=\"display: none\"\n accept=\"image/*\"\n type=\"file\" (change)=\"onFileChanged($event)\"\n #fileInput>\n <button [id]=\"'insertImage-'+id\" aeButton iconName=\"image\" (click)=\"focus(); fileInput.click()\"\n title=\"Insert Image\"\n [disabled]=\"htmlMode\" [hidden]=\"isButtonHidden('insertImage')\">\n </button>\n <button [id]=\"'insertVideo-'+id\" aeButton iconName=\"video\"\n (click)=\"insertVideo()\" title=\"Insert Video\" [disabled]=\"htmlMode\"\n [hidden]=\"isButtonHidden('insertVideo')\">\n </button>\n <button [id]=\"'insertHorizontalRule-'+id\" aeButton iconName=\"horizontalLine\" title=\"Horizontal Line\"\n (click)=\"triggerCommand('insertHorizontalRule')\" [disabled]=\"htmlMode\"\n [hidden]=\"isButtonHidden('insertHorizontalRule')\">\n </button>\n </div>\n <div aeToolbarSet>\n <button [id]=\"'clearFormatting-'+id\" aeButton iconName=\"removeFormat\" title=\"Clear Formatting\"\n class=\"angular-editor-button\"\n (click)=\"triggerCommand('removeFormat')\" [disabled]=\"htmlMode\" [hidden]=\"isButtonHidden('removeFormat')\">\n </button>\n </div>\n <div aeToolbarSet>\n <button [id]=\"'toggleEditorMode-'+id\" aeButton iconName=\"htmlCode\" title=\"HTML Code\"\n (click)=\"triggerCommand('toggleEditorMode')\" [hidden]=\"isButtonHidden('toggleEditorMode')\">\n </button>\n </div>\n <ng-content></ng-content>\n</div>\n","import {DOCUMENT} from '@angular/common';\nimport {\n AfterViewInit,\n Attribute,\n ChangeDetectorRef,\n Component,\n ContentChild,\n ElementRef,\n EventEmitter,\n forwardRef,\n HostBinding,\n HostListener,\n Inject,\n Input,\n OnDestroy,\n OnInit,\n Output,\n Renderer2,\n SecurityContext,\n TemplateRef,\n ViewChild, ViewEncapsulation\n} from '@angular/core';\nimport {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';\nimport {DomSanitizer} from '@angular/platform-browser';\nimport {AeToolbarComponent} from '../ae-toolbar/ae-toolbar.component';\nimport {AngularEditorService} from '../angular-editor.service';\nimport {AngularEditorConfig, angularEditorConfig} from '../config';\nimport {isDefined} from '../utils';\n\n@Component({\n selector: 'angular-editor',\n templateUrl: './angular-editor.component.html',\n styleUrls: ['./angular-editor.component.scss'],\n //encapsulation: ViewEncapsulation.None,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => AngularEditorComponent),\n multi: true\n },\n AngularEditorService\n ]\n})\nexport class AngularEditorComponent implements OnInit, ControlValueAccessor, AfterViewInit, OnDestroy {\n\n private onChange: (value: string) => void;\n private onTouched: () => void;\n\n modeVisual = true;\n showPlaceholder = false;\n disabled = false;\n focused = false;\n touched = false;\n changed = false;\n\n focusInstance: any;\n blurInstance: any;\n\n @Input() id = '';\n @Input() config: AngularEditorConfig = angularEditorConfig;\n @Input() placeholder = '';\n @Input() tabIndex: number | null;\n\n @Output() html;\n\n @ViewChild('editor', {static: true}) textArea: ElementRef;\n @ViewChild('editorWrapper', {static: true}) editorWrapper: ElementRef;\n @ViewChild('editorToolbar') editorToolbar: AeToolbarComponent;\n @ContentChild(\"customButtons\") customButtonsTemplateRef?: TemplateRef<any>;\n executeCommandFn = this.executeCommand.bind(this);\n\n @Output() viewMode = new EventEmitter<boolean>();\n\n /** emits `blur` event when focused out from the textarea */\n // eslint-disable-next-line @angular-eslint/no-output-native, @angular-eslint/no-output-rename\n @Output('blur') blurEvent: EventEmitter<FocusEvent> = new EventEmitter<FocusEvent>();\n\n /** emits `focus` event when focused in to the textarea */\n // eslint-disable-next-line @angular-eslint/no-output-rename, @angular-eslint/no-output-native\n @Output('focus') focusEvent: EventEmitter<FocusEvent> = new EventEmitter<FocusEvent>();\n\n @HostBinding('attr.tabindex') tabindex = -1;\n\n @HostListener('focus')\n onFocus() {\n this.focus();\n }\n\n constructor(\n private r: Renderer2,\n private editorService: AngularEditorService,\n @Inject(DOCUMENT) private doc: any,\n private sanitizer: DomSanitizer,\n private cdRef: ChangeDetectorRef,\n @Attribute('tabindex') defaultTabIndex: string,\n @Attribute('autofocus') private autoFocus: any\n ) {\n const parsedTabIndex = Number(defaultTabIndex);\n this.tabIndex = (parsedTabIndex || parsedTabIndex === 0) ? parsedTabIndex : null;\n }\n\n ngOnInit() {\n this.config.toolbarPosition = this.config.toolbarPosition ? this.config.toolbarPosition : angularEditorConfig.toolbarPosition;\n }\n\n ngAfterViewInit() {\n if (isDefined(this.autoFocus)) {\n this.focus();\n }\n }\n\n onPaste(event: ClipboardEvent) {\n if (this.config.rawPaste) {\n event.preventDefault();\n const text = event.clipboardData.getData('text/plain');\n document.execCommand('insertHTML', false, text);\n return text;\n }\n }\n\n /**\n * Executed command from editor header buttons\n * @param command string from triggerCommand\n * @param value\n */\n executeCommand(command: string, value?: string) {\n this.focus();\n if (command === 'focus') {\n return;\n }\n if (command === 'toggleEditorMode') {\n this.toggleEditorMode(this.modeVisual);\n } else if (command !== '') {\n if (command === 'clear') {\n this.editorService.removeSelectedElements(this.getCustomTags());\n this.onContentChange(this.textArea.nativeElement);\n } else if (command === 'default') {\n this.editorService.removeSelectedElements('h1,h2,h3,h4,h5,h6,p,pre');\n this.onContentChange(this.textArea.nativeElement);\n } else {\n this.editorService.executeCommand(command, value);\n }\n this.exec();\n }\n }\n\n /**\n * focus event\n */\n onTextAreaFocus(event: FocusEvent): void {\n if (this.focused) {\n event.stopPropagation();\n return;\n }\n this.focused = true;\n this.focusEvent.emit(event);\n if (!this.touched || !this.changed) {\n this.editorService.executeInNextQueueIteration(() => {\n this.configure();\n this.touched = true;\n });\n }\n }\n\n /**\n * @description fires when cursor leaves textarea\n */\n public onTextAreaMouseOut(event: MouseEvent): void {\n this.editorService.saveSelection();\n }\n\n /**\n * blur event\n */\n onTextAreaBlur(event: FocusEvent) {\n /**\n * save selection if focussed out\n */\n this.editorService.executeInNextQueueIteration(this.editorService.saveSelection);\n\n if (typeof this.onTouched === 'function') {\n this.onTouched();\n }\n\n if (event.relatedTarget !== null) {\n const parent = (event.relatedTarget as HTMLElement).parentElement;\n if (!parent.classList.contains('angular-editor-toolbar-set') && !parent.classList.contains('ae-picker')) {\n this.blurEvent.emit(event);\n this.focused = false;\n }\n }\n }\n\n /**\n * focus the text area when the editor is focused\n */\n focus() {\n if (this.modeVisual) {\n this.textArea.nativeElement.focus();\n } else {\n const sourceText = this.doc.getElementById('sourceText' + this.id);\n sourceText.focus();\n this.focused = true;\n }\n }\n\n /**\n * Executed from the contenteditable section while the input property changes\n * @param element html element from contenteditable\n */\n onContentChange(element: HTMLElement): void {\n let html = '';\n if (this.modeVisual) {\n html = element.innerHTML;\n } else {\n html = element.innerText;\n }\n if ((!html || html === '<br>')) {\n html = '';\n }\n if (typeof this.onChange === 'function') {\n this.onChange(this.config.sanitize || this.config.sanitize === undefined ?\n this.sanitizer.sanitize(SecurityContext.HTML, html) : html);\n if ((!html) !== this.showPlaceholder) {\n this.togglePlaceholder(this.showPlaceholder);\n }\n }\n this.changed = true;\n }\n\n /**\n * Set the function to be called\n * when the control receives a change event.\n *\n * @param fn a function\n */\n registerOnChange(fn: any): void {\n this.onChange = e => (e === '<br>' ? fn('') : fn(e));\n }\n\n /**\n * Set the function to be called\n * when the control receives a touch event.\n *\n * @param fn a function\n */\n registerOnTouched(fn: any): void {\n this.onTouched = fn;\n }\n\n /**\n * Write a new value to the element.\n *\n * @param value value to be executed when there is a change in contenteditable\n */\n writeValue(value: any): void {\n\n if ((!value || value === '<br>' || value === '') !== this.showPlaceholder) {\n this.togglePlaceholder(this.showPlaceholder);\n }\n\n if (value === undefined || value === '' || value === '<br>') {\n value = null;\n }\n\n this.refreshView(value);\n }\n\n /**\n * refresh view/HTML of the editor\n *\n * @param value html string from the editor\n */\n refreshView(value: string): void {\n const normalizedValue = value === null ? '' : value;\n this.r.setProperty(this.textArea.nativeElement, 'innerHTML', normalizedValue);\n\n return;\n }\n\n /**\n * toggles placeholder based on input string\n *\n * @param value A HTML string from the editor\n */\n togglePlaceholder(value: boolean): void {\n if (!value) {\n this.r.addClass(this.editorWrapper.nativeElement, 'show-placeholder');\n this.showPlaceholder = true;\n\n } else {\n this.r.removeClass(this.editorWrapper.nativeElement, 'show-placeholder');\n this.showPlaceholder = false;\n }\n }\n\n /**\n * Implements disabled state for this element\n *\n * @param isDisabled Disabled flag\n */\n setDisabledState(isDisabled: boolean): void {\n const div = this.textArea.nativeElement;\n const action = isDisabled ? 'addClass' : 'removeClass';\n this.r[action](div, 'disabled');\n this.disabled = isDisabled;\n }\n\n /**\n * toggles editor mode based on bToSource bool\n *\n * @param bToSource A boolean value from the editor\n */\n toggleEditorMode(bToSource: boolean) {\n let oContent: any;\n const editableElement = this.textArea.nativeElement;\n\n if (bToSource) {\n oContent = this.r.createText(editableElement.innerHTML);\n this.r.setProperty(editableElement, 'innerHTML', '');\n this.r.setProperty(editableElement, 'contentEditable', false);\n\n const oPre = this.r.createElement('pre');\n this.r.setStyle(oPre, 'margin', '0');\n this.r.setStyle(oPre, 'outline', 'none');\n\n const oCode = this.r.createElement('code');\n this.r.setProperty(oCode, 'id', 'sourceText' + this.id);\n this.r.setStyle(oCode, 'display', 'block');\n this.r.setStyle(oCode, 'white-space', 'pre-wrap');\n this.r.setStyle(oCode, 'word-break', 'keep-all');\n this.r.setStyle(oCode, 'outline', 'none');\n this.r.setStyle(oCode, 'margin', '0');\n this.r.setStyle(oCode, 'background-color', '#fff5b9');\n this.r.setProperty(oCode, 'contentEditable', true);\n this.r.appendChild(oCode, oContent);\n this.focusInstance = this.r.listen(oCode, 'focus', (event) => this.onTextAreaFocus(event));\n this.blurInstance = this.r.listen(oCode, 'blur', (event) => this.onTextAreaBlur(event));\n this.r.appendChild(oPre, oCode);\n this.r.appendChild(editableElement, oPre);\n\n // ToDo move to service\n this.doc.execCommand('defaultParagraphSeparator', false, 'div');\n\n this.modeVisual = false;\n this.viewMode.emit(false);\n oCode.focus();\n } else {\n if (this.doc.querySelectorAll) {\n this.r.setProperty(editableElement, 'innerHTML', editableElement.innerText);\n } else {\n oContent = this.doc.createRange();\n oContent.selectNodeContents(editableElement.firstChild);\n this.r.setProperty(editableElement, 'innerHTML', oContent.toString());\n }\n this.r.setProperty(editableElement, 'contentEditable', true);\n this.modeVisual = true;\n this.viewMode.emit(true);\n this.onContentChange(editableElement);\n editableElement.focus();\n }\n this.editorToolbar.setEditorMode(!this.modeVisual);\n }\n\n /**\n * toggles editor buttons when cursor moved or positioning\n *\n * Send a node array from the contentEditable of the editor\n */\n exec() {\n this.editorToolbar.triggerButtons();\n\n let userSelection;\n if (this.doc.getSelection) {\n userSelection = this.doc.getSelection();\n this.editorService.executeInNextQueueIteration(this.editorService.saveSelection);\n }\n\n let a = userSelection.focusNode;\n const els = [];\n while (a && a.id !== 'editor') {\n els.unshift(a);\n a = a.parentNode;\n }\n this.editorToolbar.triggerBlocks(els);\n }\n\n private configure() {\n this.editorService.uploadUrl = this.config.uploadUrl;\n this.editorService.uploadWithCredentials = this.config.uploadWithCredentials;\n if (this.config.defaultParagraphSeparator) {\n this.editorService.setDefaultParagraphSeparator(this.config.defaultParagraphSeparator);\n }\n if (this.config.defaultFontName) {\n this.editorService.setFontName(this.config.defaultFontName);\n }\n if (this.config.defaultFontSize) {\n this.editorService.setFontSize(this.config.defaultFontSize);\n }\n }\n\n getFonts() {\n const fonts = this.config.fonts ? this.config.fonts : angularEditorConfig.fonts;\n return fonts.map(x => {\n return {label: x.name, value: x.name};\n });\n }\n\n getCustomTags() {\n const tags = ['span'];\n this.config.customClasses.forEach(x => {\n if (x.tag !== undefined) {\n if (!tags.includes(x.tag)) {\n tags.push(x.tag);\n }\n }\n });\n return tags.join(',');\n }\n\n ngOnDestroy() {\n if (this.blurInstance) {\n this.blurInstance();\n }\n if (this.focusInstance) {\n this.focusInstance();\n }\n }\n\n filterStyles(html: string): string {\n html = html.replace('position: fixed;', '');\n return html;\n }\n}\n","<div\n class=\"angular-editor\"\n #angularEditor\n [style.width]=\"config.wid