UNPKG

react-froala-wysiwyg

Version:

React component for Froala WYSIWYG HTML rich text editor.

322 lines (267 loc) 8.28 kB
'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; } } };