diff --git a/packages/roosterjs-content-model-plugins/lib/imageEdit/ImageEditPlugin.ts b/packages/roosterjs-content-model-plugins/lib/imageEdit/ImageEditPlugin.ts index cc452f88552..6cccb46a8e4 100644 --- a/packages/roosterjs-content-model-plugins/lib/imageEdit/ImageEditPlugin.ts +++ b/packages/roosterjs-content-model-plugins/lib/imageEdit/ImageEditPlugin.ts @@ -3,13 +3,12 @@ import { canRegenerateImage } from './utils/canRegenerateImage'; import { checkIfImageWasResized, isASmallImage } from './utils/imageEditUtils'; import { createImageWrapper } from './utils/createImageWrapper'; import { Cropper } from './Cropper/cropperContext'; -import { getContentModelImage } from './utils/getContentModelImage'; import { getDropAndDragHelpers } from './utils/getDropAndDragHelpers'; import { getHTMLImageOptions } from './utils/getHTMLImageOptions'; +import { getSelectedImageMetadata } from './utils/updateImageEditInfo'; import { ImageEditElementClass } from './types/ImageEditElementClass'; import { Resizer } from './Resizer/resizerContext'; import { Rotator } from './Rotator/rotatorContext'; -import { updateImageEditInfo } from './utils/updateImageEditInfo'; import { updateRotateHandle } from './Rotator/updateRotateHandle'; import { updateWrapper } from './utils/updateWrapper'; import { @@ -126,16 +125,11 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin { image: HTMLImageElement, apiOperation?: ImageEditOperation ) { - const contentModelImage = getContentModelImage(editor); const imageSpan = image.parentElement; - if ( - !contentModelImage || - !imageSpan || - (imageSpan && !isElementOfType(imageSpan, 'span')) - ) { + if (!imageSpan || (imageSpan && !isElementOfType(imageSpan, 'span'))) { return; } - this.imageEditInfo = updateImageEditInfo(contentModelImage, image); + this.imageEditInfo = getSelectedImageMetadata(editor, image); this.lastSrc = image.getAttribute('src'); this.imageHTMLOptions = getHTMLImageOptions(editor, this.options, this.imageEditInfo); const { @@ -412,7 +406,7 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin { this.shadowSpan ) { editor.formatContentModel( - (model, context) => { + model => { const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs( model, false diff --git a/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/applyChange.ts b/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/applyChange.ts index c80b6de56ee..af259f85fdb 100644 --- a/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/applyChange.ts +++ b/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/applyChange.ts @@ -1,7 +1,7 @@ import { checkEditInfoState } from './checkEditInfoState'; import { generateDataURL } from './generateDataURL'; import { getGeneratedImageSize } from './generateImageSize'; -import { updateImageEditInfo } from './updateImageEditInfo'; +import { getSelectedImageMetadata, updateImageEditInfo } from './updateImageEditInfo'; import type { ContentModelImage, IEditor, @@ -28,7 +28,7 @@ export function applyChange( editingImage?: HTMLImageElement ) { let newSrc = ''; - const initEditInfo = updateImageEditInfo(contentModelImage, editingImage ?? image) ?? undefined; + const initEditInfo = getSelectedImageMetadata(editor, editingImage ?? image) ?? undefined; const state = checkEditInfoState(editInfo, initEditInfo); switch (state) { @@ -64,11 +64,11 @@ export function applyChange( if (newSrc == editInfo.src) { // If newSrc is the same with original one, it means there is only size change, but no rotation, no cropping, // so we don't need to keep edit info, we can delete it - updateImageEditInfo(contentModelImage, image, null); + updateImageEditInfo(contentModelImage, null); } else { // Otherwise, save the new edit info to the image so that next time when we edit the same image, we know // the edit info - updateImageEditInfo(contentModelImage, image, editInfo); + updateImageEditInfo(contentModelImage, editInfo); } // Write back the change to image, and set its new size diff --git a/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/getContentModelImage.ts b/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/getContentModelImage.ts deleted file mode 100644 index 6074e4c8db1..00000000000 --- a/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/getContentModelImage.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { getSelectedSegments } from 'roosterjs-content-model-dom'; -import type { ContentModelImage, IEditor } from 'roosterjs-content-model-types'; - -/** - * @internal - */ -export function getContentModelImage(editor: IEditor): ContentModelImage | null { - const model = editor.getContentModelCopy('disconnected'); - const selectedSegments = getSelectedSegments(model, false /*includeFormatHolder*/); - if (selectedSegments.length == 1 && selectedSegments[0].segmentType == 'Image') { - return selectedSegments[0] as ContentModelImage; - } - return null; -} diff --git a/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/getSelectedContentModelImage.ts b/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/getSelectedContentModelImage.ts new file mode 100644 index 00000000000..3d9085f8778 --- /dev/null +++ b/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/getSelectedContentModelImage.ts @@ -0,0 +1,19 @@ +import { getSelectedSegments } from 'roosterjs-content-model-dom'; +import type { + ReadonlyContentModelImage, + ShallowMutableContentModelDocument, +} from 'roosterjs-content-model-types'; + +/** + * @internal + */ +export function getSelectedContentModelImage( + model: ShallowMutableContentModelDocument +): ReadonlyContentModelImage | null { + const selectedSegments = getSelectedSegments(model, false /*includeFormatHolder*/); + if (selectedSegments.length == 1 && selectedSegments[0].segmentType == 'Image') { + return selectedSegments[0]; + } + + return null; +} diff --git a/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/updateImageEditInfo.ts b/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/updateImageEditInfo.ts index bd981eef7d2..7edf511774d 100644 --- a/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/updateImageEditInfo.ts +++ b/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/updateImageEditInfo.ts @@ -1,15 +1,19 @@ +import { getSelectedContentModelImage } from './getSelectedContentModelImage'; import { updateImageMetadata } from 'roosterjs-content-model-dom'; -import type { ContentModelImage, ImageMetadataFormat } from 'roosterjs-content-model-types'; +import type { + ContentModelImage, + IEditor, + ImageMetadataFormat, +} from 'roosterjs-content-model-types'; /** * @internal */ export function updateImageEditInfo( contentModelImage: ContentModelImage, - image: HTMLImageElement, newImageMetadata?: ImageMetadataFormat | null -): ImageMetadataFormat { - const imageInfo = updateImageMetadata( +) { + updateImageMetadata( contentModelImage, newImageMetadata !== undefined ? format => { @@ -18,7 +22,6 @@ export function updateImageEditInfo( } : undefined ); - return imageInfo || getInitialEditInfo(image); } function getInitialEditInfo(image: HTMLImageElement): ImageMetadataFormat { @@ -35,3 +38,23 @@ function getInitialEditInfo(image: HTMLImageElement): ImageMetadataFormat { angleRad: 0, }; } + +/** + * @internal + * @returns + */ +export function getSelectedImageMetadata( + editor: IEditor, + image: HTMLImageElement +): ImageMetadataFormat { + let imageMetadata: ImageMetadataFormat = getInitialEditInfo(image); + editor.formatContentModel(model => { + const selectedImage = getSelectedContentModelImage(model); + if (selectedImage) { + imageMetadata = { ...imageMetadata, ...selectedImage.dataset }; + } + return false; + }); + + return imageMetadata; +} diff --git a/packages/roosterjs-content-model-plugins/test/imageEdit/ImageEditPluginTest.ts b/packages/roosterjs-content-model-plugins/test/imageEdit/ImageEditPluginTest.ts index 982c97361d8..4b6c7dba2f5 100644 --- a/packages/roosterjs-content-model-plugins/test/imageEdit/ImageEditPluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/imageEdit/ImageEditPluginTest.ts @@ -1,5 +1,5 @@ import { ContentModelDocument } from 'roosterjs-content-model-types'; -import { getContentModelImage } from '../../lib/imageEdit/utils/getContentModelImage'; +import { getSelectedImageMetadata } from '../../lib/imageEdit/utils/updateImageEditInfo'; import { ImageEditPlugin } from '../../lib/imageEdit/ImageEditPlugin'; import { initEditor } from '../TestHelper'; @@ -44,18 +44,22 @@ describe('ImageEditPlugin', () => { const editor = initEditor('image_edit', [plugin], model); it('flip', () => { + const image = new Image(); + image.src = 'test'; plugin.initialize(editor); plugin.flipImage('horizontal'); - const imageModel = getContentModelImage(editor); - expect(imageModel!.dataset['editingInfo']).toBeTruthy(); + const dataset = getSelectedImageMetadata(editor, image); + expect(dataset).toBeTruthy(); plugin.dispose(); }); it('rotate', () => { + const image = new Image(); + image.src = 'test'; plugin.initialize(editor); plugin.rotateImage(Math.PI / 2); - const imageModel = getContentModelImage(editor); - expect(imageModel!.dataset['editingInfo']).toBeTruthy(); + const dataset = getSelectedImageMetadata(editor, image); + expect(dataset).toBeTruthy(); plugin.dispose(); }); }); diff --git a/packages/roosterjs-content-model-plugins/test/imageEdit/utils/getContentModelImageTest.ts b/packages/roosterjs-content-model-plugins/test/imageEdit/utils/getSelectedContentModelImageTest.ts similarity index 84% rename from packages/roosterjs-content-model-plugins/test/imageEdit/utils/getContentModelImageTest.ts rename to packages/roosterjs-content-model-plugins/test/imageEdit/utils/getSelectedContentModelImageTest.ts index 420faf69ac8..c4c152e3ca5 100644 --- a/packages/roosterjs-content-model-plugins/test/imageEdit/utils/getContentModelImageTest.ts +++ b/packages/roosterjs-content-model-plugins/test/imageEdit/utils/getSelectedContentModelImageTest.ts @@ -1,13 +1,7 @@ -import { ContentModelDocument, IEditor } from 'roosterjs-content-model-types'; -import { getContentModelImage } from '../../../lib/imageEdit/utils/getContentModelImage'; - -describe('getContentModelImage', () => { - const createEditor = (model: ContentModelDocument) => { - return { - getContentModelCopy: (mode: 'clean' | 'disconnected') => model, - }; - }; +import { ContentModelDocument } from 'roosterjs-content-model-types'; +import { getSelectedContentModelImage } from '../../../lib/imageEdit/utils/getSelectedContentModelImage'; +describe('getSelectedContentModelImage', () => { it('should return image model', () => { const model: ContentModelDocument = { blockGroupType: 'Document', @@ -44,8 +38,7 @@ describe('getContentModelImage', () => { textColor: '#000000', }, }; - const editor = createEditor(model); - const result = getContentModelImage(editor); + const result = getSelectedContentModelImage(model); expect(result).toEqual({ segmentType: 'Image', src: 'test', @@ -98,8 +91,7 @@ describe('getContentModelImage', () => { textColor: '#000000', }, }; - const editor = createEditor(model); - const result = getContentModelImage(editor); + const result = getSelectedContentModelImage(model); expect(result).toEqual(null); }); }); diff --git a/packages/roosterjs-content-model-plugins/test/imageEdit/utils/updateImageEditInfoTest.ts b/packages/roosterjs-content-model-plugins/test/imageEdit/utils/updateImageEditInfoTest.ts index d69448d5ec6..90ce33e4961 100644 --- a/packages/roosterjs-content-model-plugins/test/imageEdit/utils/updateImageEditInfoTest.ts +++ b/packages/roosterjs-content-model-plugins/test/imageEdit/utils/updateImageEditInfoTest.ts @@ -1,17 +1,82 @@ +import * as updateImageMetadata from 'roosterjs-content-model-dom/lib/modelApi/metadata/updateImageMetadata'; +import { ContentModelDocument } from 'roosterjs-content-model-types'; import { createImage } from 'roosterjs-content-model-dom'; -import { updateImageEditInfo } from '../../../lib/imageEdit/utils/updateImageEditInfo'; +import { initEditor } from '../../TestHelper'; +import { + getSelectedImageMetadata, + updateImageEditInfo, +} from '../../../lib/imageEdit/utils/updateImageEditInfo'; + +const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Image', + src: 'test', + format: { + fontFamily: 'Calibri', + fontSize: '11pt', + textColor: 'rgb(0, 0, 0)', + id: 'image_0', + maxWidth: '1800px', + }, + dataset: { + editingInfo: JSON.stringify({ + src: 'test', + }), + }, + isSelectedAsImageSelection: true, + isSelected: true, + }, + ], + format: {}, + segmentFormat: { + fontFamily: 'Calibri', + fontSize: '11pt', + textColor: 'rgb(0, 0, 0)', + }, + }, + ], + format: { + fontFamily: 'Calibri', + fontSize: '11pt', + textColor: '#000000', + }, +}; describe('updateImageEditInfo', () => { - it('get image edit info', () => { - const image = document.createElement('img'); + it('update image edit info', () => { + const updateImageMetadataSpy = spyOn(updateImageMetadata, 'updateImageMetadata'); const contentModelImage = createImage('test'); - const result = updateImageEditInfo(contentModelImage, image, { - widthPx: 10, - heightPx: 10, - }); - expect(result).toEqual({ + updateImageEditInfo(contentModelImage, { widthPx: 10, heightPx: 10, }); + expect(updateImageMetadataSpy).toHaveBeenCalled(); + }); +}); + +describe('getSelectedImageMetadata', () => { + it('get image edit info', () => { + const editor = initEditor('updateImageEditInfo', [], model); + const image = new Image(10, 10); + const metadata = getSelectedImageMetadata(editor, image); + const expected = { + src: '', + widthPx: 0, + heightPx: 0, + naturalWidth: 0, + naturalHeight: 0, + leftPercent: 0, + rightPercent: 0, + topPercent: 0, + bottomPercent: 0, + angleRad: 0, + editingInfo: '{"src":"test"}', + }; + expect(metadata).toEqual(expected); }); });