UNPKG

ckeditor4-angular

Version:

Official CKEditor 4 component for Angular.

401 lines (395 loc) 15.1 kB
import { EventEmitter, Component, forwardRef, ElementRef, NgZone, Input, Output, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'; import { getEditorNamespace } from 'ckeditor4-integrations-common'; /** * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md. */ class CKEditorComponent { constructor(elementRef, ngZone) { this.elementRef = elementRef; this.ngZone = ngZone; /** * CKEditor 4 script url address. Script will be loaded only if CKEDITOR namespace is missing. * * Defaults to 'https://cdn.ckeditor.com/4.19.1/standard-all/ckeditor.js' */ this.editorUrl = 'https://cdn.ckeditor.com/4.19.1/standard-all/ckeditor.js'; /** * Tag name of the editor component. * * The default tag is `textarea`. */ this.tagName = 'textarea'; /** * The type of the editor interface. * * By default editor interface will be initialized as `classic` editor. * You can also choose to create an editor with `inline` interface type instead. * * See https://ckeditor.com/docs/ckeditor4/latest/guide/dev_uitypes.html * and https://ckeditor.com/docs/ckeditor4/latest/examples/fixedui.html * to learn more. */ this.type = "classic" /* CLASSIC */; /** * Fired when the CKEDITOR https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.html namespace * is loaded. It only triggers once, no matter how many CKEditor 4 components are initialised. * Can be used for convenient changes in the namespace, e.g. for adding external plugins. */ this.namespaceLoaded = new EventEmitter(); /** * Fires when the editor is ready. It corresponds with the `editor#instanceReady` * https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#event-instanceReady * event. */ this.ready = new EventEmitter(); /** * Fires when the editor data is loaded, e.g. after calling setData() * https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#method-setData * editor's method. It corresponds with the `editor#dataReady` * https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#event-dataReady event. */ this.dataReady = new EventEmitter(); /** * Fires when the content of the editor has changed. It corresponds with the `editor#change` * https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#event-change * event. For performance reasons this event may be called even when data didn't really changed. * Please note that this event will only be fired when `undo` plugin is loaded. If you need to * listen for editor changes (e.g. for two-way data binding), use `dataChange` event instead. */ this.change = new EventEmitter(); /** * Fires when the content of the editor has changed. In contrast to `change` - only emits when * data really changed thus can be successfully used with `[data]` and two way `[(data)]` binding. * * See more: https://angular.io/guide/template-syntax#two-way-binding--- */ this.dataChange = new EventEmitter(); /** * Fires when the native dragStart event occurs. It corresponds with the `editor#dragstart` * https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#event-dragstart * event. */ this.dragStart = new EventEmitter(); /** * Fires when the native dragEnd event occurs. It corresponds with the `editor#dragend` * https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#event-dragend * event. */ this.dragEnd = new EventEmitter(); /** * Fires when the native drop event occurs. It corresponds with the `editor#drop` * https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#event-drop * event. */ this.drop = new EventEmitter(); /** * Fires when the file loader response is received. It corresponds with the `editor#fileUploadResponse` * https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#event-fileUploadResponse * event. */ this.fileUploadResponse = new EventEmitter(); /** * Fires when the file loader should send XHR. It corresponds with the `editor#fileUploadRequest` * https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#event-fileUploadRequest * event. */ this.fileUploadRequest = new EventEmitter(); /** * Fires when the editing area of the editor is focused. It corresponds with the `editor#focus` * https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#event-focus * event. */ this.focus = new EventEmitter(); /** * Fires after the user initiated a paste action, but before the data is inserted. * It corresponds with the `editor#paste` * https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#event-paste * event. */ this.paste = new EventEmitter(); /** * Fires after the `paste` event if content was modified. It corresponds with the `editor#afterPaste` * https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#event-afterPaste * event. */ this.afterPaste = new EventEmitter(); /** * Fires when the editing view of the editor is blurred. It corresponds with the `editor#blur` * https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#event-blur * event. */ this.blur = new EventEmitter(); /** * If the component is read–only before the editor instance is created, it remembers that state, * so the editor can become read–only once it is ready. */ this._readOnly = null; this._data = null; this._destroyed = false; } /** * Keeps track of the editor's data. * * It's also decorated as an input which is useful when not using the ngModel. * * See https://angular.io/api/forms/NgModel to learn more. */ set data(data) { if (data === this._data) { return; } if (this.instance) { this.instance.setData(data); // Data may be changed by ACF. this._data = this.instance.getData(); return; } this._data = data; } get data() { return this._data; } /** * When set to `true`, the editor becomes read-only. * * See https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#property-readOnly * to learn more. */ set readOnly(isReadOnly) { if (this.instance) { this.instance.setReadOnly(isReadOnly); return; } // Delay setting read-only state until editor initialization. this._readOnly = isReadOnly; } get readOnly() { if (this.instance) { return this.instance.readOnly; } return this._readOnly; } ngAfterViewInit() { getEditorNamespace(this.editorUrl, namespace => { this.namespaceLoaded.emit(namespace); }).then(() => { // Check if component instance was destroyed before `ngAfterViewInit` call (#110). // Here, `this.instance` is still not initialized and so additional flag is needed. if (this._destroyed) { return; } this.ngZone.runOutsideAngular(this.createEditor.bind(this)); }).catch(window.console.error); } ngOnDestroy() { this._destroyed = true; this.ngZone.runOutsideAngular(() => { if (this.instance) { this.instance.destroy(); this.instance = null; } }); } writeValue(value) { this.data = value; } registerOnChange(callback) { this.onChange = callback; } registerOnTouched(callback) { this.onTouched = callback; } createEditor() { var _a, _b; const element = document.createElement(this.tagName); this.elementRef.nativeElement.appendChild(element); const userInstanceReadyCallback = (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.on) === null || _b === void 0 ? void 0 : _b.instanceReady; const defaultConfig = { delayIfDetached: true }; const config = Object.assign(Object.assign({}, defaultConfig), this.config); if (typeof config.on === 'undefined') { config.on = {}; } config.on.instanceReady = evt => { const editor = evt.editor; this.instance = editor; // Read only state may change during instance initialization. this.readOnly = this._readOnly !== null ? this._readOnly : this.instance.readOnly; this.subscribe(this.instance); const undo = editor.undoManager; if (this.data !== null) { undo && undo.lock(); editor.setData(this.data, { callback: () => { // Locking undoManager prevents 'change' event. // Trigger it manually to updated bound data. if (this.data !== editor.getData()) { undo ? editor.fire('change') : editor.fire('dataReady'); } undo && undo.unlock(); this.ngZone.run(() => { if (typeof userInstanceReadyCallback === 'function') { userInstanceReadyCallback(evt); } this.ready.emit(evt); }); } }); } else { this.ngZone.run(() => { if (typeof userInstanceReadyCallback === 'function') { userInstanceReadyCallback(evt); } this.ready.emit(evt); }); } }; if (this.type === "inline" /* INLINE */) { CKEDITOR.inline(element, config); } else { CKEDITOR.replace(element, config); } } subscribe(editor) { editor.on('focus', evt => { this.ngZone.run(() => { this.focus.emit(evt); }); }); editor.on('paste', evt => { this.ngZone.run(() => { this.paste.emit(evt); }); }); editor.on('afterPaste', evt => { this.ngZone.run(() => { this.afterPaste.emit(evt); }); }); editor.on('dragend', evt => { this.ngZone.run(() => { this.dragEnd.emit(evt); }); }); editor.on('dragstart', evt => { this.ngZone.run(() => { this.dragStart.emit(evt); }); }); editor.on('drop', evt => { this.ngZone.run(() => { this.drop.emit(evt); }); }); editor.on('fileUploadRequest', evt => { this.ngZone.run(() => { this.fileUploadRequest.emit(evt); }); }); editor.on('fileUploadResponse', evt => { this.ngZone.run(() => { this.fileUploadResponse.emit(evt); }); }); editor.on('blur', evt => { this.ngZone.run(() => { if (this.onTouched) { this.onTouched(); } this.blur.emit(evt); }); }); editor.on('dataReady', this.propagateChange, this); if (this.instance.undoManager) { editor.on('change', this.propagateChange, this); } // If 'undo' plugin is not loaded, listen to 'selectionCheck' event instead. (#54). else { editor.on('selectionCheck', this.propagateChange, this); } } propagateChange(event) { this.ngZone.run(() => { const newData = this.instance.getData(); if (event.name === 'change') { this.change.emit(event); } else if (event.name === 'dataReady') { this.dataReady.emit(event); } if (newData === this.data) { return; } this._data = newData; this.dataChange.emit(newData); if (this.onChange) { this.onChange(newData); } }); } } CKEditorComponent.decorators = [ { type: Component, args: [{ selector: 'ckeditor', template: '<ng-template></ng-template>', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CKEditorComponent), multi: true, } ] },] } ]; CKEditorComponent.ctorParameters = () => [ { type: ElementRef }, { type: NgZone } ]; CKEditorComponent.propDecorators = { config: [{ type: Input }], editorUrl: [{ type: Input }], tagName: [{ type: Input }], type: [{ type: Input }], data: [{ type: Input }], readOnly: [{ type: Input }], namespaceLoaded: [{ type: Output }], ready: [{ type: Output }], dataReady: [{ type: Output }], change: [{ type: Output }], dataChange: [{ type: Output }], dragStart: [{ type: Output }], dragEnd: [{ type: Output }], drop: [{ type: Output }], fileUploadResponse: [{ type: Output }], fileUploadRequest: [{ type: Output }], focus: [{ type: Output }], paste: [{ type: Output }], afterPaste: [{ type: Output }], blur: [{ type: Output }] }; /** * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md. */ /** * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md. */ class CKEditorModule { } CKEditorModule.decorators = [ { type: NgModule, args: [{ imports: [FormsModule, CommonModule], declarations: [CKEditorComponent], exports: [CKEditorComponent] },] } ]; /** * Generated bundle index. Do not edit. */ export { CKEditorComponent, CKEditorModule }; //# sourceMappingURL=ckeditor4-angular.js.map