ngx-quill
Version:
An angular (>= v2) component for the easy use of the QuillJS richt text editor.
745 lines (737 loc) • 26.9 kB
JavaScript
import { __decorate, __metadata, __param } from 'tslib';
import { isPlatformServer, DOCUMENT, CommonModule } from '@angular/common';
import { InjectionToken, EventEmitter, SecurityContext, Input, Output, Component, ViewEncapsulation, forwardRef, Inject, PLATFORM_ID, ElementRef, Renderer2, NgZone, NgModule } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
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
]
};
const QUILL_CONFIG_TOKEN = new InjectionToken('config');
var QuillEditorComponent_1;
// tslint:disable-next-line:variable-name
let Quill = null;
const getFormat = (format, configFormat) => {
const passedFormat = format || configFormat;
return passedFormat || 'html';
};
const ɵ0 = getFormat;
let QuillEditorComponent = QuillEditorComponent_1 = class QuillEditorComponent {
constructor(elementRef, domSanitizer, doc,
// tslint:disable-next-line:ban-types
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.required = false;
this.customToolbarPosition = 'top';
this.sanitize = false;
this.styles = null;
this.strict = true;
this.customOptions = [];
this.preserveWhitespace = false;
this.onEditorCreated = new EventEmitter();
this.onEditorChanged = new EventEmitter();
this.onContentChanged = new EventEmitter();
this.onSelectionChanged = new EventEmitter();
this.onFocus = new EventEmitter();
this.onBlur = new EventEmitter();
this.disabled = false; // used to store initial value before ViewInit
this.valueGetter = (quillEditor, editorElement) => {
let html = editorElement.querySelector('.ql-editor').innerHTML;
if (html === '<p><br></p>' || html === '<div><br><div>') {
html = null;
}
let modelValue = html;
const format = getFormat(this.format, this.config.format);
if (format === 'text') {
modelValue = quillEditor.getText();
}
else if (format === 'object') {
modelValue = quillEditor.getContents();
}
else if (format === 'json') {
try {
modelValue = JSON.stringify(quillEditor.getContents());
}
catch (e) {
modelValue = quillEditor.getText();
}
}
return modelValue;
};
this.valueSetter = (quillEditor, value) => {
const format = getFormat(this.format, this.config.format);
if (format === 'html') {
if (this.sanitize) {
value = this.domSanitizer.sanitize(SecurityContext.HTML, value);
}
return quillEditor.clipboard.convert(value);
}
else if (format === 'json') {
try {
return JSON.parse(value);
}
catch (e) {
return [{ insert: value }];
}
}
return value;
};
this.selectionChangeHandler = (range, oldRange, source) => {
this.zone.run(() => {
if (range === null) {
this.onBlur.emit({
editor: this.quillEditor,
source
});
}
else if (oldRange === null) {
this.onFocus.emit({
editor: this.quillEditor,
source
});
}
this.onSelectionChanged.emit({
editor: this.quillEditor,
oldRange,
range,
source
});
if (!range && this.onModelTouched) {
this.onModelTouched();
}
});
};
this.textChangeHandler = (delta, oldDelta, source) => {
// only emit changes emitted by user interactions
const text = this.quillEditor.getText();
const content = this.quillEditor.getContents();
let html = this.editorElem.querySelector('.ql-editor').innerHTML;
if (html === '<p><br></p>' || html === '<div><br><div>') {
html = null;
}
this.zone.run(() => {
const trackChanges = this.trackChanges || this.config.trackChanges;
if ((source === Quill.sources.USER || trackChanges && trackChanges === 'all') && this.onModelChange) {
this.onModelChange(this.valueGetter(this.quillEditor, this.editorElem));
}
this.onContentChanged.emit({
content,
delta,
editor: this.quillEditor,
html,
oldDelta,
source,
text
});
});
};
this.editorChangeHandler = (event, current, old, source) => {
// only emit changes emitted by user interactions
if (event === 'text-change') {
const text = this.quillEditor.getText();
const content = this.quillEditor.getContents();
let html = this.editorElem.querySelector('.ql-editor').innerHTML;
if (html === '<p><br></p>' || html === '<div><br><div>') {
html = null;
}
this.zone.run(() => {
this.onEditorChanged.emit({
content,
delta: current,
editor: this.quillEditor,
event,
html,
oldDelta: old,
source,
text
});
});
}
else {
this.onEditorChanged.emit({
editor: this.quillEditor,
event,
oldRange: old,
range: current,
source
});
}
};
}
// tslint:disable-next-line:no-empty
onModelChange(_modelValue) { }
// tslint:disable-next-line:no-empty
onModelTouched() { }
ngAfterViewInit() {
if (isPlatformServer(this.platformId)) {
return;
}
if (!Quill) {
Quill = require('quill');
}
this.elementRef.nativeElement.insertAdjacentHTML(this.customToolbarPosition === 'top' ? 'beforeend' : 'afterbegin', this.preserveWhitespace ? '<pre quill-editor-element></pre>' : '<div quill-editor-element></div>');
this.editorElem = this.elementRef.nativeElement.querySelector('[quill-editor-element]');
const toolbarElem = this.elementRef.nativeElement.querySelector('[quill-editor-toolbar]');
const modules = Object.assign({}, this.modules || (this.config.modules || defaultModules));
if (toolbarElem) {
modules.toolbar = toolbarElem;
}
else if (modules.toolbar === undefined) {
modules.toolbar = defaultModules.toolbar;
}
let placeholder = this.placeholder !== undefined ? this.placeholder : this.config.placeholder;
if (placeholder === undefined) {
placeholder = 'Insert text here ...';
}
if (this.styles) {
Object.keys(this.styles).forEach((key) => {
this.renderer.setStyle(this.editorElem, key, this.styles[key]);
});
}
this.customOptions.forEach((customOption) => {
const newCustomOption = Quill.import(customOption.import);
newCustomOption.whitelist = customOption.whitelist;
Quill.register(newCustomOption, true);
});
let bounds = this.bounds && this.bounds === 'self' ? this.editorElem : this.bounds;
if (!bounds) {
bounds = this.config.bounds ? this.config.bounds : this.doc.body;
}
let debug = this.debug;
if (!debug && debug !== false && this.config.debug) {
debug = this.config.debug;
}
let readOnly = this.readOnly;
if (!readOnly && this.readOnly !== false) {
readOnly = this.config.readOnly !== undefined ? this.config.readOnly : false;
}
let scrollingContainer = this.scrollingContainer;
if (!scrollingContainer && this.scrollingContainer !== null) {
scrollingContainer = this.config.scrollingContainer === null || this.config.scrollingContainer ? this.config.scrollingContainer : null;
}
let formats = this.formats;
if (!formats && formats === undefined) {
formats = this.config.formats ? Object.assign({}, this.config.formats) : (this.config.formats === null ? null : 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) {
const format = getFormat(this.format, this.config.format);
if (format === 'object') {
this.quillEditor.setContents(this.content, 'silent');
}
else if (format === 'text') {
this.quillEditor.setText(this.content, 'silent');
}
else if (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);
}
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();
// triggered if selection or text changed
this.quillEditor.on('editor-change', this.editorChangeHandler);
// 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);
// trigger created in a timeout to avoid changed models after checked
// if you are using the editor api in created output to change the editor content
setTimeout(() => this.onEditorCreated.emit(this.quillEditor));
}
ngOnDestroy() {
if (this.quillEditor) {
this.quillEditor.off('selection-change', this.selectionChangeHandler);
this.quillEditor.off('text-change', this.textChangeHandler);
this.quillEditor.off('editor-change', this.editorChangeHandler);
}
}
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['styles']) {
const currentStyling = changes['styles'].currentValue;
const previousStyling = changes['styles'].previousValue;
if (previousStyling) {
Object.keys(previousStyling).forEach((key) => {
this.renderer.removeStyle(this.editorElem, key);
});
}
if (currentStyling) {
Object.keys(currentStyling).forEach((key) => {
this.renderer.setStyle(this.editorElem, key, this.styles[key]);
});
}
}
// tslint:enable:no-string-literal
}
writeValue(currentValue) {
this.content = currentValue;
const format = getFormat(this.format, this.config.format);
if (this.quillEditor) {
if (currentValue) {
if (format === 'text') {
this.quillEditor.setText(currentValue);
}
else {
this.quillEditor.setContents(this.valueSetter(this.quillEditor, this.content));
}
return;
}
this.quillEditor.setText('');
}
}
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');
}
}
}
registerOnChange(fn) {
this.onModelChange = fn;
}
registerOnTouched(fn) {
this.onModelTouched = fn;
}
validate() {
if (!this.quillEditor) {
return null;
}
const err = {};
let valid = true;
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;
}
};
__decorate([
Input(),
__metadata("design:type", String)
], QuillEditorComponent.prototype, "format", void 0);
__decorate([
Input(),
__metadata("design:type", String)
], QuillEditorComponent.prototype, "theme", void 0);
__decorate([
Input(),
__metadata("design:type", Object)
], QuillEditorComponent.prototype, "modules", void 0);
__decorate([
Input(),
__metadata("design:type", Object)
], QuillEditorComponent.prototype, "debug", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], QuillEditorComponent.prototype, "readOnly", void 0);
__decorate([
Input(),
__metadata("design:type", String)
], QuillEditorComponent.prototype, "placeholder", void 0);
__decorate([
Input(),
__metadata("design:type", Number)
], QuillEditorComponent.prototype, "maxLength", void 0);
__decorate([
Input(),
__metadata("design:type", Number)
], QuillEditorComponent.prototype, "minLength", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], QuillEditorComponent.prototype, "required", void 0);
__decorate([
Input(),
__metadata("design:type", Array)
], QuillEditorComponent.prototype, "formats", void 0);
__decorate([
Input(),
__metadata("design:type", String)
], QuillEditorComponent.prototype, "customToolbarPosition", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], QuillEditorComponent.prototype, "sanitize", void 0);
__decorate([
Input(),
__metadata("design:type", Object)
], QuillEditorComponent.prototype, "styles", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], QuillEditorComponent.prototype, "strict", void 0);
__decorate([
Input(),
__metadata("design:type", Object)
], QuillEditorComponent.prototype, "scrollingContainer", void 0);
__decorate([
Input(),
__metadata("design:type", Object)
], QuillEditorComponent.prototype, "bounds", void 0);
__decorate([
Input(),
__metadata("design:type", Array)
], QuillEditorComponent.prototype, "customOptions", void 0);
__decorate([
Input(),
__metadata("design:type", String)
], QuillEditorComponent.prototype, "trackChanges", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], QuillEditorComponent.prototype, "preserveWhitespace", void 0);
__decorate([
Output(),
__metadata("design:type", EventEmitter)
], QuillEditorComponent.prototype, "onEditorCreated", void 0);
__decorate([
Output(),
__metadata("design:type", EventEmitter)
], QuillEditorComponent.prototype, "onEditorChanged", void 0);
__decorate([
Output(),
__metadata("design:type", EventEmitter)
], QuillEditorComponent.prototype, "onContentChanged", void 0);
__decorate([
Output(),
__metadata("design:type", EventEmitter)
], QuillEditorComponent.prototype, "onSelectionChanged", void 0);
__decorate([
Output(),
__metadata("design:type", EventEmitter)
], QuillEditorComponent.prototype, "onFocus", void 0);
__decorate([
Output(),
__metadata("design:type", EventEmitter)
], QuillEditorComponent.prototype, "onBlur", void 0);
__decorate([
Input(),
__metadata("design:type", Object)
], QuillEditorComponent.prototype, "valueGetter", void 0);
__decorate([
Input(),
__metadata("design:type", Object)
], QuillEditorComponent.prototype, "valueSetter", void 0);
QuillEditorComponent = QuillEditorComponent_1 = __decorate([
Component({
encapsulation: ViewEncapsulation.None,
providers: [
{
multi: true,
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => QuillEditorComponent_1)
},
{
multi: true,
provide: NG_VALIDATORS,
useExisting: forwardRef(() => QuillEditorComponent_1)
}
],
selector: 'quill-editor',
template: `
<ng-content select="[quill-editor-toolbar]"></ng-content>
`
}),
__param(2, Inject(DOCUMENT)),
__param(3, Inject(PLATFORM_ID)),
__param(6, Inject(QUILL_CONFIG_TOKEN)),
__metadata("design:paramtypes", [ElementRef,
DomSanitizer, Object, Object,
Renderer2,
NgZone, Object])
], QuillEditorComponent);
let QuillViewHTMLComponent = class QuillViewHTMLComponent {
constructor(sanitizer, config) {
this.sanitizer = sanitizer;
this.config = config;
this.innerHTML = '';
this.themeClass = 'ql-snow';
this.content = '';
}
ngOnChanges(changes) {
if (changes.theme) {
const theme = changes.theme.currentValue || (this.config.theme ? this.config.theme : 'snow');
this.themeClass = `ql-${theme} ngx-quill-view-html`;
}
else if (!this.theme) {
const theme = this.config.theme ? this.config.theme : 'snow';
this.themeClass = `ql-${theme} ngx-quill-view-html`;
}
if (changes.content) {
this.innerHTML = this.sanitizer.bypassSecurityTrustHtml(changes.content.currentValue);
}
}
};
__decorate([
Input(),
__metadata("design:type", String)
], QuillViewHTMLComponent.prototype, "content", void 0);
__decorate([
Input(),
__metadata("design:type", String)
], QuillViewHTMLComponent.prototype, "theme", void 0);
QuillViewHTMLComponent = __decorate([
Component({
encapsulation: ViewEncapsulation.None,
selector: 'quill-view-html',
template: `
<div class="ql-container" [ngClass]="themeClass">
<div class="ql-editor" [innerHTML]="innerHTML">
</div>
</div>
`,
styles: [`
.ql-container.ngx-quill-view-html {
border: 0;
}
`]
}),
__param(1, Inject(QUILL_CONFIG_TOKEN)),
__metadata("design:paramtypes", [DomSanitizer, Object])
], QuillViewHTMLComponent);
// tslint:disable-next-line:variable-name
let Quill$1 = null;
const getFormat$1 = (format, configFormat) => {
const passedFormat = format || configFormat;
return passedFormat || 'html';
};
let QuillViewComponent = class QuillViewComponent {
constructor(
// tslint:disable-next-line:ban-types
platformId, config, renderer, elementRef) {
this.platformId = platformId;
this.config = config;
this.renderer = renderer;
this.elementRef = elementRef;
this.strict = true;
this.customOptions = [];
this.preserveWhitespace = false;
this.valueSetter = (quillEditor, value) => {
const format = getFormat$1(this.format, this.config.format);
let content = value;
if (format === 'html' || format === 'text') {
content = quillEditor.clipboard.convert(value);
}
else if (format === 'json') {
try {
content = JSON.parse(value);
}
catch (e) {
content = [{ insert: value }];
}
}
quillEditor.setContents(content);
};
}
ngOnChanges(changes) {
if (!this.quillEditor) {
return;
}
if (changes.content) {
this.valueSetter(this.quillEditor, changes.content.currentValue);
}
}
ngAfterViewInit() {
if (isPlatformServer(this.platformId)) {
return;
}
if (!Quill$1) {
Quill$1 = require('quill');
}
const modules = Object.assign({}, this.modules || (this.config.modules || defaultModules));
modules.toolbar = false;
this.customOptions.forEach((customOption) => {
const newCustomOption = Quill$1.import(customOption.import);
newCustomOption.whitelist = customOption.whitelist;
Quill$1.register(newCustomOption, true);
});
let debug = this.debug;
if (!debug && debug !== false && this.config.debug) {
debug = this.config.debug;
}
let formats = this.formats;
if (!formats && formats === undefined) {
formats = this.config.formats ? Object.assign({}, this.config.formats) : (this.config.formats === null ? null : undefined);
}
const theme = this.theme || (this.config.theme ? this.config.theme : 'snow');
this.elementRef.nativeElement.insertAdjacentHTML('afterbegin', this.preserveWhitespace ? '<pre quill-view-element></pre>' : '<div quill-view-element></div>');
this.editorElem = this.elementRef.nativeElement.querySelector('[quill-view-element]');
this.quillEditor = new Quill$1(this.editorElem, {
debug,
formats,
modules,
readOnly: true,
strict: this.strict,
theme
});
this.renderer.addClass(this.editorElem, 'ngx-quill-view');
if (this.content) {
this.valueSetter(this.quillEditor, this.content);
}
}
};
__decorate([
Input(),
__metadata("design:type", String)
], QuillViewComponent.prototype, "format", void 0);
__decorate([
Input(),
__metadata("design:type", String)
], QuillViewComponent.prototype, "theme", void 0);
__decorate([
Input(),
__metadata("design:type", Object)
], QuillViewComponent.prototype, "modules", void 0);
__decorate([
Input(),
__metadata("design:type", Object)
], QuillViewComponent.prototype, "debug", void 0);
__decorate([
Input(),
__metadata("design:type", Array)
], QuillViewComponent.prototype, "formats", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], QuillViewComponent.prototype, "strict", void 0);
__decorate([
Input(),
__metadata("design:type", Object)
], QuillViewComponent.prototype, "content", void 0);
__decorate([
Input(),
__metadata("design:type", Array)
], QuillViewComponent.prototype, "customOptions", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], QuillViewComponent.prototype, "preserveWhitespace", void 0);
QuillViewComponent = __decorate([
Component({
encapsulation: ViewEncapsulation.None,
selector: 'quill-view',
template: `
`,
styles: [`
.ql-container.ngx-quill-view {
border: 0;
}
`]
}),
__param(0, Inject(PLATFORM_ID)),
__param(1, Inject(QUILL_CONFIG_TOKEN)),
__metadata("design:paramtypes", [Object, Object, Renderer2,
ElementRef])
], QuillViewComponent);
var QuillModule_1;
let QuillModule = QuillModule_1 = class QuillModule {
static forRoot(config) {
return {
ngModule: QuillModule_1,
providers: [
{
provide: QUILL_CONFIG_TOKEN,
// tslint:disable-next-line:only-arrow-functions
useValue: config || { modules: defaultModules }
}
]
};
}
};
QuillModule = QuillModule_1 = __decorate([
NgModule({
declarations: [
QuillEditorComponent,
QuillViewComponent,
QuillViewHTMLComponent
],
exports: [QuillEditorComponent, QuillViewComponent, QuillViewHTMLComponent],
imports: [CommonModule],
providers: []
})
], QuillModule);
export { QUILL_CONFIG_TOKEN, QuillEditorComponent, QuillModule, QuillViewComponent, QuillViewHTMLComponent, defaultModules, ɵ0 };
//# sourceMappingURL=ngx-quill.js.map