ngx-quill
Version:
An angular (>= v2) component for the easy use of the QuillJS richt text editor.
575 lines (567 loc) • 19.5 kB
JavaScript
import { isPlatformServer, DOCUMENT } from '@angular/common';
import { DomSanitizer } from '@angular/platform-browser';
import { InjectionToken, Component, ElementRef, EventEmitter, forwardRef, Inject, Input, NgZone, Output, PLATFORM_ID, Renderer2, SecurityContext, ViewEncapsulation, NgModule } from '@angular/core';
import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
const defaultModules = {
toolbar: [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ header: 1 }, { header: 2 }],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ script: 'sub' }, { script: 'super' }],
[{ indent: '-1' }, { indent: '+1' }],
[{ direction: 'rtl' }],
[{ size: ['small', false, 'large', 'huge'] }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[
{ color: [] },
{ background: [] }
],
[{ font: [] }],
[{ align: [] }],
['clean'],
['link', 'image', 'video'] // link and image, video
]
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
const QUILL_CONFIG_TOKEN = new InjectionToken('config');
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
// tslint:disable-next-line:variable-name
/** @type {?} */
let Quill = null;
class QuillEditorComponent {
// used to store initial value before ViewInit
/**
* @param {?} elementRef
* @param {?} domSanitizer
* @param {?} doc
* @param {?} platformId
* @param {?} renderer
* @param {?} zone
* @param {?} config
*/
constructor(elementRef, domSanitizer, doc, platformId, renderer, zone, config) {
this.elementRef = elementRef;
this.domSanitizer = domSanitizer;
this.doc = doc;
this.platformId = platformId;
this.renderer = renderer;
this.zone = zone;
this.config = config;
this.format = 'html';
this.required = false;
this.customToolbarPosition = 'top';
this.sanitize = false;
this.style = null;
this.strict = true;
this.customOptions = [];
this.onEditorCreated = new EventEmitter();
this.onContentChanged = new EventEmitter();
this.onSelectionChanged = new EventEmitter();
this.disabled = false; // used to store initial value before ViewInit
this.valueGetter = (/**
* @param {?} quillEditor
* @param {?} editorElement
* @return {?}
*/
(quillEditor, editorElement) => {
/** @type {?} */
let html = editorElement.children[0].innerHTML;
if (html === '<p><br></p>' || html === '<div><br><div>') {
html = null;
}
/** @type {?} */
let modelValue = html;
if (this.format === 'text') {
modelValue = quillEditor.getText();
}
else if (this.format === 'object') {
modelValue = quillEditor.getContents();
}
else if (this.format === 'json') {
try {
modelValue = JSON.stringify(quillEditor.getContents());
}
catch (e) {
modelValue = quillEditor.getText();
}
}
return modelValue;
});
this.valueSetter = (/**
* @param {?} quillEditor
* @param {?} value
* @return {?}
*/
(quillEditor, value) => {
if (this.format === 'html') {
if (this.sanitize) {
value = this.domSanitizer.sanitize(SecurityContext.HTML, value);
}
return quillEditor.clipboard.convert(value);
}
else if (this.format === 'json') {
try {
return JSON.parse(value);
}
catch (e) {
return [{ insert: value }];
}
}
return value;
});
this.selectionChangeHandler = (/**
* @param {?} range
* @param {?} oldRange
* @param {?} source
* @return {?}
*/
(range, oldRange, source) => {
this.zone.run((/**
* @return {?}
*/
() => {
this.onSelectionChanged.emit({
editor: this.quillEditor,
oldRange,
range,
source
});
if (!range && this.onModelTouched) {
this.onModelTouched();
}
}));
});
this.textChangeHandler = (/**
* @param {?} delta
* @param {?} oldDelta
* @param {?} source
* @return {?}
*/
(delta, oldDelta, source) => {
// only emit changes emitted by user interactions
// only emit changes emitted by user interactions
/** @type {?} */
const text = this.quillEditor.getText();
/** @type {?} */
const content = this.quillEditor.getContents();
/** @type {?} */
let html = (/** @type {?} */ (this.editorElem)).children[0].innerHTML;
if (html === '<p><br></p>' || html === '<div><br><div>') {
html = null;
}
this.zone.run((/**
* @return {?}
*/
() => {
/** @type {?} */
const trackChanges = this.trackChanges || this.config.trackChanges;
if ((source === Quill.sources.USER || trackChanges && trackChanges === 'all') && this.onModelChange) {
this.onModelChange(this.valueGetter(this.quillEditor, (/** @type {?} */ (this.editorElem))));
}
this.onContentChanged.emit({
content,
delta,
editor: this.quillEditor,
html,
oldDelta,
source,
text
});
}));
});
}
// tslint:disable-next-line:no-empty
/**
* @param {?=} _modelValue
* @return {?}
*/
onModelChange(_modelValue) { }
// tslint:disable-next-line:no-empty
/**
* @return {?}
*/
onModelTouched() { }
/**
* @return {?}
*/
ngAfterViewInit() {
if (isPlatformServer(this.platformId)) {
return;
}
if (!Quill) {
Quill = require('quill');
}
if (this.customToolbarPosition === 'top') {
this.elementRef.nativeElement.insertAdjacentHTML('beforeend', '<div quill-editor-element></div>');
}
else {
this.elementRef.nativeElement.insertAdjacentHTML('afterbegin', '<div quill-editor-element></div>');
}
this.editorElem = this.elementRef.nativeElement.querySelector('[quill-editor-element]');
/** @type {?} */
const toolbarElem = this.elementRef.nativeElement.querySelector('[quill-editor-toolbar]');
/** @type {?} */
const modules = this.modules || (this.config.modules || defaultModules);
if (modules.toolbar === undefined) {
modules.toolbar = defaultModules.toolbar;
}
/** @type {?} */
let placeholder = this.placeholder !== undefined ? this.placeholder : this.config.placeholder;
if (placeholder === undefined) {
placeholder = 'Insert text here ...';
}
if (toolbarElem) {
// tslint:disable-next-line:no-string-literal
modules['toolbar'] = toolbarElem;
}
if (this.style) {
Object.keys(this.style).forEach((/**
* @param {?} key
* @return {?}
*/
(key) => {
this.renderer.setStyle(this.editorElem, key, this.style[key]);
}));
}
this.customOptions.forEach((/**
* @param {?} customOption
* @return {?}
*/
(customOption) => {
/** @type {?} */
const newCustomOption = Quill.import(customOption.import);
newCustomOption.whitelist = customOption.whitelist;
Quill.register(newCustomOption, true);
}));
/** @type {?} */
let bounds = this.bounds && this.bounds === 'self' ? this.editorElem : this.bounds;
if (!bounds) {
bounds = this.config.bounds ? this.config.bounds : this.doc.body;
}
/** @type {?} */
let debug = this.debug;
if (!debug && debug !== false && this.config.debug) {
debug = this.config.debug;
}
/** @type {?} */
let readOnly = this.readOnly;
if (!readOnly && this.readOnly !== false) {
readOnly = this.config.readOnly !== undefined ? this.config.readOnly : false;
}
/** @type {?} */
let scrollingContainer = this.scrollingContainer;
if (!scrollingContainer && this.scrollingContainer !== null) {
scrollingContainer = this.config.scrollingContainer === null || this.config.scrollingContainer ? this.config.scrollingContainer : null;
}
/** @type {?} */
let formats = this.formats;
if (!formats && formats === undefined) {
formats = this.config.formats || this.config.formats === null ? this.config.formats : undefined;
}
this.quillEditor = new Quill(this.editorElem, {
bounds,
debug,
formats,
modules,
placeholder,
readOnly,
scrollingContainer,
strict: this.strict,
theme: this.theme || (this.config.theme ? this.config.theme : 'snow')
});
if (this.content) {
if (this.format === 'object') {
this.quillEditor.setContents(this.content, 'silent');
}
else if (this.format === 'text') {
this.quillEditor.setText(this.content, 'silent');
}
else if (this.format === 'json') {
try {
this.quillEditor.setContents(JSON.parse(this.content), 'silent');
}
catch (e) {
this.quillEditor.setText(this.content, 'silent');
}
}
else {
if (this.sanitize) {
this.content = this.domSanitizer.sanitize(SecurityContext.HTML, this.content);
}
/** @type {?} */
const contents = this.quillEditor.clipboard.convert(this.content);
this.quillEditor.setContents(contents, 'silent');
}
this.quillEditor.history.clear();
}
// initialize disabled status based on this.disabled as default value
this.setDisabledState();
this.onEditorCreated.emit(this.quillEditor);
// mark model as touched if editor lost focus
this.quillEditor.on('selection-change', this.selectionChangeHandler);
// update model if text changes
this.quillEditor.on('text-change', this.textChangeHandler);
}
/**
* @return {?}
*/
ngOnDestroy() {
if (this.quillEditor) {
this.quillEditor.off('selection-change', this.selectionChangeHandler);
this.quillEditor.off('text-change', this.textChangeHandler);
}
}
/**
* @param {?} changes
* @return {?}
*/
ngOnChanges(changes) {
if (!this.quillEditor) {
return;
}
// tslint:disable:no-string-literal
if (changes['readOnly']) {
this.quillEditor.enable(!changes['readOnly'].currentValue);
}
if (changes['placeholder']) {
this.quillEditor.root.dataset.placeholder =
changes['placeholder'].currentValue;
}
if (changes['style']) {
/** @type {?} */
const currentStyling = changes['style'].currentValue;
/** @type {?} */
const previousStyling = changes['style'].previousValue;
if (previousStyling) {
Object.keys(previousStyling).forEach((/**
* @param {?} key
* @return {?}
*/
(key) => {
this.renderer.removeStyle(this.editorElem, key);
}));
}
if (currentStyling) {
Object.keys(currentStyling).forEach((/**
* @param {?} key
* @return {?}
*/
(key) => {
this.renderer.setStyle(this.editorElem, key, this.style[key]);
}));
}
}
// tslint:enable:no-string-literal
}
/**
* @param {?} currentValue
* @return {?}
*/
writeValue(currentValue) {
this.content = currentValue;
if (this.quillEditor) {
if (currentValue) {
if (this.format === 'text') {
this.quillEditor.setText(currentValue);
}
else {
this.quillEditor.setContents(this.valueSetter(this.quillEditor, this.content));
}
return;
}
this.quillEditor.setText('');
}
}
/**
* @param {?=} isDisabled
* @return {?}
*/
setDisabledState(isDisabled = this.disabled) {
// store initial value to set appropriate disabled status after ViewInit
this.disabled = isDisabled;
if (this.quillEditor) {
if (isDisabled) {
this.quillEditor.disable();
this.renderer.setAttribute(this.elementRef.nativeElement, 'disabled', 'disabled');
}
else {
if (!this.readOnly) {
this.quillEditor.enable();
}
this.renderer.removeAttribute(this.elementRef.nativeElement, 'disabled');
}
}
}
/**
* @param {?} fn
* @return {?}
*/
registerOnChange(fn) {
this.onModelChange = fn;
}
/**
* @param {?} fn
* @return {?}
*/
registerOnTouched(fn) {
this.onModelTouched = fn;
}
/**
* @return {?}
*/
validate() {
if (!this.quillEditor) {
return null;
}
/** @type {?} */
const err = {};
/** @type {?} */
let valid = true;
/** @type {?} */
const textLength = this.quillEditor.getText().trim().length;
if (this.minLength && textLength && textLength < this.minLength) {
err.minLengthError = {
given: textLength,
minLength: this.minLength
};
valid = false;
}
if (this.maxLength && textLength > this.maxLength) {
err.maxLengthError = {
given: textLength,
maxLength: this.maxLength
};
valid = false;
}
if (this.required && !textLength) {
err.requiredError = {
empty: true
};
valid = false;
}
return valid ? null : err;
}
}
QuillEditorComponent.decorators = [
{ type: Component, args: [{
encapsulation: ViewEncapsulation.None,
providers: [
{
multi: true,
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef((/**
* @return {?}
*/
() => QuillEditorComponent))
},
{
multi: true,
provide: NG_VALIDATORS,
useExisting: forwardRef((/**
* @return {?}
*/
() => QuillEditorComponent))
}
],
selector: 'quill-editor',
template: `
<ng-content select="[quill-editor-toolbar]"></ng-content>
`
}] }
];
/** @nocollapse */
QuillEditorComponent.ctorParameters = () => [
{ type: ElementRef },
{ type: DomSanitizer },
{ type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
{ type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] },
{ type: Renderer2 },
{ type: NgZone },
{ type: undefined, decorators: [{ type: Inject, args: [QUILL_CONFIG_TOKEN,] }] }
];
QuillEditorComponent.propDecorators = {
format: [{ type: Input }],
theme: [{ type: Input }],
modules: [{ type: Input }],
debug: [{ type: Input }],
readOnly: [{ type: Input }],
placeholder: [{ type: Input }],
maxLength: [{ type: Input }],
minLength: [{ type: Input }],
required: [{ type: Input }],
formats: [{ type: Input }],
customToolbarPosition: [{ type: Input }],
sanitize: [{ type: Input }],
style: [{ type: Input }],
strict: [{ type: Input }],
scrollingContainer: [{ type: Input }],
bounds: [{ type: Input }],
customOptions: [{ type: Input }],
trackChanges: [{ type: Input }],
onEditorCreated: [{ type: Output }],
onContentChanged: [{ type: Output }],
onSelectionChanged: [{ type: Output }],
valueGetter: [{ type: Input }],
valueSetter: [{ type: Input }]
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
const ɵ0 = { modules: defaultModules };
class QuillModule {
/**
* @param {?=} config
* @return {?}
*/
static forRoot(config) {
return {
ngModule: QuillModule,
providers: [
{
provide: QUILL_CONFIG_TOKEN,
// tslint:disable-next-line:only-arrow-functions
useValue: config || { modules: defaultModules }
}
]
};
}
}
QuillModule.decorators = [
{ type: NgModule, args: [{
declarations: [
QuillEditorComponent
],
exports: [QuillEditorComponent],
imports: [],
providers: [
{
provide: QUILL_CONFIG_TOKEN,
useValue: ɵ0
}
]
},] }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
export { QuillModule, QuillEditorComponent, QUILL_CONFIG_TOKEN, defaultModules as ɵa };
//# sourceMappingURL=ngx-quill.js.map