fix: 修复 froalaEditor 数值可能不同步的问题 (#2815)

This commit is contained in:
liaoxuezhi 2021-11-01 15:59:37 +08:00 committed by GitHub
parent 981537867c
commit 905f891521
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 290 additions and 19 deletions

View File

@ -495,7 +495,7 @@ if (fis.project.currentMedia() === 'publish') {
'!mpegts.js/**',
'!hls.js/**',
'!froala-editor/**',
'!react-froala-wysiwyg/**',
'!tinymce/**',
'!zrender/**',
'!echarts/**',
@ -526,11 +526,7 @@ if (fis.project.currentMedia() === 'publish') {
'!punycode/**'
],
'rich-text.js': [
'src/components/RichText.tsx',
'froala-editor/**',
'react-froala-wysiwyg/**'
],
'rich-text.js': ['src/components/RichText.tsx', 'froala-editor/**'],
'tinymce.js': ['src/components/Tinymce.tsx', 'tinymce/**'],
@ -569,7 +565,7 @@ if (fis.project.currentMedia() === 'publish') {
'!mpegts.js/**',
'!hls.js/**',
'!froala-editor/**',
'!react-froala-wysiwyg/**',
'!src/components/RichText.tsx',
'!zrender/**',
'!echarts/**',
@ -777,7 +773,7 @@ if (fis.project.currentMedia() === 'publish') {
'!mpegts.js/**',
'!hls.js/**',
'!froala-editor/**',
'!react-froala-wysiwyg/**',
'!tinymce/**',
'!zrender/**',
'!echarts/**',
@ -808,11 +804,7 @@ if (fis.project.currentMedia() === 'publish') {
'!punycode/**'
],
'pkg/rich-text.js': [
'src/components/RichText.js',
'froala-editor/**',
'react-froala-wysiwyg/**'
],
'pkg/rich-text.js': ['src/components/RichText.js', 'froala-editor/**'],
'pkg/tinymce.js': ['src/components/Tinymce.tsx', 'tinymce/**'],
@ -864,7 +856,7 @@ if (fis.project.currentMedia() === 'publish') {
'!mpegts.js/**',
'!hls.js/**',
'!froala-editor/**',
'!react-froala-wysiwyg/**',
'!src/components/RichText.tsx',
'!zrender/**',
'!echarts/**',

View File

@ -1,6 +1,6 @@
{
"name": "amis",
"version": "1.4.0",
"version": "1.4.1-beta.2",
"description": "一种MIS页面生成工具",
"main": "lib/index.js",
"scripts": {
@ -76,7 +76,6 @@
"react-datetime": "2.16.2",
"react-dom": "^16.8.6",
"react-dropzone": "^11.4.2",
"react-froala-wysiwyg": "3.1.1",
"react-input-range": "1.3.0",
"react-json-view": "1.21.3",
"react-overlays": "5.1.1",

View File

@ -7,7 +7,7 @@
import React from 'react';
// @ts-ignore
import FroalaEditorComponent from 'react-froala-wysiwyg';
import FroalaEditor from 'froala-editor';
// @ts-ignore
import Froala from 'froala-editor/js/froala_editor.min.js';
import 'froala-editor/js/plugins/align.min';
@ -45,7 +45,288 @@ import 'froala-editor/js/languages/zh_cn.js';
import 'froala-editor/css/froala_style.min.css';
import 'froala-editor/css/froala_editor.pkgd.min.css';
export default class FroalaEditor extends React.Component<any, any> {
export interface FroalaEditorComponentProps {
config: any;
model: string;
onModelChange: (value: string) => void;
}
// 代码来源于https://github.com/froala/react-froala-wysiwyg/blob/master/lib/FroalaEditorFunctionality.jsx
// 改动原因是model 同步有些问题,有时候不更新,所以基于官方代码改造一下。
// 目前发现的问题是,如果 model 数据修改,如果此时 editor 还没有初始化完成则不会同步成功
class FroalaEditorComponent extends React.Component<FroalaEditorComponentProps> {
listeningEvents: any;
element: any;
editor: any;
config: any;
editorInitialized: any;
INNER_HTML_ATTR: any;
hasSpecialTag: any;
oldModel: any;
el: any;
_initEvents: any;
constructor(props: FroalaEditorComponentProps) {
super(props);
this.listeningEvents = [];
this.element = null;
this.editor = null;
this.config = {
immediateReactModelUpdate: false,
reactIgnoreAttrs: null
};
this.editorInitialized = false;
this.INNER_HTML_ATTR = 'innerHTML';
this.oldModel = null;
}
// After first time render.
componentDidMount() {
this.createEditor();
}
componentWillUnmount() {
this.destroyEditor();
}
componentDidUpdate() {
if (JSON.stringify(this.oldModel) == JSON.stringify(this.props.model)) {
return;
}
this.setContent();
}
// Return cloned object
clone(item: any) {
const me = this;
if (!item) {
return item;
} // null, undefined values check
let types = [Number, String, Boolean],
result: any;
// 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: any, index: number, array: Array<any>) {
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) {
return;
}
this.config = this.clone(this.props.config || this.config);
this.config = {...this.config};
this.element = this.el;
if (this.props.model) {
this.element.innerHTML = this.props.model;
}
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);
}
setContent(firstTime?: boolean) {
if (this.props.model || this.props.model == '') {
this.oldModel = this.props.model;
if (this.editorInitialized) {
this.setNormalTagContent(firstTime);
} else {
this._initEvents.push(() => this.setNormalTagContent());
}
}
}
setNormalTagContent(firstTime?: boolean) {
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?
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();
}
}
destroyEditor() {
if (this.element) {
this.editor.destroy && this.editor.destroy();
this.listeningEvents.length = 0;
this.element = null;
this.editorInitialized = false;
}
}
getEditor() {
if (this.element) {
return this.editor;
}
return null;
}
updateModel() {
if (!this.props.onModelChange) {
return;
}
let modelContent = '';
if (this.hasSpecialTag) {
let attributeNodes = this.element.attributes;
let attrs: any = {};
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
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: string, callback: Function) {
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;
}
}
render() {
return (
<textarea ref={el => (this.el = el)}>{this.props.children}</textarea>
);
}
}
export default class extends React.Component<any, any> {
constructor(props: any) {
super(props);
Froala.VIDEO_PROVIDERS = [
@ -61,7 +342,6 @@ export default class FroalaEditor extends React.Component<any, any> {
render() {
return (
<FroalaEditorComponent
tag="textarea"
config={this.props.config}
model={this.props.model}
onModelChange={this.props.onModelChange}