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

View file

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

View file

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

View file

@ -460,3 +460,18 @@ export const flatten = (obj: unknown) => {
return result; 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 };
}