diff --git a/components/lib/editor/Editor.js b/components/lib/editor/Editor.js
index 1d3612d5bf..072ed69956 100644
--- a/components/lib/editor/Editor.js
+++ b/components/lib/editor/Editor.js
@@ -62,12 +62,23 @@ export const Editor = React.memo(React.forwardRef((props, ref) => {
quill.current.setContents(quill.current.clipboard.convert(props.value));
}
- quill.current.on('text-change', (delta, source) => {
+ quill.current.on('text-change', (delta, oldContents, source) => {
let firstChild = contentRef.current.children[0];
let html = firstChild ? firstChild.innerHTML : null;
let text = quill.current.getText();
if (html === '
') {
- html = null;
+ html = null;
+ }
+
+ // GitHub #2271 prevent infinite loop on clipboard paste of HTML
+ if (source === "api") {
+ const htmlValue = contentRef.current.children[0];
+ const editorValue = document.createElement("div");
+ editorValue.innerHTML = props.value || "";
+ // this is necessary because Quill rearranged style elements
+ if (DomHandler.isEqualElement(htmlValue, editorValue)) {
+ return;
+ }
}
if (props.onTextChange) {
diff --git a/components/lib/utils/DomHandler.js b/components/lib/utils/DomHandler.js
index 42d568a7bb..e0bff47c7f 100644
--- a/components/lib/utils/DomHandler.js
+++ b/components/lib/utils/DomHandler.js
@@ -953,4 +953,86 @@ export default class DomHandler {
return ((element && element.nodeType === 9) || this.isExist(element)) ? element : null;
}
}
+
+ /**
+ * Get the attribute names for an element and sorts them alpha for comparison
+ */
+ static getAttributeNames(node) {
+ let index, rv, attrs;
+
+ rv = [];
+ attrs = node.attributes;
+ for (index = 0; index < attrs.length; ++index) {
+ rv.push(attrs[index].nodeName);
+ }
+ rv.sort();
+ return rv;
+ }
+
+ /**
+ * Compare two elements for equality. Even will compare if the style element
+ * is out of order for example:
+ *
+ * elem1 = style="color: red; font-size: 28px"
+ * elem2 = style="font-size: 28px; color: red"
+ */
+ static isEqualElement(elm1, elm2) {
+ let attrs1, attrs2, name, node1, node2;
+
+ // Compare attributes without order sensitivity
+ attrs1 = DomHandler.getAttributeNames(elm1);
+ attrs2 = DomHandler.getAttributeNames(elm2);
+ if (attrs1.join(",") !== attrs2.join(",")) {
+ // console.log("Found nodes with different sets of attributes; not equiv");
+ return false;
+ }
+
+ // ...and values
+ // unless you want to compare DOM0 event handlers
+ // (onclick="...")
+ for (let index = 0; index < attrs1.length; ++index) {
+ name = attrs1[index];
+ if (name === 'style') {
+ const astyle = elm1.style;
+ const bstyle = elm2.style;
+ const rexDigitsOnly = /^\d+$/;
+ for (const key of Object.keys(astyle)) {
+ if (!rexDigitsOnly.test(key) && astyle[key] !== bstyle[key]) {
+ // Not equivalent, stop
+ //console.log("Found nodes with mis-matched values for attribute '" + name + "'; not equiv");
+ return false;
+ }
+ }
+ } else {
+ if (elm1.getAttribute(name) !== elm2.getAttribute(name)) {
+ // console.log("Found nodes with mis-matched values for attribute '" + name + "'; not equiv");
+ return false;
+ }
+ }
+ }
+
+ // Walk the children
+ for (node1 = elm1.firstChild, node2 = elm2.firstChild; node1 && node2; node1 = node1.nextSibling, node2 = node2.nextSibling) {
+ if (node1.nodeType !== node2.nodeType) {
+ // display("Found nodes of different types; not equiv");
+ return false;
+ }
+ if (node1.nodeType === 1) { // Element
+ if (!DomHandler.isEqualElement(node1, node2)) {
+ return false;
+ }
+ } else if (node1.nodeValue !== node2.nodeValue) {
+ // console.log("Found nodes with mis-matched nodeValues; not equiv");
+ return false;
+ }
+ }
+ if (node1 || node2) {
+ // One of the elements had more nodes than the other
+ // console.log("Found more children of one element than the other; not equivalent");
+ return false;
+ }
+
+ // Seem the same
+ return true;
+ }
}
\ No newline at end of file