diff --git a/.changeset/thick-years-shop.md b/.changeset/thick-years-shop.md new file mode 100644 index 0000000000..24cd3e09a4 --- /dev/null +++ b/.changeset/thick-years-shop.md @@ -0,0 +1,5 @@ +--- +"@udecode/plate-core": minor +--- + +add `normalizeInitialValue` prop to `Plate`. When `true`, it will normalize the initial value passed to the `editor` once it's created. This is useful when adding normalization rules on already existing content. Default is `false`. diff --git a/docs/docs/guides/Plate.md b/docs/docs/guides/Plate.md index 1e52a45d54..adccbef38a 100644 --- a/docs/docs/guides/Plate.md +++ b/docs/docs/guides/Plate.md @@ -94,6 +94,12 @@ props if `editor` is defined. - Initial value of the editor. +### `normalizeInitialValue` + +- When `true`, it will normalize the initial value passed to the `editor` once it gets created. +- This is useful when adding normalization rules on already existing content. +- Default is `false`. + ### `options` - Options stored by plugin key. diff --git a/packages/core/src/components/Plate.spec.tsx b/packages/core/src/components/Plate.spec.tsx index 259dda6a19..41739cb984 100644 --- a/packages/core/src/components/Plate.spec.tsx +++ b/packages/core/src/components/Plate.spec.tsx @@ -1,5 +1,8 @@ import React from 'react'; import { render } from '@testing-library/react'; +import { isEqual, memoize } from 'lodash'; +import { createEditor, Editor, NodeEntry, Transforms } from 'slate'; +import { PlatePlugin, TEditor } from '../types'; import { Plate } from './Plate'; describe('Plate', () => { @@ -9,3 +12,88 @@ describe('Plate', () => { expect(1).toBe(1); }); }); + +describe('Plate', () => { + it('should trigger normalize if normalizeInitialValue set', () => { + const fn = jest.fn((e: TEditor, [node, path]: NodeEntry) => { + if ( + Editor.isBlock(e, node) && + path?.length && + !isEqual((node as any).path, path) + ) { + Transforms.setNodes(e, { path } as any, { at: path }); + } + }); + + const plugins: PlatePlugin[] = memoize((): PlatePlugin[] => [ + { + withOverrides: (e) => { + const { normalizeNode } = e; + e.normalizeNode = (n: NodeEntry) => { + fn(e, n); + normalizeNode(n); + }; + return e; + }, + }, + ])(); + + const editor = createEditor(); + + render( + + ); + + expect(fn).toBeCalled(); + + expect(editor.children).toStrictEqual([ + { children: [{ text: '' }], path: [0] }, + ]); + }); + + it('should not trigger normalize if normalizeInitialValue is not set to true', () => { + const fn = jest.fn((e: TEditor, [node, path]: NodeEntry) => { + if ( + Editor.isBlock(e, node) && + path?.length && + !isEqual((node as any).path, path) + ) { + Transforms.setNodes(e, { path } as any, { at: path }); + } + }); + + const plugins: PlatePlugin[] = memoize((): PlatePlugin[] => [ + { + withOverrides: (e) => { + const { normalizeNode } = e; + e.normalizeNode = (n: NodeEntry) => { + fn(e, n); + normalizeNode(n); + }; + return e; + }, + }, + ])(); + + const editor = createEditor(); + + render( + + ); + + expect(fn).not.toBeCalled(); + + expect(editor.children).not.toStrictEqual([ + { children: [{ text: '' }], path: [0] }, + ]); + }); +}); diff --git a/packages/core/src/hooks/usePlate/usePlate.ts b/packages/core/src/hooks/usePlate/usePlate.ts index 684d3e0f85..001c372aa4 100644 --- a/packages/core/src/hooks/usePlate/usePlate.ts +++ b/packages/core/src/hooks/usePlate/usePlate.ts @@ -18,6 +18,7 @@ export const usePlate = ({ plugins, onChange, editableProps, + normalizeInitialValue, }: UsePlateOptions) => { usePlateEffects({ id, @@ -27,6 +28,7 @@ export const usePlate = ({ editor, value, options, + normalizeInitialValue, }); return { diff --git a/packages/core/src/hooks/usePlate/usePlateEffects.ts b/packages/core/src/hooks/usePlate/usePlateEffects.ts index de71498a14..554d1a86f2 100644 --- a/packages/core/src/hooks/usePlate/usePlateEffects.ts +++ b/packages/core/src/hooks/usePlate/usePlateEffects.ts @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { createEditor } from 'slate'; +import { createEditor, Editor } from 'slate'; import { createHistoryPlugin } from '../../plugins/createHistoryPlugin'; import { createReactPlugin } from '../../plugins/createReactPlugin'; import { usePlateActions } from '../../stores/plate/plate.actions'; @@ -24,6 +24,7 @@ export const usePlateEffects = ({ components, options, initialValue, + normalizeInitialValue, plugins, }: UsePlateEffectsOptions) => { const { @@ -114,4 +115,10 @@ export const usePlateEffects = ({ storePlugins, setEditor, ]); + + useEffect(() => { + if (storeEditor && normalizeInitialValue) { + Editor.normalize(storeEditor, { force: true }); + } + }, [storeEditor, normalizeInitialValue]); }; diff --git a/packages/core/src/types/UsePlateEffectsOptions.ts b/packages/core/src/types/UsePlateEffectsOptions.ts index bd8fe998f7..7bf299f0a2 100644 --- a/packages/core/src/types/UsePlateEffectsOptions.ts +++ b/packages/core/src/types/UsePlateEffectsOptions.ts @@ -28,4 +28,11 @@ export interface UsePlateEffectsOptions * @see {@link EditorId} */ components?: Record; + + /** + * When `true`, it will normalize the initial value passed to the `editor` once it gets created. + * This is useful when adding normalization rules on already existing content. + * @default false + */ + normalizeInitialValue?: boolean; }