Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Support for `react` version `^19.0.0` by updating the `react` and `react-dom` in the `peerDependencies` to `^19.0.0`

### Fixed
- onEditorChange called three times after calling `insertContent` on a selection. #INT-3226

## 6.0.0 - 2025-02-21

### Fixed
Expand Down
20 changes: 7 additions & 13 deletions src/main/ts/components/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as React from 'react';
import type { Bookmark, EditorEvent, TinyMCE, Editor as TinyMCEEditor } from 'tinymce';
import { IEvents } from '../Events';
import { ScriptItem, ScriptLoader } from '../ScriptLoader2';
import { getTinymce } from '../TinyMCE';
import { isFunction, isTextareaOrInput, mergePlugins, uuid, configHandlers, isBeforeInputEventAvailable, isInDoc, setMode } from '../Utils';
import { configHandlers, isBeforeInputEventAvailable, isFunction, isInDoc, isTextareaOrInput, mergePlugins, setMode, uuid } from '../Utils';
import { EditorPropTypes, IEditorPropTypes } from './EditorPropTypes';
import type { Bookmark, Editor as TinyMCEEditor, EditorEvent, TinyMCE } from 'tinymce';

const changeEvents = 'change keyup compositionend setcontent CommentChange';

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

Expand Down Expand Up @@ -241,7 +243,7 @@ export class Editor extends React.Component<IAllProps> {
public componentWillUnmount() {
const editor = this.editor;
if (editor) {
editor.off(this.changeEvents(), this.handleEditorChange);
editor.off(changeEvents, this.handleEditorChange);
editor.off(this.beforeInputEvent(), this.handleBeforeInput);
editor.off('keypress', this.handleEditorChangeSpecial);
editor.off('keydown', this.handleBeforeInputSpecial);
Expand All @@ -259,14 +261,6 @@ export class Editor extends React.Component<IAllProps> {
return this.inline ? this.renderInline() : this.renderIframe();
}

private changeEvents() {
const isIE = getTinymce(this.view)?.Env?.browser?.isIE();
return (isIE
? 'change keyup compositionend setcontent CommentChange'
: 'change input compositionend setcontent CommentChange'
);
}

private beforeInputEvent() {
return isBeforeInputEventAvailable() ? 'beforeinput SelectionChange' : 'SelectionChange';
}
Expand Down Expand Up @@ -335,13 +329,13 @@ export class Editor extends React.Component<IAllProps> {
const wasControlled = isValueControlled(prevProps);
const nowControlled = isValueControlled(this.props);
if (!wasControlled && nowControlled) {
this.editor.on(this.changeEvents(), this.handleEditorChange);
this.editor.on(changeEvents, this.handleEditorChange);
this.editor.on(this.beforeInputEvent(), this.handleBeforeInput);
this.editor.on('keydown', this.handleBeforeInputSpecial);
this.editor.on('keyup', this.handleEditorChangeSpecial);
this.editor.on('NewBlock', this.handleEditorChange);
} else if (wasControlled && !nowControlled) {
this.editor.off(this.changeEvents(), this.handleEditorChange);
this.editor.off(changeEvents, this.handleEditorChange);
this.editor.off(this.beforeInputEvent(), this.handleBeforeInput);
this.editor.off('keydown', this.handleBeforeInputSpecial);
this.editor.off('keyup', this.handleEditorChangeSpecial);
Expand Down
6 changes: 4 additions & 2 deletions src/stories/Editor.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { StoryObj } from '@storybook/react';
import React from 'react';
import { EditorEvent, Events, Editor as TinyMCEEditor } from 'tinymce';
import { StoryObj } from '@storybook/react';
import { Editor, IAllProps } from '../main/ts/components/Editor';

const apiKey = 'qagffr3pkuv17a8on1afax661irst1hbr4e6tbv888sz91jc';
Expand Down Expand Up @@ -60,7 +60,9 @@ export const ControlledInput: StoryObj<Editor> = {
<Editor
apiKey={apiKey}
value={data}
onEditorChange={(e) => setData(e)}
onEditorChange={(e) => {
setData(e);
}}
/>
<textarea
style={{ width: '100%', height: '200px' }}
Expand Down
25 changes: 21 additions & 4 deletions src/test/ts/browser/EditorBehaviorTest.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as Loader from '../alien/Loader';
import { PlatformDetection } from '@ephox/sand';
import * as Loader from '../alien/Loader';

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

import { Assertions, Waiter } from '@ephox/agar';
import { TinyAssertions, TinySelections } from '@ephox/mcagar';
import { EditorEvent, Events, Editor as TinyMCEEditor } from 'tinymce';
import { getTinymce } from '../../../main/ts/TinyMCE';
import { EventStore, VERSIONS } from '../alien/TestHelpers';
import { Editor as TinyMCEEditor, EditorEvent, Events } from 'tinymce';
import { Assertions, Waiter } from '@ephox/agar';
import { TinyAssertions } from '@ephox/mcagar';

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

Expand Down Expand Up @@ -135,6 +135,23 @@ describe('EditorBehaviourTest', () => {

eventStore.clearState();
});
it('INT-3226: onEditorChange is triggered only twice after calling insertContent', async () => {
using ctx = await render({ onEditorChange: eventStore.createHandler('onEditorChange') });
const { editor } = ctx;
editor.setContent('<p>abc</p>');
await Waiter.pTryUntilPredicate('Editor content is set to correct value', () => ctx.editor.getContent() === '<p>abc</p>');
eventStore.clearState();
TinySelections.setSelection(editor, [ 0, 0 ], 1, [ 0, 0 ], 2);
editor.insertContent('e');
await Waiter.pTryUntilPredicate('Editor content is set to correct value', () => ctx.editor.getContent() === '<p>aec</p>');
eventStore.each<string>('onEditorChange', (events) => {
Assertions.assertEq(
'onEditorChange should have been triggered twice',
2,
events.length
);
});
});
})
);
});