Fix EditorJS read-mode toggle race condition (#1711)

* Fix EditorJS read-mode toggle race condition

Fixes rare edge case when toggling read mode is peerformed at the same
time.

* Make PromiseQueue function
This commit is contained in:
Dawid Tarasiuk 2022-01-10 13:34:17 +01:00 committed by GitHub
parent a01a2b5a30
commit 4c53dd0792
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 35 additions and 14 deletions

20
package-lock.json generated
View file

@ -1826,12 +1826,13 @@
}
},
"@editorjs/editorjs": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/@editorjs/editorjs/-/editorjs-2.20.0.tgz",
"integrity": "sha512-e6DWi8bMypFhovq9R6cefaDWVfrlVU++Q7ABp79+MxZIuC/SKAW5EtxBbKPL22H/Mc3bJIhZCxOqEl70HBh2yw==",
"version": "2.22.2",
"resolved": "https://registry.npmjs.org/@editorjs/editorjs/-/editorjs-2.22.2.tgz",
"integrity": "sha512-rPCv7Z5LZebreQaaL4DZuWzoVGEqwB+P7BF1dsefGQNBmLyeLF412topeW2b6e+g4l1oQ7t75kCOACNTEyYYIA==",
"requires": {
"codex-notifier": "^1.1.2",
"codex-tooltip": "^1.0.1"
"codex-tooltip": "^1.0.2",
"nanoid": "^3.1.22"
}
},
"@editorjs/embed": {
@ -11530,9 +11531,9 @@
"integrity": "sha512-DCp6xe/LGueJ1N5sXEwcBc3r3PyVkEEDNWCVigfvywAkeXcZMk9K41a31tkEFBW0Ptlwji6/JlAb49E3Yrxbtg=="
},
"codex-tooltip": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/codex-tooltip/-/codex-tooltip-1.0.2.tgz",
"integrity": "sha512-oC+Bu5X/zyhbPydgMSLWKoM/+vkJMqaLWu3Dt/jZgXS3MWK23INwC5DMBrVXZSufAFk0i0SUni38k9rLMyZn/w=="
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/codex-tooltip/-/codex-tooltip-1.0.4.tgz",
"integrity": "sha512-Ud+N+y8PMIa9xGyKuo2j3q8QlfTzkMWQ5KeRrbCDerwVn7xq45nqPKQCFBXEMV0YI42/OqSMnsxP8MyVAyVhnA=="
},
"collapse-white-space": {
"version": "1.0.6",
@ -21820,6 +21821,11 @@
"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
"optional": true
},
"nanoid": {
"version": "3.1.30",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
"integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ=="
},
"nanomatch": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",

View file

@ -18,7 +18,7 @@
},
"dependencies": {
"@apollo/client": "^3.4.15",
"@editorjs/editorjs": "^2.19.3",
"@editorjs/editorjs": "^2.22.2",
"@editorjs/header": "^2.6.1",
"@editorjs/image": "^2.6.0",
"@editorjs/list": "^1.6.1",

View file

@ -1,5 +1,6 @@
import EditorJS, { LogLevels, OutputData } from "@editorjs/editorjs";
import { FormControl, FormHelperText, InputLabel } from "@material-ui/core";
import { PromiseQueue } from "@saleor/misc";
import classNames from "classnames";
import React from "react";
@ -31,7 +32,7 @@ const RichTextEditor: React.FC<RichTextEditorProps> = ({
const [isFocused, setFocus] = React.useState(false);
const editor = React.useRef<EditorJS>();
const editorContainer = React.useRef<HTMLDivElement>();
const prevTogglePromise = React.useRef<Promise<boolean>>(); // used to await subsequent toggle invocations
const togglePromiseQueue = React.useRef(PromiseQueue()); // used to await subsequent toggle invocations
const initialMount = React.useRef(true);
React.useEffect(
@ -75,15 +76,14 @@ const RichTextEditor: React.FC<RichTextEditorProps> = ({
if (editor.current?.readOnly) {
// readOnly.toggle() by itself does not enqueue the events and will result in a broken output if invocations overlap
// Remove this logic when this is fixed in EditorJS
if (prevTogglePromise.current instanceof Promise) {
await prevTogglePromise.current;
}
prevTogglePromise.current = editor.current.readOnly.toggle(disabled);
togglePromiseQueue.current.add(() =>
editor.current.readOnly.toggle(disabled)
);
// Switching to readOnly with empty blocks present causes the editor to freeze
// Remove this logic when this is fixed in EditorJS
if (!disabled && !data?.blocks?.length) {
await prevTogglePromise.current;
await togglePromiseQueue.current.queue;
editor.current.clear();
}
}

View file

@ -460,3 +460,18 @@ export const flatten = (obj: unknown) => {
return result;
};
export function PromiseQueue() {
let queue = Promise.resolve();
function add<T>(operation: (value: T | void) => PromiseLike<T>) {
return new Promise((resolve, reject) => {
queue = queue
.then(operation)
.then(resolve)
.catch(reject);
});
}
return { queue, add };
}