From 203c70abee851d83ae9b94d909a37a7e0972a74b Mon Sep 17 00:00:00 2001 From: "Jiuqing Song (from Dev Box)" Date: Thu, 21 Nov 2024 16:52:25 -0800 Subject: [PATCH] #2878 Provide a callback function to allow fixup model before write back --- .../lib/coreApi/setContentModel/setContentModel.ts | 2 ++ .../lib/editor/core/createEditorCore.ts | 1 + .../coreApi/setContentModel/setContentModelTest.ts | 5 +++++ .../test/editor/core/createEditorCoreTest.ts | 4 ++++ .../lib/editor/EditorCore.ts | 12 +++++++++++- .../lib/editor/EditorOptions.ts | 12 +++++++++++- 6 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/roosterjs-content-model-core/lib/coreApi/setContentModel/setContentModel.ts b/packages/roosterjs-content-model-core/lib/coreApi/setContentModel/setContentModel.ts index 19d3da8e411..0cf950d85fe 100644 --- a/packages/roosterjs-content-model-core/lib/coreApi/setContentModel/setContentModel.ts +++ b/packages/roosterjs-content-model-core/lib/coreApi/setContentModel/setContentModel.ts @@ -29,6 +29,8 @@ export const setContentModel: SetContentModel = (core, model, option, onNodeCrea modelToDomContext.onNodeCreated = onNodeCreated; + core.onFixUpModel?.(model); + const selection = contentModelToDom( core.logicalRoot.ownerDocument, core.logicalRoot, diff --git a/packages/roosterjs-content-model-core/lib/editor/core/createEditorCore.ts b/packages/roosterjs-content-model-core/lib/editor/core/createEditorCore.ts index 7dbea590e45..886b03dc7ee 100644 --- a/packages/roosterjs-content-model-core/lib/editor/core/createEditorCore.ts +++ b/packages/roosterjs-content-model-core/lib/editor/core/createEditorCore.ts @@ -47,6 +47,7 @@ export function createEditorCore(contentDiv: HTMLDivElement, options: EditorOpti domHelper: createDOMHelper(contentDiv), ...getPluginState(corePlugins), disposeErrorHandler: options.disposeErrorHandler, + onFixUpModel: options.onFixUpModel, experimentalFeatures: options.experimentalFeatures ? [...options.experimentalFeatures] : [], }; } diff --git a/packages/roosterjs-content-model-core/test/coreApi/setContentModel/setContentModelTest.ts b/packages/roosterjs-content-model-core/test/coreApi/setContentModel/setContentModelTest.ts index 201e90ae96c..6dc06d93946 100644 --- a/packages/roosterjs-content-model-core/test/coreApi/setContentModel/setContentModelTest.ts +++ b/packages/roosterjs-content-model-core/test/coreApi/setContentModel/setContentModelTest.ts @@ -114,10 +114,13 @@ describe('setContentModel', () => { const mockedRange = { type: 'image', } as any; + const mockedOnFixUpModel = jasmine.createSpy('fixupModel'); contentModelToDomSpy.and.returnValue(mockedRange); core.environment.modelToDomSettings.builtIn = defaultOption; + (core as any).onFixUpModel = mockedOnFixUpModel; + setContentModel(core, mockedModel, additionalOption); expect(createModelToDomContextSpy).toHaveBeenCalledWith( @@ -133,6 +136,8 @@ describe('setContentModel', () => { mockedContext ); expect(setDOMSelectionSpy).toHaveBeenCalledWith(core, mockedRange); + expect(mockedOnFixUpModel).toHaveBeenCalledWith(mockedModel); + expect(mockedOnFixUpModel).toHaveBeenCalledBefore(contentModelToDomSpy); }); it('no default option, with shadow edit', () => { diff --git a/packages/roosterjs-content-model-core/test/editor/core/createEditorCoreTest.ts b/packages/roosterjs-content-model-core/test/editor/core/createEditorCoreTest.ts index b4af3645965..197c55c1306 100644 --- a/packages/roosterjs-content-model-core/test/editor/core/createEditorCoreTest.ts +++ b/packages/roosterjs-content-model-core/test/editor/core/createEditorCoreTest.ts @@ -101,6 +101,7 @@ describe('createEditorCore', () => { domHelper: mockedDOMHelper, disposeErrorHandler: undefined, experimentalFeatures: [], + onFixUpModel: undefined, ...additionalResult, }); @@ -149,6 +150,7 @@ describe('createEditorCore', () => { const mockedDisposeErrorHandler = 'DISPOSE' as any; const mockedGenerateColorKey = 'KEY' as any; const mockedKnownColors = 'COLORS' as any; + const mockedOnFixUpModel = 'FIXUP' as any; const mockedOptions = { coreApiOverride: { a: 'b', @@ -159,6 +161,7 @@ describe('createEditorCore', () => { disposeErrorHandler: mockedDisposeErrorHandler, generateColorKey: mockedGenerateColorKey, knownColors: mockedKnownColors, + onFixUpModel: mockedOnFixUpModel, } as any; runTest(mockedDiv, mockedOptions, { @@ -181,6 +184,7 @@ describe('createEditorCore', () => { darkColorHandler: mockedDarkColorHandler, trustedHTMLHandler: mockedTrustHtmlHandler, disposeErrorHandler: mockedDisposeErrorHandler, + onFixUpModel: mockedOnFixUpModel, }); expect(DarkColorHandlerImpl.createDarkColorHandler).toHaveBeenCalledWith( diff --git a/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts b/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts index 46d00bd3733..aa8b6816238 100644 --- a/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts +++ b/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts @@ -7,7 +7,10 @@ import type { DOMEventRecord } from '../parameter/DOMEventRecord'; import type { Snapshot } from '../parameter/Snapshot'; import type { EntityState } from '../parameter/FormatContentModelContext'; import type { DarkColorHandler } from '../context/DarkColorHandler'; -import type { ContentModelDocument } from '../contentModel/blockGroup/ContentModelDocument'; +import type { + ContentModelDocument, + ReadonlyContentModelDocument, +} from '../contentModel/blockGroup/ContentModelDocument'; import type { DOMSelection } from '../selection/DOMSelection'; import type { DomToModelOptionForCreateModel } from '../context/DomToModelOption'; import type { EditorContext } from '../context/EditorContext'; @@ -370,6 +373,13 @@ export interface EditorCore extends PluginState { */ readonly disposeErrorHandler?: (plugin: EditorPlugin, error: Error) => void; + /** + * An optional callback function that will be invoked before write content model back to editor. + * This is used for make sure model can satisfy some customized requirement + * @param model The model to fix up + */ + readonly onFixUpModel?: (model: ReadonlyContentModelDocument) => void; + /** * Enabled experimental features */ diff --git a/packages/roosterjs-content-model-types/lib/editor/EditorOptions.ts b/packages/roosterjs-content-model-types/lib/editor/EditorOptions.ts index 4bc635e03f1..aad4ea14219 100644 --- a/packages/roosterjs-content-model-types/lib/editor/EditorOptions.ts +++ b/packages/roosterjs-content-model-types/lib/editor/EditorOptions.ts @@ -7,7 +7,10 @@ import type { ContentModelSegmentFormat } from '../contentModel/format/ContentMo import type { CoreApiMap } from './EditorCore'; import type { DomToModelOption } from '../context/DomToModelOption'; import type { ModelToDomOption } from '../context/ModelToDomOption'; -import type { ContentModelDocument } from '../contentModel/blockGroup/ContentModelDocument'; +import type { + ContentModelDocument, + ReadonlyContentModelDocument, +} from '../contentModel/blockGroup/ContentModelDocument'; import type { Snapshots } from '../parameter/Snapshot'; import type { TrustedHTMLHandler } from '../parameter/TrustedHTMLHandler'; @@ -68,6 +71,13 @@ export interface ContentModelOptions { */ defaultSegmentFormat?: ContentModelSegmentFormat; + /** + * An optional callback function that will be invoked before write content model back to editor. + * This is used for make sure model can satisfy some customized requirement + * @param model The model to fix up + */ + onFixUpModel?: (model: ReadonlyContentModelDocument) => void; + /** * @deprecated */