Skip to content

Commit a345338

Browse files
hamza0867spocke
andauthored
INT-3226: Fix onEditorChanged called three times after calling insertContent on a selection (#581)
* INT-3226: Fix onEditorChange called three times after inserting content on a selection Co-authored-by: spocke <[email protected]>
1 parent 35b4c64 commit a345338

File tree

4 files changed

+35
-19
lines changed

4 files changed

+35
-19
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010
- Support for `react` version `^19.0.0` by updating the `react` and `react-dom` in the `peerDependencies` to `^19.0.0`
1111

12+
### Fixed
13+
- The `onEditorChange` callback was called three times when content was inserted with `insertContent` editor API. #INT-3226
14+
1215
## 6.0.0 - 2025-02-21
1316

1417
### Fixed

src/main/ts/components/Editor.tsx

+7-13
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import * as React from 'react';
2+
import type { Bookmark, EditorEvent, TinyMCE, Editor as TinyMCEEditor } from 'tinymce';
23
import { IEvents } from '../Events';
34
import { ScriptItem, ScriptLoader } from '../ScriptLoader2';
45
import { getTinymce } from '../TinyMCE';
5-
import { isFunction, isTextareaOrInput, mergePlugins, uuid, configHandlers, isBeforeInputEventAvailable, isInDoc, setMode } from '../Utils';
6+
import { configHandlers, isBeforeInputEventAvailable, isFunction, isInDoc, isTextareaOrInput, mergePlugins, setMode, uuid } from '../Utils';
67
import { EditorPropTypes, IEditorPropTypes } from './EditorPropTypes';
7-
import type { Bookmark, Editor as TinyMCEEditor, EditorEvent, TinyMCE } from 'tinymce';
8+
9+
const changeEvents = 'change keyup compositionend setcontent CommentChange';
810

911
type OmitStringIndexSignature<T> = { [K in keyof T as string extends K ? never : K]: T[K] };
1012

@@ -241,7 +243,7 @@ export class Editor extends React.Component<IAllProps> {
241243
public componentWillUnmount() {
242244
const editor = this.editor;
243245
if (editor) {
244-
editor.off(this.changeEvents(), this.handleEditorChange);
246+
editor.off(changeEvents, this.handleEditorChange);
245247
editor.off(this.beforeInputEvent(), this.handleBeforeInput);
246248
editor.off('keypress', this.handleEditorChangeSpecial);
247249
editor.off('keydown', this.handleBeforeInputSpecial);
@@ -259,14 +261,6 @@ export class Editor extends React.Component<IAllProps> {
259261
return this.inline ? this.renderInline() : this.renderIframe();
260262
}
261263

262-
private changeEvents() {
263-
const isIE = getTinymce(this.view)?.Env?.browser?.isIE();
264-
return (isIE
265-
? 'change keyup compositionend setcontent CommentChange'
266-
: 'change input compositionend setcontent CommentChange'
267-
);
268-
}
269-
270264
private beforeInputEvent() {
271265
return isBeforeInputEventAvailable() ? 'beforeinput SelectionChange' : 'SelectionChange';
272266
}
@@ -335,13 +329,13 @@ export class Editor extends React.Component<IAllProps> {
335329
const wasControlled = isValueControlled(prevProps);
336330
const nowControlled = isValueControlled(this.props);
337331
if (!wasControlled && nowControlled) {
338-
this.editor.on(this.changeEvents(), this.handleEditorChange);
332+
this.editor.on(changeEvents, this.handleEditorChange);
339333
this.editor.on(this.beforeInputEvent(), this.handleBeforeInput);
340334
this.editor.on('keydown', this.handleBeforeInputSpecial);
341335
this.editor.on('keyup', this.handleEditorChangeSpecial);
342336
this.editor.on('NewBlock', this.handleEditorChange);
343337
} else if (wasControlled && !nowControlled) {
344-
this.editor.off(this.changeEvents(), this.handleEditorChange);
338+
this.editor.off(changeEvents, this.handleEditorChange);
345339
this.editor.off(this.beforeInputEvent(), this.handleBeforeInput);
346340
this.editor.off('keydown', this.handleBeforeInputSpecial);
347341
this.editor.off('keyup', this.handleEditorChangeSpecial);

src/stories/Editor.stories.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import { StoryObj } from '@storybook/react';
12
import React from 'react';
23
import { EditorEvent, Events, Editor as TinyMCEEditor } from 'tinymce';
3-
import { StoryObj } from '@storybook/react';
44
import { Editor, IAllProps } from '../main/ts/components/Editor';
55

66
const apiKey = 'qagffr3pkuv17a8on1afax661irst1hbr4e6tbv888sz91jc';
@@ -60,7 +60,9 @@ export const ControlledInput: StoryObj<Editor> = {
6060
<Editor
6161
apiKey={apiKey}
6262
value={data}
63-
onEditorChange={(e) => setData(e)}
63+
onEditorChange={(e) => {
64+
setData(e);
65+
}}
6466
/>
6567
<textarea
6668
style={{ width: '100%', height: '200px' }}

src/test/ts/browser/EditorBehaviorTest.ts

+21-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import * as Loader from '../alien/Loader';
21
import { PlatformDetection } from '@ephox/sand';
2+
import * as Loader from '../alien/Loader';
33

44
import { describe, it } from '@ephox/bedrock-client';
55

6+
import { Assertions, Waiter } from '@ephox/agar';
7+
import { TinyAssertions, TinySelections } from '@ephox/mcagar';
8+
import { EditorEvent, Events, Editor as TinyMCEEditor } from 'tinymce';
69
import { getTinymce } from '../../../main/ts/TinyMCE';
710
import { EventStore, VERSIONS } from '../alien/TestHelpers';
8-
import { Editor as TinyMCEEditor, EditorEvent, Events } from 'tinymce';
9-
import { Assertions, Waiter } from '@ephox/agar';
10-
import { TinyAssertions } from '@ephox/mcagar';
1111

1212
type SetContentEvent = EditorEvent<Events.EditorEventMap['SetContent']>;
1313

@@ -135,6 +135,23 @@ describe('EditorBehaviourTest', () => {
135135

136136
eventStore.clearState();
137137
});
138+
it('INT-3226: onEditorChange is triggered only once after calling insertContent', async () => {
139+
using ctx = await render({ onEditorChange: eventStore.createHandler('onEditorChange') });
140+
const { editor } = ctx;
141+
editor.setContent('<p>abc</p>');
142+
await Waiter.pTryUntilPredicate('Editor content is set to correct value', () => ctx.editor.getContent() === '<p>abc</p>');
143+
eventStore.clearState();
144+
TinySelections.setSelection(editor, [ 0, 0 ], 1, [ 0, 0 ], 2);
145+
editor.insertContent('e');
146+
await Waiter.pTryUntilPredicate('Editor content is set to correct value', () => ctx.editor.getContent() === '<p>aec</p>');
147+
eventStore.each<string>('onEditorChange', (events) => {
148+
Assertions.assertEq(
149+
'onEditorChange should have been triggered once',
150+
1,
151+
events.length
152+
);
153+
});
154+
});
138155
})
139156
);
140157
});

0 commit comments

Comments
 (0)