UNPKG

@kolkov/angular-editor

Version:

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

1 lines 105 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-select/ae-select.component.ts","../../../projects/angular-editor/src/lib/ae-select/ae-select.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-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-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, DOCUMENT} from '@angular/core';\nimport {HttpClient, HttpEvent} from '@angular/common/http';\nimport {Observable} from 'rxjs';\n\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 /**\n * Apply custom class to selection with enterprise-level HTML structure preservation.\n * Supports three modes:\n * - 'inline': Wrap selection in a single element (legacy behavior)\n * - 'block': Apply class to each block element in selection\n * - 'auto': Smart detection based on selection span (default)\n *\n * @param customClass The custom class configuration\n */\n createCustomClass(customClass: CustomClass): void {\n if (!customClass || !this.savedSelection) {\n return;\n }\n\n const mode = customClass.mode || 'auto';\n const range = this.savedSelection;\n\n // Restore selection before applying\n this.restoreSelection();\n\n if (mode === 'inline') {\n this.applyClassInline(range, customClass);\n } else if (mode === 'block') {\n this.applyClassToBlocks(range, customClass);\n } else {\n // Auto mode: detect if selection spans multiple blocks\n const blocks = this.getBlockElementsInRange(range);\n if (blocks.length > 1) {\n this.applyClassToBlocks(range, customClass);\n } else {\n this.applyClassInline(range, customClass);\n }\n }\n }\n\n /**\n * Apply class inline by wrapping selection in a single element.\n * Uses extractContents + insertNode pattern for safety.\n */\n private applyClassInline(range: Range, customClass: CustomClass): void {\n const tagName = customClass.tag || 'span';\n\n try {\n // Create wrapper element\n const wrapper = this.doc.createElement(tagName);\n wrapper.className = customClass.class;\n\n // Extract contents and wrap (safer than surroundContents)\n const contents = range.extractContents();\n wrapper.appendChild(contents);\n range.insertNode(wrapper);\n\n // Normalize to merge adjacent text nodes\n if (wrapper.parentNode) {\n wrapper.parentNode.normalize();\n }\n\n // Update selection to the new wrapper\n range.selectNodeContents(wrapper);\n const sel = this.doc.getSelection();\n sel.removeAllRanges();\n sel.addRange(range);\n } catch (e) {\n // Fallback to legacy method if DOM manipulation fails\n console.warn('applyClassInline failed, using fallback:', e);\n this.applyClassInlineFallback(customClass);\n }\n }\n\n /**\n * Fallback method for inline class application (legacy behavior).\n */\n private applyClassInlineFallback(customClass: CustomClass): void {\n const tagName = customClass.tag || 'span';\n const newTag = '<' + tagName + ' class=\"' + customClass.class + '\">' + this.selectedText + '</' + tagName + '>';\n this.insertHtml(newTag);\n }\n\n /**\n * Apply class to each block element in selection.\n * Preserves HTML structure by adding class to existing elements.\n */\n private applyClassToBlocks(range: Range, customClass: CustomClass): void {\n const blocks = this.getBlockElementsInRange(range);\n\n if (blocks.length === 0) {\n // No blocks found, fall back to inline\n this.applyClassInline(range, customClass);\n return;\n }\n\n // Apply class to each block element\n blocks.forEach(block => {\n // Toggle class: remove if present, add if not\n if (block.classList.contains(customClass.class)) {\n block.classList.remove(customClass.class);\n } else {\n block.classList.add(customClass.class);\n }\n });\n\n // Trigger change detection by moving cursor\n const sel = this.doc.getSelection();\n if (sel && blocks.length > 0) {\n // Reselect the original range\n sel.removeAllRanges();\n sel.addRange(range);\n }\n }\n\n /**\n * Get all block-level elements within a range.\n * Returns elements that are fully or partially selected.\n */\n private getBlockElementsInRange(range: Range): HTMLElement[] {\n const blockTags = [\n 'P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',\n 'LI', 'BLOCKQUOTE', 'PRE', 'ADDRESS', 'ARTICLE',\n 'ASIDE', 'FIGCAPTION', 'FIGURE', 'FOOTER', 'HEADER',\n 'MAIN', 'NAV', 'SECTION'\n ];\n const blocks: HTMLElement[] = [];\n const seen = new Set<HTMLElement>();\n\n // Get the common ancestor\n const container = range.commonAncestorContainer;\n const root = container.nodeType === Node.ELEMENT_NODE\n ? container as HTMLElement\n : container.parentElement;\n\n if (!root) {\n return blocks;\n }\n\n // If root itself is a block element and fully contains the range\n if (blockTags.includes(root.tagName) && this.isNodeFullyInRange(root, range)) {\n return [root];\n }\n\n // Find all block elements within the range using TreeWalker\n const walker = this.doc.createTreeWalker(\n root,\n NodeFilter.SHOW_ELEMENT,\n {\n acceptNode: (node: Element) => {\n if (!blockTags.includes(node.tagName)) {\n return NodeFilter.FILTER_SKIP;\n }\n // Check if node intersects with selection\n if (range.intersectsNode(node)) {\n return NodeFilter.FILTER_ACCEPT;\n }\n return NodeFilter.FILTER_SKIP;\n }\n }\n );\n\n let node: Node | null;\n while ((node = walker.nextNode())) {\n const element = node as HTMLElement;\n // Avoid duplicates and nested blocks\n if (!seen.has(element) && !this.hasAncestorInSet(element, seen)) {\n seen.add(element);\n blocks.push(element);\n }\n }\n\n // If no blocks found, check if we're inside a block\n if (blocks.length === 0) {\n let parent = root;\n while (parent && parent !== this.doc.body) {\n if (blockTags.includes(parent.tagName)) {\n blocks.push(parent);\n break;\n }\n parent = parent.parentElement;\n }\n }\n\n return blocks;\n }\n\n /**\n * Check if a node is fully contained within a range.\n */\n private isNodeFullyInRange(node: Node, range: Range): boolean {\n const nodeRange = this.doc.createRange();\n nodeRange.selectNodeContents(node);\n return range.compareBoundaryPoints(Range.START_TO_START, nodeRange) <= 0 &&\n range.compareBoundaryPoints(Range.END_TO_END, nodeRange) >= 0;\n }\n\n /**\n * Check if element has an ancestor in the given set.\n */\n private hasAncestorInSet(element: HTMLElement, set: Set<HTMLElement>): boolean {\n let parent = element.parentElement;\n while (parent) {\n if (set.has(parent)) {\n return true;\n }\n parent = parent.parentElement;\n }\n return false;\n }\n\n insertVideo(videoUrl: string) {\n if (videoUrl.match('www.youtube.com') || videoUrl.match('youtu.be')) {\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 // Support both formats: youtube.com/watch?v=ID and youtu.be/ID\n let id: string;\n if (videoUrl.includes('youtu.be/')) {\n id = videoUrl.split('youtu.be/')[1].split('?')[0];\n } else {\n id = videoUrl.split('v=')[1].split('&')[0];\n }\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\n/**\n * Custom class configuration for applying styles to selected content.\n *\n * @example\n * // Basic usage (inline span)\n * { name: 'Red Text', class: 'text-red' }\n *\n * // Block-level class (applies to each paragraph)\n * { name: 'Highlight', class: 'highlight', mode: 'block' }\n *\n * // Auto mode (smart detection)\n * { name: 'Quote', class: 'quote', mode: 'auto', tag: 'div' }\n */\nexport interface CustomClass {\n /** Display name shown in dropdown */\n name: string;\n /** CSS class to apply */\n class: string;\n /** HTML tag to use for wrapping (default: 'span') */\n tag?: string;\n /**\n * Application mode:\n * - 'inline': Always wrap selection in a single element (legacy behavior)\n * - 'block': Apply class to each block element in selection\n * - 'auto': Smart detection - inline for single block, block for multiple (default)\n */\n mode?: 'inline' | 'block' | 'auto';\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 textDirection?: 'ltr' | 'rtl' | 'auto';\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 {\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 standalone: false\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 //console.log(event.button, event.buttons);\n if (event.buttons !== 1) {\n return;\n }\n event.preventDefault();\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 <span 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 (mousedown)=\"optionSelect(item, $event)\">\n {{item.label}}\n </span>\n <span class=\"dropdown-item\" *ngIf=\"!options.length\">No items for select</span>\n </span>\n</span>\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 standalone: false\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 {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 standalone: false\n})\nexport class AeToolbarSetComponent {\n\n constructor() {\n }\n\n}\n","<ng-content></ng-content>\n","import {\n Component,\n ElementRef,\n EventEmitter,\n Inject,\n Input,\n Output,\n Renderer2,\n ViewChild,\n ViewEncapsulation,\n DOCUMENT\n} from '@angular/core';\nimport {AngularEditorService, UploadResponse} from '../angular-editor.service';\nimport {HttpEvent, HttpResponse} from '@angular/common/http';\n\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 standalone: false\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: '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 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 // Use getAttribute to preserve relative URLs instead of href which returns absolute URL\n const href = parent.getAttribute('href');\n if (href !== '' && href !== null) {\n url = 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 // Reset input value to allow re-uploading the same file\n event.target.value = null;\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 }\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","\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 DOCUMENT\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 standalone: false\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.executeInNextQueueItera