ngx-text-editor
Version:
WYSIWYG Editor for Angular 2+
1,024 lines (1,015 loc) • 68.8 kB
JavaScript
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