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 beb993f2e97..0400bf8668e 100644 --- a/packages/roosterjs-content-model-core/lib/coreApi/setContentModel/setContentModel.ts +++ b/packages/roosterjs-content-model-core/lib/coreApi/setContentModel/setContentModel.ts @@ -38,6 +38,8 @@ export const setContentModel: SetContentModel = ( 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 15c33e36ed6..14b8dc1f2b9 100644 --- a/packages/roosterjs-content-model-core/test/coreApi/setContentModel/setContentModelTest.ts +++ b/packages/roosterjs-content-model-core/test/coreApi/setContentModel/setContentModelTest.ts @@ -117,10 +117,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( @@ -136,6 +139,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 e97c74fc77e..42144b97f61 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'; @@ -376,6 +379,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 */