Skip to content

Commit

Permalink
fix: skip set_selection onChange events
Browse files Browse the repository at this point in the history
  • Loading branch information
z0al committed Jul 10, 2023
1 parent b7e8ebd commit 13b8d0d
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 37 deletions.
4 changes: 2 additions & 2 deletions cypress/e2e/rich-text/RichTextEditor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('Rich Text Editor', { viewportHeight: 2000 }, () => {
];

beforeEach(() => {
cy.viewport(1000, 2000);
cy.viewport(1280, 720);
richText = new RichTextPage();
richText.visit();
});
Expand Down Expand Up @@ -392,7 +392,7 @@ describe('Rich Text Editor', { viewportHeight: 2000 }, () => {

// temporarily skipped. Snapshots don't match. Will be fixed in a follow up PR
// eslint-disable-next-line
it.skip('runs initial normalization without triggering a value change', () => {
it('runs initial normalization without triggering a value change', () => {
cy.setInitialValue(validDocumentThatRequiresNormalization);

cy.reload();
Expand Down
19 changes: 4 additions & 15 deletions packages/rich-text/src/RichTextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ import { FieldExtensionSDK } from '@contentful/app-sdk';
import { EntityProvider } from '@contentful/field-editor-reference';
import { FieldConnector } from '@contentful/field-editor-shared';
import * as Contentful from '@contentful/rich-text-types';
import { Document } from '@contentful/rich-text-types';
import { Plate, PlateProvider } from '@udecode/plate-core';
import { css, cx } from 'emotion';
import deepEquals from 'fast-deep-equal';
import noop from 'lodash/noop';

import { ContentfulEditorIdProvider, getContentfulEditorId } from './ContentfulEditorProvider';
import { createOnChangeCallback } from './helpers/callbacks';
import { toSlateValue } from './helpers/toSlateValue';
import { normalizeInitialValue } from './internal/misc';
import { getPlugins, disableCorePlugins } from './plugins';
Expand Down Expand Up @@ -43,8 +41,6 @@ export const ConnectedRichTextEditor = (props: ConnectedProps) => {
[sdk, onAction, restrictedMarks]
);

const handleChange = props.onChange;

const initialValue = React.useMemo(() => {
return normalizeInitialValue(
{
Expand All @@ -55,14 +51,6 @@ export const ConnectedRichTextEditor = (props: ConnectedProps) => {
);
}, [props.value, plugins]);

const onChange = React.useMemo(
() =>
createOnChangeCallback((document: Document) => {
handleChange?.(document);
}),
[handleChange]
);

const classNames = cx(
styles.editor,
props.minHeight !== undefined ? css({ minHeight: props.minHeight }) : undefined,
Expand All @@ -79,13 +67,13 @@ export const ConnectedRichTextEditor = (props: ConnectedProps) => {
initialValue={initialValue}
plugins={plugins}
disableCorePlugins={disableCorePlugins}
onChange={onChange}>
>
{!props.isToolbarHidden && (
<StickyToolbarWrapper isDisabled={props.isDisabled}>
<Toolbar isDisabled={props.isDisabled} />
</StickyToolbarWrapper>
)}
<SyncEditorValue incomingValue={initialValue} />
<SyncEditorValue incomingValue={initialValue} onChange={props.onChange} />
<Plate
id={id}
editableProps={{
Expand Down Expand Up @@ -117,7 +105,8 @@ const RichTextEditor = (props: Props) => {
field={sdk.field}
isInitiallyDisabled={isInitiallyDisabled}
isEmptyValue={isEmptyValue}
isEqualValues={deepEquals}>
isEqualValues={deepEquals}
>
{({ lastRemoteValue, disabled, setValue }) => (
<ConnectedRichTextEditor
{...otherProps}
Expand Down
27 changes: 26 additions & 1 deletion packages/rich-text/src/SyncEditorValue.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import * as React from 'react';

import * as Contentful from '@contentful/rich-text-types';
import { usePlateActions } from '@udecode/plate-core';
import equal from 'fast-deep-equal';

import { createOnChangeCallback } from './helpers/callbacks';
import { usePlateSelectors } from './internal/hooks';
import { withoutNormalizing } from './internal/misc';
import { isNode, getEndPoint } from './internal/queries';
Expand Down Expand Up @@ -37,6 +40,7 @@ const setEditorContent = (editor: PlateEditor, nodes?: Node[]): void => {

export type SyncEditorStateProps = {
incomingValue?: Value;
onChange?: (doc: Contentful.Document) => unknown;
};

/**
Expand All @@ -47,8 +51,29 @@ export type SyncEditorStateProps = {
* where we can no longer access the editor instance outside the Plate
* provider.
*/
export const SyncEditorValue = ({ incomingValue }: SyncEditorStateProps) => {
export const SyncEditorValue = ({ incomingValue, onChange }: SyncEditorStateProps) => {
const editor = usePlateSelectors().editor();
const setEditorOnChange = usePlateActions().onChange();

React.useEffect(() => {
const cb = createOnChangeCallback(onChange);

setEditorOnChange({
fn: (document) => {
console.log(editor.operations);
// Skip irrelevant events e.g. mouse selection
const operations = editor?.operations.filter((op) => {
return op.type !== 'set_selection';
});

if (operations.length === 0) {
return;
}

cb(document);
},
});
}, [editor, onChange, setEditorOnChange]);

// Cache latest editor value to avoid unnecessary updates
const lastIncomingValue = React.useRef(incomingValue);
Expand Down
23 changes: 4 additions & 19 deletions packages/rich-text/src/helpers/callbacks.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,20 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { toContentfulDocument } from '@contentful/contentful-slatejs-adapter';
import { Document } from '@contentful/rich-text-types';
import equal from 'fast-deep-equal';
import debounce from 'lodash/debounce';

import schema from '../constants/Schema';
import { removeInternalMarks } from './removeInternalMarks';

export const createOnChangeCallback = (handler?: (value: Document) => void) => {
// Cache previous value to avoid firing the handler unnecessarily
//
// Note: We are not using lodash/memoize here to avoid memory leaks
// due to having an infinite cache while we only care about the last
// value.
let cache: unknown = null;

return debounce((document: unknown) => {
if (equal(document, cache)) {
return;
}

cache = document;
export const createOnChangeCallback = (handler?: (value: Document) => void) =>
debounce((document: unknown) => {
const doc = removeInternalMarks(
toContentfulDocument({
// eslint-disable-next-line -- parameter type is not exported @typescript-eslint/no-explicit-any
document: document as any,
schema: schema,
// eslint-disable-next-line -- parameter type is not exported @typescript-eslint/no-explicit-any
}) as any
);
// eslint-disable-next-line -- correct parameter type is not defined @typescript-eslint/no-explicit-any

const cleanedDocument = removeInternalMarks(doc as Record<string, any>);
handler?.(cleanedDocument);
}, 500);
};

0 comments on commit 13b8d0d

Please sign in to comment.