Skip to content

Commit 2eb87fc

Browse files
authored
[WC-1778]: rich text - apply changes on losing focus (#496)
2 parents 7dfd1e6 + 1366c55 commit 2eb87fc

File tree

5 files changed

+66
-15
lines changed

5 files changed

+66
-15
lines changed

packages/pluggableWidgets/rich-text-web/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## [Unreleased]
88

9+
### Fixed
10+
11+
- We fixed an issue when Rich Text widget not saving data when user leaves the page quickly after editing.
12+
13+
### Security
14+
15+
- Update ckeditor4 to version 4.21.0
16+
917
## [2.1.5] - 2023-03-24
1018

1119
### Fixed

packages/pluggableWidgets/rich-text-web/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "rich-text-web",
33
"widgetName": "RichText",
4-
"version": "2.1.5",
4+
"version": "2.1.6",
55
"description": "Rich inline or toolbar text editing",
66
"copyright": "© Mendix Technology BV 2023. All rights reserved.",
77
"repository": {
@@ -81,7 +81,7 @@
8181
},
8282
"dependencies": {
8383
"@types/dompurify": "^2.4.0",
84-
"ckeditor4": "^4.20.2",
84+
"ckeditor4": "^4.21.0",
8585
"ckeditor4-react": "^2.1.1",
8686
"classnames": "^2.3.2",
8787
"dompurify": "^2.4.5"

packages/pluggableWidgets/rich-text-web/src/components/Editor.tsx

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ export class Editor extends Component<EditorProps> {
3434
editorScript = "widgets/ckeditor/ckeditor.js";
3535
element: HTMLElement;
3636
lastSentValue: string | undefined;
37+
applyChangesDebounce: () => void;
38+
cancelRAF: (() => void) | undefined;
39+
hasFocus: boolean;
3740

3841
constructor(props: EditorProps) {
3942
super(props);
@@ -42,10 +45,12 @@ export class Editor extends Component<EditorProps> {
4245
this.element = this.props.element;
4346
this.editorKey = this.getNewKey();
4447
this.editorHookProps = this.getNewEditorHookProps();
45-
this.onChange = debounce(this.onChange.bind(this), 500);
48+
this.onChange = this.onChange.bind(this);
49+
this.applyChangesDebounce = debounce(this.applyChangesImmediately.bind(this), 500);
4650
this.onKeyPress = this.onKeyPress.bind(this);
4751
this.onPasteContent = this.onPasteContent.bind(this);
4852
this.onDropContent = this.onDropContent.bind(this);
53+
this.hasFocus = false;
4954
}
5055

5156
setNewRenderProps(): void {
@@ -171,17 +176,21 @@ export class Editor extends Component<EditorProps> {
171176
}
172177
}
173178

174-
// onChange is wrapped in debounce, so, we always need to check
175-
// weather we sill have editor.
176179
onChange(_event: CKEditorEventPayload<"change">): void {
177180
if (this.editor) {
178181
const editorData = this.editor.getData();
179182
const content = this.widgetProps.sanitizeContent ? DOMPurify.sanitize(editorData) : editorData;
180183
this.lastSentValue = content;
181-
this.widgetProps.stringAttribute.setValue(content);
184+
this.applyChangesDebounce();
182185
}
186+
}
183187

184-
this.widgetProps.onChange?.execute();
188+
applyChangesImmediately() {
189+
// put last seen content to the attribute if it exists
190+
if (this.lastSentValue !== undefined) {
191+
this.widgetProps.stringAttribute.setValue(this.lastSentValue);
192+
this.widgetProps.onChange?.execute();
193+
}
185194
}
186195

187196
addListeners(): void {
@@ -253,6 +262,25 @@ export class Editor extends Component<EditorProps> {
253262
this.lastSentValue = undefined;
254263
}
255264

265+
componentDidMount() {
266+
this.cancelRAF = animationLoop(() => {
267+
if (this.element && this.element.parentElement) {
268+
const newHasFocus = this.element.parentElement.contains(document.activeElement);
269+
if (newHasFocus !== this.hasFocus) {
270+
this.hasFocus = newHasFocus;
271+
if (!this.hasFocus) {
272+
// changed from true to false, user left the element, apply changes immediately
273+
this.applyChangesImmediately();
274+
}
275+
}
276+
}
277+
});
278+
}
279+
280+
componentWillUnmount() {
281+
this.cancelRAF?.();
282+
}
283+
256284
componentDidUpdate(): void {
257285
const prevAttr = this.widgetProps.stringAttribute;
258286
const nextAttr = this.props.widgetProps.stringAttribute;
@@ -269,3 +297,18 @@ export class Editor extends Component<EditorProps> {
269297
return <MainEditor key={key} config={config} />;
270298
}
271299
}
300+
301+
function animationLoop(callback: () => void): () => void {
302+
let requestId: number;
303+
304+
const requestFrame = () => {
305+
requestId = window.requestAnimationFrame(() => {
306+
callback();
307+
requestFrame();
308+
});
309+
};
310+
311+
requestFrame();
312+
313+
return () => window.cancelAnimationFrame(requestId);
314+
}

packages/pluggableWidgets/rich-text-web/src/package.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<package xmlns="http://www.mendix.com/package/1.0/">
3-
<clientModule name="RichText" version="2.1.5" xmlns="http://www.mendix.com/clientModule/1.0/">
3+
<clientModule name="RichText" version="2.1.6" xmlns="http://www.mendix.com/clientModule/1.0/">
44
<widgetFiles>
55
<widgetFile path="RichText.xml" />
66
</widgetFiles>

pnpm-lock.yaml

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)