UNPKG

ckeditor4-angular

Version:

Official CKEditor 4 component for Angular.

428 lines (422 loc) 17.4 kB
import * as i0 from '@angular/core'; import { EventEmitter, forwardRef, Component, 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-2025, 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.25.1-lts/standard-all/ckeditor.js' */ this.editorUrl = 'https://cdn.ckeditor.com/4.25.1-lts/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() { const element = document.createElement(this.tagName); this.elementRef.nativeElement.appendChild(element); const userInstanceReadyCallback = this.config?.on?.instanceReady; const defaultConfig = { delayIfDetached: true }; const config = { ...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.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: CKEditorComponent, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); CKEditorComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: CKEditorComponent, selector: "ckeditor", inputs: { config: "config", editorUrl: "editorUrl", tagName: "tagName", type: "type", data: "data", readOnly: "readOnly" }, outputs: { namespaceLoaded: "namespaceLoaded", ready: "ready", dataReady: "dataReady", change: "change", dataChange: "dataChange", dragStart: "dragStart", dragEnd: "dragEnd", drop: "drop", fileUploadResponse: "fileUploadResponse", fileUploadRequest: "fileUploadRequest", focus: "focus", paste: "paste", afterPaste: "afterPaste", blur: "blur" }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CKEditorComponent), multi: true, } ], ngImport: i0, template: '<ng-template></ng-template>', isInline: true }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: CKEditorComponent, decorators: [{ type: Component, args: [{ selector: 'ckeditor', template: '<ng-template></ng-template>', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CKEditorComponent), multi: true, } ] }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.NgZone }]; }, 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-2025, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md. */ /** * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md. */ class CKEditorModule { } CKEditorModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: CKEditorModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); CKEditorModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: CKEditorModule, declarations: [CKEditorComponent], imports: [FormsModule, CommonModule], exports: [CKEditorComponent] }); CKEditorModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: CKEditorModule, imports: [[FormsModule, CommonModule]] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: CKEditorModule, decorators: [{ type: NgModule, args: [{ imports: [FormsModule, CommonModule], declarations: [CKEditorComponent], exports: [CKEditorComponent] }] }] }); /** * Generated bundle index. Do not edit. */ export { CKEditorComponent, CKEditorModule }; //# sourceMappingURL=ckeditor4-angular.mjs.map