UNPKG

ngx-text-editor

Version:
1,024 lines (1,015 loc) 68.8 kB
import { Injectable, EventEmitter, Component, forwardRef, Renderer2, Input, Output, ViewChild, HostListener, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { NG_VALUE_ACCESSOR, Validators, FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { PopoverConfig, PopoverModule } from 'ngx-bootstrap/popover'; import { HttpRequest, HttpClient, HttpResponse } from '@angular/common/http'; import { Subject } from 'rxjs'; /** * enable or disable toolbar based on configuration * * @param value toolbar item * @param toolbar toolbar configuration object */ function canEnableToolbarOptions(value, toolbar) { if (value) { if (toolbar['length'] === 0) { return true; } else { const found = toolbar.filter(array => { return array.indexOf(value) !== -1; }); return !!found.length; } } else { return false; } } /** * set editor configuration * * @param value configuration via [config] propertyify * @param ngxTextEditorConfig default editor configuration * @param input direct configuration inputs via directives */ function getEditorConfiguration(value, ngxTextEditorConfig, input) { for (const i in ngxTextEditorConfig) { if (i) { if (input[i] !== undefined) { value[i] = input[i]; } if (!value.hasOwnProperty(i)) { value[i] = ngxTextEditorConfig[i]; } } } return value; } /** * return vertical if the element is the resizer property is set to basic * * @param resizer type of resizer, either basic or stack */ function canResize(resizer) { if (resizer === 'basic') { return 'vertical'; } return false; } /** * save selection when the editor is focussed out */ function saveSelection() { if (window.getSelection) { const sel = window.getSelection(); if (sel.getRangeAt && sel.rangeCount) { return sel.getRangeAt(0); } } else if (document.getSelection && document.createRange) { return document.createRange(); } return null; } /** * restore selection when the editor is focussed in * * @param range saved selection when the editor is focussed out */ function restoreSelection(range) { if (range) { if (window.getSelection) { const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); return true; } else if (document.getSelection && range.select) { range.select(); return true; } } else { return false; } } var Utils = /*#__PURE__*/Object.freeze({ __proto__: null, canEnableToolbarOptions: canEnableToolbarOptions, getEditorConfiguration: getEditorConfiguration, canResize: canResize, saveSelection: saveSelection, restoreSelection: restoreSelection }); class CommandExecutorService { /** * * @param _http HTTP Client for making http requests */ constructor(_http) { this._http = _http; /** saves the selection from the editor when focused out */ this.savedSelection = undefined; } /** * executes command from the toolbar * * @param command command to be executed */ execute(command) { if (!this.savedSelection && command !== 'enableObjectResizing') { throw new Error('Range out of Editor'); } if (command === 'enableObjectResizing') { document.execCommand('enableObjectResizing', true); } if (command === 'blockquote') { document.execCommand('formatBlock', false, 'blockquote'); } if (command === 'removeBlockquote') { document.execCommand('formatBlock', false, 'div'); } if (command === 'h1') { document.execCommand('formatBlock', false, 'h1'); } if (command === 'h2') { document.execCommand('formatBlock', false, 'h2'); } if (command === 'h3') { document.execCommand('formatBlock', false, 'h3'); } if (command === 'clear') { document.execCommand('formatBlock', false, 'div'); } document.execCommand(command, false, null); } /** * inserts image in the editor * * @param imageURI url of the image to be inserted */ insertImage(imageURI) { if (this.savedSelection) { if (imageURI) { const restored = restoreSelection(this.savedSelection); if (restored) { const img = document.createElement('img'); img.src = imageURI; img.style.width = 'auto'; const inserted = this.insertHtml(img.outerHTML); if (!inserted) { throw new Error('Invalid URL'); } } } } else { throw new Error('Range out of the editor'); } } /** * inserts image in the editor * * @param videParams url of the image to be inserted */ insertVideo(videParams) { if (this.savedSelection) { if (videParams) { const restored = restoreSelection(this.savedSelection); if (restored) { if (this.isYoutubeLink(videParams.videoUrl)) { const youtubeURL = '<iframe width="' + videParams.width + '" height="' + videParams.height + '"' + 'src="' + videParams.videoUrl + '"></iframe>'; this.insertHtml(youtubeURL); } else if (this.checkTagSupportInBrowser('video')) { if (this.isValidURL(videParams.videoUrl)) { const videoSrc = '<video width="' + videParams.width + '" height="' + videParams.height + '"' + ' controls="true"><source src="' + videParams.videoUrl + '"></video>'; this.insertHtml(videoSrc); } else { throw new Error('Invalid video URL'); } } else { throw new Error('Unable to insert video'); } } } } else { throw new Error('Range out of the editor'); } } /** * checks the input url is a valid youtube URL or not * * @param url Youtue URL */ isYoutubeLink(url) { const ytRegExp = /^(http(s)?:\/\/)?((w){3}.)?youtu(be|.be)?(\.com)?\/.+/; return ytRegExp.test(url); } /** * check whether the string is a valid url or not * @param url url */ isValidURL(url) { const urlRegExp = /(http|https):\/\/(\w+:{0,1}\w*)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/; return urlRegExp.test(url); } /** * uploads image to the server * * @param file file that has to be uploaded * @param endPoint enpoint to which the image has to be uploaded */ uploadImage(file, endPoint) { if (!endPoint) { throw new Error('Image Endpoint isn`t provided or invalid'); } const formData = new FormData(); if (file) { formData.append('file', file); const req = new HttpRequest('POST', endPoint, formData, { reportProgress: true }); return this._http.request(req); } else { throw new Error('Invalid Image'); } } /** * inserts link in the editor * * @param params parameters that holds the information for the link */ createLink(params) { if (this.savedSelection) { /** * check whether the saved selection contains a range or plain selection */ if (params.urlNewTab) { const newUrl = '<a href="' + params.urlLink + '" target="_blank">' + params.urlText + '</a>'; if (document.getSelection().type !== 'Range') { const restored = restoreSelection(this.savedSelection); if (restored) { this.insertHtml(newUrl); } } else { throw new Error('Only new links can be inserted. You cannot edit URL`s'); } } else { const restored = restoreSelection(this.savedSelection); if (restored) { document.execCommand('createLink', false, params.urlLink); } } } else { throw new Error('Range out of the editor'); } } /** * insert color either font or background * * @param color color to be inserted * @param where where the color has to be inserted either text/background */ insertColor(color, where) { if (this.savedSelection) { const restored = restoreSelection(this.savedSelection); if (restored && this.checkSelection()) { if (where === 'textColor') { document.execCommand('foreColor', false, color); } else { document.execCommand('hiliteColor', false, color); } } } else { throw new Error('Range out of the editor'); } } /** * set font size for text * * @param fontSize font-size to be set */ setFontSize(fontSize) { if (this.savedSelection && this.checkSelection()) { const deletedValue = this.deleteAndGetElement(); if (deletedValue) { const restored = restoreSelection(this.savedSelection); if (restored) { if (this.isNumeric(fontSize)) { const fontPx = '<span style="font-size: ' + fontSize + 'px;">' + deletedValue + '</span>'; this.insertHtml(fontPx); } else { const fontPx = '<span style="font-size: ' + fontSize + ';">' + deletedValue + '</span>'; this.insertHtml(fontPx); } } } } else { throw new Error('Range out of the editor'); } } /** * set font name/family for text * * @param fontName font-family to be set */ setFontName(fontName) { if (this.savedSelection && this.checkSelection()) { const deletedValue = this.deleteAndGetElement(); if (deletedValue) { const restored = restoreSelection(this.savedSelection); if (restored) { if (this.isNumeric(fontName)) { const fontFamily = '<span style="font-family: ' + fontName + 'px;">' + deletedValue + '</span>'; this.insertHtml(fontFamily); } else { const fontFamily = '<span style="font-family: ' + fontName + ';">' + deletedValue + '</span>'; this.insertHtml(fontFamily); } } } } else { throw new Error('Range out of the editor'); } } /** insert HTML */ insertHtml(html) { const isHTMLInserted = document.execCommand('insertHTML', false, html); if (!isHTMLInserted) { throw new Error('Unable to perform the operation'); } return isHTMLInserted; } /** * check whether the value is a number or string * if number return true * else return false */ isNumeric(value) { return /^-{0,1}\d+$/.test(value); } /** delete the text at selected range and return the value */ deleteAndGetElement() { let slectedText; if (this.savedSelection) { slectedText = this.savedSelection.toString(); this.savedSelection.deleteContents(); return slectedText; } return false; } /** check any slection is made or not */ checkSelection() { const slectedText = this.savedSelection.toString(); if (slectedText.length === 0) { throw new Error('No Selection Made'); } return true; } /** * check tag is supported by browser or not * * @param tag HTML tag */ checkTagSupportInBrowser(tag) { return !(document.createElement(tag) instanceof HTMLUnknownElement); } } CommandExecutorService.decorators = [ { type: Injectable } ]; CommandExecutorService.ctorParameters = () => [ { type: HttpClient } ]; /** time in which the message has to be cleared */ const DURATION = 7000; class MessageService { constructor() { /** variable to hold the user message */ this.message = new Subject(); } /** returns the message sent by the editor */ getMessage() { return this.message.asObservable(); } /** * sends message to the editor * * @param message message to be sent */ sendMessage(message) { this.message.next(message); this.clearMessageIn(DURATION); } /** * a short interval to clear message * * @param milliseconds time in seconds in which the message has to be cleared */ clearMessageIn(milliseconds) { setTimeout(() => { this.message.next(undefined); }, milliseconds); } } MessageService.decorators = [ { type: Injectable } ]; MessageService.ctorParameters = () => []; /** * toolbar default configuration */ const ngxTextEditorConfig = { editable: true, spellcheck: true, height: 'auto', minHeight: '0', width: 'auto', minWidth: '0', translate: 'yes', enableToolbar: true, showToolbar: true, placeholder: 'Enter text here...', imageEndPoint: '', toolbar: [ ['h1', 'h2', 'h3', 'bold', 'italic', 'underline', 'strikeThrough', 'superscript', 'subscript'], ['fontName', 'fontSize', 'color'], ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull', 'indent', 'outdent'], ['cut', 'copy', 'delete', 'removeFormat', 'undo', 'redo'], ['paragraph', 'blockquote', 'removeBlockquote', 'horizontalLine', 'orderedList', 'unorderedList'], ['link', 'unlink', 'image', 'video'] ], toolbarTitle: { 'bold': 'Bold', 'italic': 'Italic', 'underline': 'Underline', 'strikeThrough': 'strikeThrough', 'superscript': 'Superscript', 'subscript': 'Subscript', 'fontName': 'Font Family', 'fontSize': 'Font Size', 'color': 'Color Picker', 'justifyLeft': 'Justify Left', 'justifyCenter': 'Justify Center', 'justifyRight': 'Justify Right', 'justifyFull': 'Justify Full', 'indent': 'Indent', 'outdent': 'Outdent', 'cut': 'Cut', 'copy': 'Copy', 'delete': 'Delete', 'removeFormat': 'Clear Formatting', 'undo': 'Undo', 'redo': 'Redo', 'paragraph': 'Paragraph', 'blockquote': 'Blockquote', 'removeBlockquote': 'Remove Blockquote', 'horizontalLine': 'Horizontal Line', 'orderedList': 'Ordered List', 'unorderedList': 'Color Picker', 'link': 'Insert Link', 'URLInput': 'URL', 'urlTextInput': 'Text', 'urlNewTab': 'Open in new tab', 'linkSubmit': 'Submit', 'unlink': 'Unlink', 'image': 'Insert Image', 'chooseImage': 'Choose Image', 'uploadingimage': 'Uploading Image', 'imageURLInput': 'URL', 'imageSubmit': 'Submit', 'video': 'Insert Video', 'videoURLInput': 'URL', 'height': 'height (px)', 'width': 'width (px)', 'heightwidth': 'Height/Width', 'videoSubmit': 'Submit', 'textColor': 'Text', 'backgroundColor': 'Background', 'hexInput': 'Hex Color', 'hexSubmit': 'Submit', 'fontSubmit': 'Submit', 'example': 'Ex:' } }; class NgxTextEditorComponent { /** * @param _messageService service to send message to the editor message component * @param _commandExecutor executes command from the toolbar * @param _renderer access and manipulate the dom element */ constructor(_messageService, _commandExecutor, _renderer) { this._messageService = _messageService; this._commandExecutor = _commandExecutor; this._renderer = _renderer; /** * The editor can be resized vertically. * * `basic` resizer enables the html5 reszier. Check here https://www.w3schools.com/cssref/css3_pr_resize.asp * * `stack` resizer enable a resizer that looks like as if in https://stackoverflow.com */ this.resizer = 'stack'; /** * The config property is a JSON object * * All avaibale inputs inputs can be provided in the configuration as JSON * inputs provided directly are considered as top priority */ this.config = ngxTextEditorConfig; /** emits `blur` event when focused out from the textarea */ this.blur = new EventEmitter(); /** emits `focus` event when focused in to the textarea */ this.focus = new EventEmitter(); /** emits `uploadImage` event when image is selected */ this.uploadImage = new EventEmitter(); this.Utils = Utils; } /** * events */ onTextAreaFocus() { this.focus.emit('focus'); } /** focus the text area when the editor is focussed */ onEditorFocus() { this.textArea.nativeElement.focus(); } /** * Executed from the contentEditable section while the input property changes * @param event html string from contentEditable */ onContentChange(event) { const innerHTML = event.target.value; if (typeof this.onChange === 'function') { this.onChange(innerHTML); this.togglePlaceholder(innerHTML); } } onTextAreaBlur() { /** save selection if focussed out */ this._commandExecutor.savedSelection = saveSelection(); if (typeof this.onTouched === 'function') { this.onTouched(); } this.blur.emit('blur'); } /** * Executed when the image from the disc is selected * @param image uploaded file object */ onUploadImage(image) { this.uploadImage.emit(image); } /** * resizing text area * * @param offsetY vertical height of the eidtable portion of the editor */ resizeTextArea(offsetY) { let newHeight = parseInt(this.height, 10); newHeight += offsetY; this.height = newHeight + 'px'; this.textArea.nativeElement.style.height = this.height; } /** * editor actions, i.e., executes command from toolbar * * @param commandName name of the command to be executed */ executeCommand(commandName) { try { this._commandExecutor.execute(commandName); } catch (error) { this._messageService.sendMessage(error.message); } } /** * Write a new value to the element. * * @param value value to be executed when there is a change in contenteditable */ writeValue(value) { this.togglePlaceholder(value); if (value === null || value === undefined || value === '' || value === '<br>') { value = null; } this.refreshView(value); } /** * Set the function to be called * when the control receives a change event. * * @param fn a function */ registerOnChange(fn) { this.onChange = fn; } /** * Set the function to be called * when the control receives a touch event. * * @param fn a function */ registerOnTouched(fn) { this.onTouched = fn; } /** * refresh view/HTML of the editor * * @param value html string from the editor */ refreshView(value) { const normalizedValue = value === null ? '' : value; this._renderer.setProperty(this.textArea.nativeElement, 'innerHTML', normalizedValue); } /** * toggles placeholder based on input string * * @param value A HTML string from the editor */ togglePlaceholder(value) { if (!value || value === '<br>' || value === '') { this._renderer.addClass(this.ngxWrapper.nativeElement, 'show-placeholder'); } else { this._renderer.removeClass(this.ngxWrapper.nativeElement, 'show-placeholder'); } } /** * returns a json containing input params */ getCollectiveParams() { return { editable: this.editable, spellcheck: this.spellcheck, placeholder: this.placeholder, translate: this.translate, height: this.height, minHeight: this.minHeight, width: this.width, minWidth: this.minWidth, enableToolbar: this.enableToolbar, showToolbar: this.showToolbar, imageEndPoint: this.imageEndPoint, toolbar: this.toolbar }; } ngOnInit() { /** * set configuration */ this.config = this.Utils.getEditorConfiguration(this.config, ngxTextEditorConfig, this.getCollectiveParams()); this.height = this.height || this.textArea.nativeElement.offsetHeight; this.executeCommand('enableObjectResizing'); } } NgxTextEditorComponent.decorators = [ { type: Component, args: [{ selector: 'ngx-text-editor', template: "<div class=\"ngx-text-editor\"\n id=\"ngxTextEditor\"\n [style.width]=\"config['width']\"\n [style.minWidth]=\"config['minWidth']\"\n tabindex=\"0\"\n (focus)=\"onEditorFocus()\">\n\n <ngx-text-editor-toolbar [config]=\"config\"\n (execute)=\"executeCommand($event)\"\n (uploadImage)=\"onUploadImage($event)\"></ngx-text-editor-toolbar>\n\n <div #ngxWrapper\n class=\"ngx-wrapper\">\n <textarea #ngxTextArea\n class=\"ngx-text-editor-textarea\"\n [placeholder]=\"placeholder || config['placeholder']\"\n [attr.contenteditable]=\"config['editable']\"\n (input)=\"onContentChange($event)\"\n [attr.translate]=\"config['translate']\"\n [attr.spellcheck]=\"config['spellcheck']\"\n [style.height]=\"config['height']\"\n [style.minHeight]=\"config['minHeight']\"\n [style.resize]=\"Utils?.canResize(resizer)\"\n (focus)=\"onTextAreaFocus()\"\n (blur)=\"onTextAreaBlur()\"></textarea>\n </div>\n\n <ngx-text-editor-message></ngx-text-editor-message>\n\n <ngx-grippie *ngIf=\"resizer === 'stack'\"></ngx-grippie>\n</div>\n", providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NgxTextEditorComponent), multi: true }], styles: [".ngx-text-editor{position:relative}.ngx-text-editor ::ng-deep [contenteditable=true]:empty:before{content:attr(placeholder);display:block;color:#868e96;opacity:1}.ngx-text-editor .ngx-wrapper{position:relative}.ngx-text-editor .ngx-wrapper .ngx-text-editor-textarea{min-height:5rem;padding:.5rem .8rem 1rem;border:1px solid #ddd;background-color:transparent;overflow-x:hidden;overflow-y:auto;z-index:2;position:relative;width:100%;max-width:100%;min-width:100%}.ngx-text-editor .ngx-wrapper .ngx-text-editor-textarea:focus,.ngx-text-editor .ngx-wrapper .ngx-text-editor-textarea.focus{outline:0}.ngx-text-editor .ngx-wrapper .ngx-text-editor-textarea ::ng-deep blockquote{margin-left:1rem;border-left:.2em solid #dfe2e5;padding-left:.5rem}.ngx-text-editor .ngx-wrapper ::ng-deep p{margin-bottom:0}.ngx-text-editor .ngx-wrapper .ngx-text-editor-placeholder{display:none;position:absolute;top:0;padding:.5rem .8rem 1rem .9rem;z-index:1;color:#6c757d;opacity:1}.ngx-text-editor .ngx-wrapper.show-placeholder .ngx-text-editor-placeholder{display:block}\n"] },] } ]; NgxTextEditorComponent.ctorParameters = () => [ { type: MessageService }, { type: CommandExecutorService }, { type: Renderer2 } ]; NgxTextEditorComponent.propDecorators = { editable: [{ type: Input }], spellcheck: [{ type: Input }], placeholder: [{ type: Input }], translate: [{ type: Input }], height: [{ type: Input }], minHeight: [{ type: Input }], width: [{ type: Input }], minWidth: [{ type: Input }], toolbar: [{ type: Input }], resizer: [{ type: Input }], config: [{ type: Input }], showToolbar: [{ type: Input }], enableToolbar: [{ type: Input }], imageEndPoint: [{ type: Input }], blur: [{ type: Output }], focus: [{ type: Output }], uploadImage: [{ type: Output }], textArea: [{ type: ViewChild, args: ['ngxTextArea', { static: true },] }], ngxWrapper: [{ type: ViewChild, args: ['ngxWrapper', { static: true },] }] }; class NgxGrippieComponent { /** * Constructor * * @param _editorComponent Editor component */ constructor(_editorComponent) { this._editorComponent = _editorComponent; /** previous value befor resizing the editor */ this.oldY = 0; /** set to true on mousedown event */ this.grabber = false; } /** * * @param event Mouseevent * * Update the height of the editor when the grabber is dragged */ onMouseMove(event) { if (!this.grabber) { return; } this._editorComponent.resizeTextArea(event.clientY - this.oldY); this.oldY = event.clientY; } /** * * @param event Mouseevent * * set the grabber to false on mouse up action */ onMouseUp(event) { this.grabber = false; } onResize(event, resizer) { this.grabber = true; this.oldY = event.clientY; event.preventDefault(); } } NgxGrippieComponent.decorators = [ { type: Component, args: [{ selector: 'ngx-grippie', template: "<div class=\"ngx-text-editor-grippie\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" style=\"isolation:isolate\" viewBox=\"651.6 235 26 5\"\n width=\"26\" height=\"5\">\n <g id=\"sprites\">\n <path d=\" M 651.6 235 L 653.6 235 L 653.6 237 L 651.6 237 M 654.6 238 L 656.6 238 L 656.6 240 L 654.6 240 M 660.6 238 L 662.6 238 L 662.6 240 L 660.6 240 M 666.6 238 L 668.6 238 L 668.6 240 L 666.6 240 M 672.6 238 L 674.6 238 L 674.6 240 L 672.6 240 M 657.6 235 L 659.6 235 L 659.6 237 L 657.6 237 M 663.6 235 L 665.6 235 L 665.6 237 L 663.6 237 M 669.6 235 L 671.6 235 L 671.6 237 L 669.6 237 M 675.6 235 L 677.6 235 L 677.6 237 L 675.6 237\"\n fill=\"rgb(147,153,159)\" />\n </g>\n </svg>\n</div>\n", styles: [".ngx-text-editor-grippie{height:9px;background-color:#f1f1f1;position:relative;text-align:center;cursor:s-resize;border:1px solid #ddd;border-top:transparent}.ngx-text-editor-grippie svg{position:absolute;top:1.5px;width:50%;right:25%}\n"] },] } ]; NgxGrippieComponent.ctorParameters = () => [ { type: NgxTextEditorComponent } ]; NgxGrippieComponent.propDecorators = { onMouseMove: [{ type: HostListener, args: ['document:mousemove', ['$event'],] }], onMouseUp: [{ type: HostListener, args: ['document:mouseup', ['$event'],] }], onResize: [{ type: HostListener, args: ['mousedown', ['$event'],] }] }; class NgxTextEditorMessageComponent { /** * @param _messageService service to send message to the editor */ constructor(_messageService) { this._messageService = _messageService; /** property that holds the message to be displayed on the editor */ this.ngxMessage = undefined; this._messageService.getMessage().subscribe((message) => this.ngxMessage = message); } /** * clears editor message */ clearMessage() { this.ngxMessage = undefined; } } NgxTextEditorMessageComponent.decorators = [ { type: Component, args: [{ selector: 'ngx-text-editor-message', template: "<div class=\"ngx-text-editor-message\" *ngIf=\"ngxMessage\" (dblclick)=\"clearMessage()\">\n {{ ngxMessage }}\n</div>\n", styles: [".ngx-text-editor-message{font-size:80%;background-color:#f1f1f1;border:1px solid #ddd;border-top:transparent;padding:0 .5rem .1rem;transition:.5s ease-in}\n"] },] } ]; NgxTextEditorMessageComponent.ctorParameters = () => [ { type: MessageService } ]; class NgxTextEditorToolbarComponent { constructor(_popOverConfig, _formBuilder, _messageService, _commandExecutorService) { this._popOverConfig = _popOverConfig; this._formBuilder = _formBuilder; this._messageService = _messageService; this._commandExecutorService = _commandExecutorService; /** set to false when image is being uploaded */ this.uploadComplete = true; /** upload percentage */ this.updloadPercentage = 0; /** set to true when the image is being uploaded */ this.isUploading = false; /** which tab to active for color insertion */ this.selectedColorTab = 'textColor'; /** font family name */ this.fontName = ''; /** font size */ this.fontSize = ''; /** hex color code */ this.hexColor = ''; /** show/hide image uploader */ this.isImageUploader = false; this.wasH1Pressed = false; /** * Emits an event when a toolbar button is clicked */ this.execute = new EventEmitter(); /** * Emits an event then an image is selected */ this.uploadImage = new EventEmitter(); this._popOverConfig.outsideClick = true; this._popOverConfig.placement = 'bottom'; this._popOverConfig.container = 'body'; } /** * enable or diable toolbar based on configuration * * @param value name of the toolbar buttons */ canEnableToolbarOptions(value) { return canEnableToolbarOptions(value, this.config['toolbar']); } /** * triggers command from the toolbar to be executed and emits an event * * @param command name of the command to be executed */ triggerCommand(command) { this.execute.emit(command); } /** * create URL insert form */ buildUrlForm() { this.urlForm = this._formBuilder.group({ urlLink: ['', [Validators.required]], urlText: ['', [Validators.required]], urlNewTab: [true] }); } /** * inserts link in the editor */ insertLink() { try { this._commandExecutorService.createLink(this.urlForm.value); } catch (error) { this._messageService.sendMessage(error.message); } /** reset form to default */ this.buildUrlForm(); /** close inset URL pop up */ this.urlPopover.hide(); } /** * create insert image form */ buildImageForm() { this.imageForm = this._formBuilder.group({ imageUrl: ['', [Validators.required]] }); } /** * create insert image form */ buildVideoForm() { this.videoForm = this._formBuilder.group({ videoUrl: ['', [Validators.required]], height: [''], width: [''] }); } /** * Executed when file is selected * * @param e onChange event */ onFileChange(e) { this.uploadComplete = false; this.isUploading = true; if (e.target.files.length > 0) { const file = e.target.files[0]; if (!this.config.imageEndPoint) { this.uploadImage.emit(file); this.uploadComplete = true; this.isUploading = false; } else { try { this._commandExecutorService.uploadImage(file, this.config.imageEndPoint).subscribe(event => { if (event.type) { this.updloadPercentage = Math.round(100 * event.loaded / event.total); } if (event instanceof HttpResponse) { try { this._commandExecutorService.insertImage(event.body.url); } catch (error) { this._messageService.sendMessage(error.message); } this.uploadComplete = true; this.isUploading = false; } }); } catch (error) { this._messageService.sendMessage(error.message); this.uploadComplete = true; this.isUploading = false; } } } } /** insert image in the editor */ insertImage() { try { this._commandExecutorService.insertImage(this.imageForm.value.imageUrl); } catch (error) { this._messageService.sendMessage(error.message); } /** reset form to default */ this.buildImageForm(); /** close inset URL pop up */ this.imagePopover.hide(); } /** insert image in the editor */ insertVideo() { try { this._commandExecutorService.insertVideo(this.videoForm.value); } catch (error) { this._messageService.sendMessage(error.message); } /** reset form to default */ this.buildVideoForm(); /** close inset URL pop up */ this.videoPopover.hide(); } /** inser text/background color */ insertColor(color, where) { try { this._commandExecutorService.insertColor(color, where); } catch (error) { this._messageService.sendMessage(error.message); } this.colorPopover.hide(); } /** set font size */ setFontSize(fontSize) { try { this._commandExecutorService.setFontSize(fontSize); } catch (error) { this._messageService.sendMessage(error.message); } this.fontSizePopover.hide(); } /** set font Name/family */ setFontName(fontName) { try { this._commandExecutorService.setFontName(fontName); } catch (error) { this._messageService.sendMessage(error.message); } this.fontSizePopover.hide(); } ngOnInit() { this.buildUrlForm(); this.buildImageForm(); this.buildVideoForm(); } toggleHeading(heading) { if (!this.wasH1Pressed) { this.execute.emit(heading); } else { this.execute.emit('clear'); } this.wasH1Pressed = !this.wasH1Pressed; } } NgxTextEditorToolbarComponent.decorators = [ { type: Component, args: [{ selector: 'ngx-text-editor-toolbar', template: "<div class=\"ngx-toolbar\" *ngIf=\"config['showToolbar']\">\n <div class=\"ngx-toolbar-set\">\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('h1')\" (click)=\"toggleHeading('h1')\"\n title=\"H1\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-header\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('h2')\" (click)=\"toggleHeading('h2')\"\n title=\"H2\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-header\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('h3')\" (click)=\"toggleHeading('h3')\"\n title=\"H3\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-header\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('bold')\" (click)=\"triggerCommand('bold')\"\n title=\"{{config.toolbarTitle['bold']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-bold\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('italic')\" (click)=\"triggerCommand('italic')\"\n title=\"{{config.toolbarTitle['italic']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-italic\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('underline')\" (click)=\"triggerCommand('underline')\"\n title=\"{{config.toolbarTitle['underline']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-underline\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('strikeThrough')\" (click)=\"triggerCommand('strikeThrough')\"\n title=\"{{config.toolbarTitle['strikeThrough']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-strikethrough\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('superscript')\" (click)=\"triggerCommand('superscript')\"\n title=\"{{config.toolbarTitle['superscript']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-superscript\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('subscript')\" (click)=\"triggerCommand('subscript')\"\n title=\"{{config.toolbarTitle['subscript']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-subscript\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <div class=\"ngx-toolbar-set\">\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('fontName')\" (click)=\"fontName = ''\"\n title=\"{{config.toolbarTitle['fontName']}}\" [popover]=\"fontNameTemplate\" #fontNamePopover=\"bs-popover\" containerClass=\"ngxePopover\"\n [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-font\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('fontSize')\" (click)=\"fontSize = ''\"\n title=\"{{config.toolbarTitle['fontSize']}}\" [popover]=\"fontSizeTemplate\" #fontSizePopover=\"bs-popover\" containerClass=\"ngxePopover\"\n [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-text-height\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('color')\" (click)=\"hexColor = ''\"\n title=\"{{config.toolbarTitle['color']}}\" [popover]=\"insertColorTemplate\" #colorPopover=\"bs-popover\" containerClass=\"ngxePopover\"\n [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-tint\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <div class=\"ngx-toolbar-set\">\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('justifyLeft')\" (click)=\"triggerCommand('justifyLeft')\"\n title=\"{{config.toolbarTitle['justifyLeft']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-align-left\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('justifyCenter')\" (click)=\"triggerCommand('justifyCenter')\"\n title=\"{{config.toolbarTitle['justifyCenter']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-align-center\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('justifyRight')\" (click)=\"triggerCommand('justifyRight')\"\n title=\"{{config.toolbarTitle['justifyRight']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-align-right\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('justifyFull')\" (click)=\"triggerCommand('justifyFull')\"\n title=\"{{config.toolbarTitle['justifyFull']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-align-justify\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('indent')\" (click)=\"triggerCommand('indent')\"\n title=\"{{config.toolbarTitle['indent']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-indent\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('outdent')\" (click)=\"triggerCommand('outdent')\"\n title=\"{{config.toolbarTitle['outdent']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-outdent\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <div class=\"ngx-toolbar-set\">\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('cut')\" (click)=\"triggerCommand('cut')\"\n title=\"{{config.toolbarTitle['cut']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-scissors\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('copy')\" (click)=\"triggerCommand('copy')\"\n title=\"{{config.toolbarTitle['copy']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-files-o\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('delete')\" (click)=\"triggerCommand('delete')\"\n title=\"{{config.toolbarTitle['delete']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-trash\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('removeFormat')\" (click)=\"triggerCommand('removeFormat')\"\n title=\"{{config.toolbarTitle['removeFormat']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-eraser\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('undo')\" (click)=\"triggerCommand('undo')\"\n title=\"{{config.toolbarTitle['undo']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-undo\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('redo')\" (click)=\"triggerCommand('redo')\"\n title=\"{{config.toolbarTitle['redo']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-repeat\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <div class=\"ngx-toolbar-set\">\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('paragraph')\" (click)=\"triggerCommand('insertParagraph')\"\n title=\"{{config.toolbarTitle['paragraph']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-paragraph\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('blockquote')\" (click)=\"triggerCommand('blockquote')\"\n title=\"{{config.toolbarTitle['blockquote']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-quote-left\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('removeBlockquote')\" (click)=\"triggerCommand('removeBlockquote')\"\n title=\"{{config.toolbarTitle['removeBlockquote']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-quote-right\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('horizontalLine')\" (click)=\"triggerCommand('insertHorizontalRule')\"\n title=\"{{config.toolbarTitle['horizontalLine']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-minus\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('unorderedList')\" (click)=\"triggerCommand('insertUnorderedList')\"\n title=\"{{config.toolbarTitle['unorderedList']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-list-ul\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('orderedList')\" (click)=\"triggerCommand('insertOrderedList')\"\n title=\"{{config.toolbarTitle['orderedList']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-list-ol\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <div class=\"ngx-toolbar-set\">\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('link')\" (click)=\"buildUrlForm()\"\n [popover]=\"insertLinkTemplate\" title=\"{{config.toolbarTitle['link']}}\" #urlPopover=\"bs-popover\" containerClass=\"ngxePopover\"\n [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-link\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('unlink')\" (click)=\"triggerCommand('unlink')\"\n title=\"{{config.toolbarTitle['unlink']}}\" [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-chain-broken\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('image')\" (click)=\"buildImageForm()\"\n title=\"{{config.toolbarTitle['image']}}\" [popover]=\"insertImageTemplate\" #imagePopover=\"bs-popover\" containerClass=\"ngxePopover\"\n [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-picture-o\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"ngx-text-editor-button\" *ngIf=\"canEnableToolbarOptions('video')\" (click)=\"buildVideoForm()\"\n title=\"{{config.toolbarTitle['video']}}\" [popover]=\"insertVideoTemplate\" #videoPopover=\"bs-popover\" containerClass=\"ngxePopover\"\n [disabled]=\"!config['enableToolbar']\">\n <i class=\"fa fa-youtube-play\" aria-hidden=\"true\"></i>\n </button>\n </div>\n</div>\n\n<!-- URL Popover template -->\n<ng-template #insertLinkTemplate>\n <div class=\"ngxe-popover extra-gt\">\n <form [formGroup]=\"urlForm\" (ngSubmit)=\"urlForm.valid && insertLink()\" autocomplete=\"off\">\n <div class=\"form-group\">\n <label for=\"urlInput\" class=\"small\">{{config.toolbarTitle['URLInput']}}</label>\n <input type=\"text\" class=\"form-control-sm\" id=\"URLInput\" placeholder=\"{{config.toolbarTitle['URLInput']}}\" formControlName=\"urlLink\" required>\n </div>\n <div class=\"form-group\">\n <label for=\"urlTextInput\" class=\"small\">{{config.toolbarTitle['urlTextInput']}}</label>\n <input type=\"text\" class=\"form-control-sm\" id=\"urlTextInput\" placeholder=\"{{config.toolbarTitle['urlTextInput']}}\" formControlName=\"urlText\"\n required>\n </div>\n <div class=\"form-check\">\n <input type=\"checkbox\" class=\"form-check-input\" id=\"urlNewTab\" formControlName=\"urlNewTab\">\n <label class=\"form-check-label\" for=\"urlNewTab\">{{config.toolbarTitle['urlNewTab']}}</label>\n </div>\n <button type=\"submit\" class=\"btn-primary btn-sm btn\">{{config.toolbarTitle['linkSubmit']}}</button>\n </form>\n </div>\n</ng-template>\n\n<!-- Image Uploader Popover template -->\n<ng-template #insertImageTemplate>\n <div class=\"ngxe-popover imgc-ctnr\">\n <div class=\"imgc-topbar btn-ctnr\">\n <button type=\"button\" class=\"btn\" [ngClass]=\"{active: isImageUploader}\" (click)=\"isImageUploader = true\">\n <i class=\"fa fa-upload\"></i>\n </button>\n <button type=\"button\" class=\"btn\" [ngClass]=\"{active: !isImageUploader}\" (click)=\"isImageUploader = false\">\n <i class=\"fa fa-link\"></i>\n </button>\n </div>\n <div class=\"imgc-ctnt is-image\">\n <div *ngIf=\"isImageUploader; else insertImageLink\"> </div>\n <div *ngIf=\"!isImageUploader; else imageUploder\"> </div>\n <ng-template #imageUploder>\n <div c