react-froala-wysiwyg
Version:
React component for Froala WYSIWYG HTML rich text editor.
322 lines (267 loc) • 8.28 kB
JSX
'use client';
import React from 'react';
let lastId = 0;
import FroalaEditor from 'froala-editor';
export default class FroalaEditorFunctionality extends React.Component {
constructor(props) {
super(props);
// Tag on which the editor is initialized.
this.defaultTag = 'div';
this.tag = props.tag || this.defaultTag;
this.listeningEvents = [];
// Jquery wrapped element.
this.element = null;
// Editor element.
this.editor = null;
// Editor options config
this.config = {
immediateReactModelUpdate: false,
reactIgnoreAttrs: null
};
this.editorInitialized = false;
this.editorCreated = false;
this.SPECIAL_TAGS = ['img', 'button', 'input', 'a'];
this.INNER_HTML_ATTR = 'innerHTML';
this.hasSpecialTag = false;
this.oldModel = null;
}
// After first time render.
componentDidMount() {
let tagName = this.el.tagName.toLowerCase();
if (this.SPECIAL_TAGS.indexOf(tagName) != -1) {
this.tag = tagName;
this.hasSpecialTag = true;
}
if (this.props.onManualControllerReady) {
this.generateManualController();
} else {
this.createEditor();
}
}
componentWillUnmount() {
this.destroyEditor();
}
componentDidUpdate() {
if (JSON.stringify(this.oldModel) == JSON.stringify(this.props.model)) {
return;
}
this.setContent();
}
// Return cloned object
clone(item) {
const me = this;
if (!item) { return item; } // null, undefined values check
let types = [ Number, String, Boolean ],
result;
// normalizing primitives if someone did new String('aaa'), or new Number('444');
types.forEach(function(type) {
if (item instanceof type) {
result = type( item );
}
});
if (typeof result == "undefined") {
if (Object.prototype.toString.call( item ) === "[object Array]") {
result = [];
item.forEach(function(child, index, array) {
result[index] = me.clone( child );
});
} else if (typeof item == "object") {
// testing that this is DOM
if (item.nodeType && typeof item.cloneNode == "function") {
result = item.cloneNode( true );
} else if (!item.prototype) { // check that this is a literal
if (item instanceof Date) {
result = new Date(item);
} else {
// it is an object literal
result = {};
for (var i in item) {
result[i] = me.clone( item[i] );
}
}
} else {
if (false && item.constructor) {
result = new item.constructor();
} else {
result = item;
}
}
} else {
result = item;
}
}
return result;
}
createEditor() {
if (this.editorInitialized || this.editorCreated) {
return;
}
this.config = this.clone(this.props.config || this.config);
this.config = {...this.config};
this.element = this.el;
this.setContent(true);
// Default initialized.
this.registerEvent('initialized', this.config.events && this.config.events.initialized);
// Check if events are set.
if (!this.config.events) this.config.events = {};
this.config.events.initialized = () => this.initListeners();
this.editor = new FroalaEditor(this.element, this.config);
this.editorCreated = true;
}
setContent(firstTime) {
if (this.props.model || this.props.model == '') {
this.oldModel = this.props.model;
if (this.hasSpecialTag) {
this.setSpecialTagContent();
} else {
this.setNormalTagContent(firstTime);
}
}
}
setNormalTagContent(firstTime) {
let self = this;
function htmlSet() {
self.editor.html && self.editor.html.set(self.props.model || '');
if (self.editorInitialized && self.editor.undo) {
//This will reset the undo stack everytime the model changes externally. Can we fix this?
//https://github.com/froala-labs/froala-editor-js-2/issues/4214
if (!self.props.skipReset) {
self.editor.undo.reset();
}
self.editor.undo.saveStep();
}
}
if (firstTime) {
if (this.config.initOnClick) {
this.registerEvent('initializationDelayed', () => {
htmlSet();
});
this.registerEvent('initialized', () => {
this.editorInitialized = true;
});
} else {
this.registerEvent('initialized', () => {
this.editorInitialized = true;
htmlSet();
});
}
} else {
htmlSet();
}
}
setSpecialTagContent() {
let tags = this.props.model;
// add tags on element
if (tags) {
for (let attr in tags) {
if (tags.hasOwnProperty(attr) && attr != this.INNER_HTML_ATTR) {
this.element.setAttribute(attr, tags[attr]);
}
}
if (tags.hasOwnProperty(this.INNER_HTML_ATTR)) {
this.element.innerHTML = tags[this.INNER_HTML_ATTR];
}
}
}
destroyEditor() {
if (this.element) {
this.editor.destroy && this.editor.destroy();
this.listeningEvents.length = 0;
this.element = null;
this.editorInitialized = false;
this.editorCreated = false;
this.config = {
immediateReactModelUpdate: false,
reactIgnoreAttrs: null
};
let tagName = this.el.tagName.toLowerCase();
if (this.SPECIAL_TAGS.indexOf(tagName) == -1) {
if(this.editor && this.editor.destrying && !this.props.onManualControllerReady && this.tag == 'textarea'){
this.editor.$box.remove()
}
}
if(this.tag != 'textarea'){
this.editor.$wp = '';
}
}
}
getEditor() {
if (this.element) {
return this.editor;
}
return null;
}
generateManualController() {
let self = this;
let controls = {
initialize: () => self.createEditor.call(self),
destroy: () => self.destroyEditor.call(self),
getEditor: () => self.getEditor.call(self)
};
this.props.onManualControllerReady(controls);
}
updateModel() {
if (!this.props.onModelChange) {
return;
}
let modelContent = '';
if (this.hasSpecialTag) {
let attributeNodes = this.element.attributes;
let attrs = {};
for (let i = 0; i < attributeNodes.length; i++) {
let attrName = attributeNodes[i].name;
if (this.config.reactIgnoreAttrs && this.config.reactIgnoreAttrs.indexOf(attrName) != -1) {
continue;
}
attrs[attrName] = attributeNodes[i].value;
}
if (this.element.innerHTML) {
attrs[this.INNER_HTML_ATTR] = this.element.innerHTML;
}
modelContent = attrs;
} else {
let returnedHtml = this.editor.html.get();
if (typeof returnedHtml === 'string') {
modelContent = returnedHtml;
}
}
this.oldModel = modelContent;
this.props.onModelChange(modelContent);
}
initListeners() {
let self = this;
// bind contentChange and keyup event to froalaModel
if(this.editor && this.editor.events){
this.editor.events.on('contentChanged', function () {
self.updateModel();
});
}
if (this.config.immediateReactModelUpdate) {
this.editor.events.on('keyup', function () {
self.updateModel();
});
}
// Call init events.
if (this._initEvents) {
for (let i = 0; i < this._initEvents.length; i++) {
this._initEvents[i].call(this.editor);
}
}
}
// register event on jquery editor element
registerEvent(eventName, callback) {
if (!eventName || !callback) {
return;
}
if (eventName == 'initialized') {
if (!this._initEvents) this._initEvents = [];
this._initEvents.push(callback);
}
else {
if (!this.config.events) {
this.config.events = {};
}
this.config.events[eventName] = callback;
}
}
};