From db6f8d83a4bb993d3970ac27e7477c1beb96501f Mon Sep 17 00:00:00 2001 From: Bryan Valverde U Date: Fri, 11 Oct 2024 14:21:06 -0600 Subject: [PATCH] Bump packages to 9.11.2 (#2826) * Add a ID to the TempDiv, to be able to identify on blur was caused for copy/cut #2813 * delete image range * remove selection * fix build * WIP * clean info when delete image * WIP * WIP * add unit tests * nit * Remove style "white-space" from empty paragraph (#2820) * Remove white-space style for empty paragraph * improve * Fix #2804 (#2821) * touch images * Do not treat image without src as empty image (#2823) * update versions.json * update versions --------- Co-authored-by: Julia Roldi (from Dev Box) Co-authored-by: Julia Roldi <87443959+juliaroldi@users.noreply.github.com> Co-authored-by: Jiuqing Song --- .../lib/corePlugin/cache/domIndexerImpl.ts | 12 + .../corePlugin/cache/domIndexerImplTest.ts | 58 + .../lib/modelApi/common/normalizeParagraph.ts | 14 + .../modelApi/common/normalizeParagraphTest.ts | 30 + .../lib/imageEdit/ImageEditPlugin.ts | 30 +- .../imageEdit/utils/getDropAndDragHelpers.ts | 6 +- .../test/imageEdit/ImageEditPluginTest.ts | 1388 ++++++++++++++++- .../utils/getDropAndDragHelpersTest.ts | 3 +- versions.json | 8 +- 9 files changed, 1491 insertions(+), 58 deletions(-) diff --git a/packages/roosterjs-content-model-core/lib/corePlugin/cache/domIndexerImpl.ts b/packages/roosterjs-content-model-core/lib/corePlugin/cache/domIndexerImpl.ts index 3887b42dbbb..001b80a0fde 100644 --- a/packages/roosterjs-content-model-core/lib/corePlugin/cache/domIndexerImpl.ts +++ b/packages/roosterjs-content-model-core/lib/corePlugin/cache/domIndexerImpl.ts @@ -1,5 +1,7 @@ import { EmptySegmentFormat, + addCode, + addLink, createParagraph, createSelectionMarker, createText, @@ -448,6 +450,16 @@ export class DomIndexerImpl implements DomIndexer { const marker = createSelectionMarker(first.format); newSegments.push(marker); + if (startOffset < (textNode.nodeValue ?? '').length) { + if (first.link) { + addLink(marker, first.link); + } + + if (first.code) { + addCode(marker, first.code); + } + } + selectable = marker; endOffset = startOffset; } else if (endOffset > startOffset) { diff --git a/packages/roosterjs-content-model-core/test/corePlugin/cache/domIndexerImplTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/cache/domIndexerImplTest.ts index 378a7addb88..85c4310d0b6 100644 --- a/packages/roosterjs-content-model-core/test/corePlugin/cache/domIndexerImplTest.ts +++ b/packages/roosterjs-content-model-core/test/corePlugin/cache/domIndexerImplTest.ts @@ -12,6 +12,7 @@ import { import { CacheSelection, ContentModelDocument, + ContentModelLink, ContentModelSegment, DOMSelection, } from 'roosterjs-content-model-types'; @@ -934,6 +935,63 @@ describe('domIndexerImpl.reconcileSelection', () => { ], }); }); + + it('Existing text has link', () => { + const node = document.createTextNode('test') as any; + const newRangeEx: DOMSelection = { + type: 'range', + range: createRange(node, 2), + isReverted: false, + }; + const link: ContentModelLink = { + dataset: {}, + format: { + href: 'test', + }, + }; + const paragraph = createParagraph(); + const segment = createText('', {}, link); + + paragraph.segments.push(segment); + domIndexerImpl.onSegment(node, paragraph, [segment]); + + const result = domIndexerImpl.reconcileSelection(model, newRangeEx); + + const segment1: ContentModelSegment = { + segmentType: 'Text', + text: 'te', + format: {}, + link, + }; + const segment2: ContentModelSegment = { + segmentType: 'Text', + text: 'st', + format: {}, + link, + }; + + expect(result).toBeTrue(); + expect(node.__roosterjsContentModel).toEqual({ + paragraph, + segments: [segment1, segment2], + }); + expect(paragraph).toEqual({ + blockType: 'Paragraph', + format: {}, + segments: [ + segment1, + { + segmentType: 'SelectionMarker', + format: {}, + isSelected: true, + link, + }, + segment2, + ], + }); + expect(setSelectionSpy).not.toHaveBeenCalled(); + expect(model.hasRevertedRangeSelection).toBeFalsy(); + }); }); describe('domIndexerImpl.reconcileChildList', () => { diff --git a/packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts b/packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts index c970442bb8e..c86efc34dae 100644 --- a/packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts +++ b/packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts @@ -38,6 +38,8 @@ export function normalizeParagraph(paragraph: ReadonlyContentModelParagraph) { mutateBlock(paragraph).segments.pop(); } } + + normalizeParagraphStyle(paragraph); } if (!isWhiteSpacePreserved(paragraph.format.whiteSpace)) { @@ -51,6 +53,18 @@ export function normalizeParagraph(paragraph: ReadonlyContentModelParagraph) { moveUpSegmentFormat(paragraph); } +function normalizeParagraphStyle(paragraph: ReadonlyContentModelParagraph) { + // New paragraph should not have white-space style + if ( + paragraph.format.whiteSpace && + paragraph.segments.every( + seg => seg.segmentType == 'Br' || seg.segmentType == 'SelectionMarker' + ) + ) { + delete mutateBlock(paragraph).format.whiteSpace; + } +} + function removeEmptySegments(block: ReadonlyContentModelParagraph) { for (let j = block.segments.length - 1; j >= 0; j--) { if (isSegmentEmpty(block.segments[j])) { diff --git a/packages/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts index ca033384432..1de5d7c3e3b 100644 --- a/packages/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts +++ b/packages/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts @@ -809,4 +809,34 @@ describe('Move up format', () => { cachedElement: mockedCache, }); }); + + it('Empty paragraph has white-space style', () => { + const para = createParagraph(false, { whiteSpace: 'pre-wrap' }); + const br = createBr(); + + para.segments.push(br); + + normalizeParagraph(para); + + expect(para).toEqual({ + blockType: 'Paragraph', + segments: [br], + format: {}, + }); + }); + + it('Paragraph has content and white-space style', () => { + const para = createParagraph(false, { whiteSpace: 'pre-wrap' }); + const text = createText('test'); + + para.segments.push(text); + + normalizeParagraph(para); + + expect(para).toEqual({ + blockType: 'Paragraph', + segments: [text], + format: { whiteSpace: 'pre-wrap' }, + }); + }); }); diff --git a/packages/roosterjs-content-model-plugins/lib/imageEdit/ImageEditPlugin.ts b/packages/roosterjs-content-model-plugins/lib/imageEdit/ImageEditPlugin.ts index fb3a13ef529..554941a44f2 100644 --- a/packages/roosterjs-content-model-plugins/lib/imageEdit/ImageEditPlugin.ts +++ b/packages/roosterjs-content-model-plugins/lib/imageEdit/ImageEditPlugin.ts @@ -14,10 +14,10 @@ import { Resizer } from './Resizer/resizerContext'; import { Rotator } from './Rotator/rotatorContext'; import { updateRotateHandle } from './Rotator/updateRotateHandle'; import { updateWrapper } from './utils/updateWrapper'; - import { ChangeSource, getSafeIdSelector, + getSelectedParagraphs, isElementOfType, isNodeOfType, mutateBlock, @@ -254,7 +254,10 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin { } } - private applyFormatWithContentModel( + /** + * EXPOSED FOR TESTING PURPOSE ONLY + */ + protected applyFormatWithContentModel( editor: IEditor, isCropMode: boolean, shouldSelectImage: boolean, @@ -262,6 +265,7 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin { ) { let editingImageModel: ContentModelImage | undefined; const selection = editor.getDOMSelection(); + editor.formatContentModel( model => { const editingImage = getSelectedImage(model); @@ -269,6 +273,7 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin { ? editingImage : findEditingImage(model); let result = false; + if ( shouldSelectImage || previousSelectedImage?.image != editingImage?.image || @@ -301,6 +306,16 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin { image.isSelected = shouldSelectImage; image.isSelectedAsImageSelection = shouldSelectImage; delete image.dataset.isEditing; + + if (selection?.type == 'range' && !selection.range.collapsed) { + const selectedParagraphs = getSelectedParagraphs(model, true); + const isImageInRange = selectedParagraphs.some(paragraph => + paragraph.segments.includes(image) + ); + if (isImageInRange) { + image.isSelected = true; + } + } } ); @@ -314,6 +329,7 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin { this.isEditing = false; this.isCropMode = false; + if ( editingImage && selection?.type == 'image' && @@ -404,6 +420,7 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin { if (this.imageEditInfo) { this.startEditing(editor, image, ['resize', 'rotate']); if (this.selectedImage && this.imageEditInfo && this.wrapper && this.clonedImage) { + const isMobileOrTable = !!editor.getEnvironment().isMobileOrTablet; this.dndHelpers = [ ...getDropAndDragHelpers( this.wrapper, @@ -429,7 +446,8 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin { this.wasImageResized = true; } }, - this.zoomScale + this.zoomScale, + isMobileOrTable ), ...getDropAndDragHelpers( this.wrapper, @@ -460,7 +478,8 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin { ); } }, - this.zoomScale + this.zoomScale, + isMobileOrTable ), ]; @@ -555,7 +574,8 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin { this.isCropMode = true; } }, - this.zoomScale + this.zoomScale, + !!editor.getEnvironment().isMobileOrTablet ), ]; updateWrapper( diff --git a/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/getDropAndDragHelpers.ts b/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/getDropAndDragHelpers.ts index 4e99265fba1..ae4b83d29d4 100644 --- a/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/getDropAndDragHelpers.ts +++ b/packages/roosterjs-content-model-plugins/lib/imageEdit/utils/getDropAndDragHelpers.ts @@ -16,7 +16,8 @@ export function getDropAndDragHelpers( elementClass: ImageEditElementClass, helper: DragAndDropHandler, updateWrapper: (context: DragAndDropContext, _handle: HTMLElement) => void, - zoomScale: number + zoomScale: number, + useTouch: boolean ): DragAndDropHelper[] { return getEditElements(wrapper, elementClass).map( element => @@ -31,7 +32,8 @@ export function getDropAndDragHelpers( }, updateWrapper, helper, - zoomScale + zoomScale, + useTouch ) ); } diff --git a/packages/roosterjs-content-model-plugins/test/imageEdit/ImageEditPluginTest.ts b/packages/roosterjs-content-model-plugins/test/imageEdit/ImageEditPluginTest.ts index c9a6851b20d..d00cc2cde4a 100644 --- a/packages/roosterjs-content-model-plugins/test/imageEdit/ImageEditPluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/imageEdit/ImageEditPluginTest.ts @@ -1,5 +1,7 @@ +import * as applyChange from '../../lib/imageEdit/utils/applyChange'; import * as findImage from '../../lib/imageEdit/utils/findEditingImage'; import * as getSelectedImage from '../../lib/imageEdit/utils/getSelectedImage'; +import * as normalizeImageSelection from '../../lib/imageEdit/utils/normalizeImageSelection'; import { ChangeSource, createImage, createParagraph } from 'roosterjs-content-model-dom'; import { getSelectedImageMetadata } from '../../lib/imageEdit/utils/updateImageEditInfo'; import { ImageEditPlugin } from '../../lib/imageEdit/ImageEditPlugin'; @@ -12,55 +14,54 @@ import { IEditor, } from 'roosterjs-content-model-types'; -const model: ContentModelDocument = { - blockGroupType: 'Document', - blocks: [ - { - segments: [ - { - isSelected: true, - segmentType: 'SelectionMarker', - format: { - fontFamily: 'Calibri', - fontSize: '11pt', - textColor: 'rgb(0, 0, 0)', - }, - }, - { - src: - 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAsJCQcJCQcJCQkJCwkJCQkJCQsJCwsMCwsLDA0QDB...', - isSelectedAsImageSelection: true, - segmentType: 'Image', - isSelected: true, - format: { - fontFamily: 'Calibri', - fontSize: '11pt', - textColor: 'rgb(0, 0, 0)', - id: 'image_0', - maxWidth: '1123px', +describe('ImageEditPlugin', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + segments: [ + { + isSelected: true, + segmentType: 'SelectionMarker', + format: { + fontFamily: 'Calibri', + fontSize: '11pt', + textColor: 'rgb(0, 0, 0)', + }, }, - dataset: { - isEditing: 'true', + { + src: + 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAsJCQcJCQcJCQkJCwkJCQkJCQsJCwsMCwsLDA0QDB...', + isSelectedAsImageSelection: true, + segmentType: 'Image', + isSelected: true, + format: { + fontFamily: 'Calibri', + fontSize: '11pt', + textColor: 'rgb(0, 0, 0)', + id: 'image_0', + maxWidth: '1123px', + }, + dataset: { + isEditing: 'true', + }, }, + ], + segmentFormat: { + fontFamily: 'Calibri', + fontSize: '11pt', + textColor: 'rgb(0, 0, 0)', }, - ], - segmentFormat: { - fontFamily: 'Calibri', - fontSize: '11pt', - textColor: 'rgb(0, 0, 0)', + blockType: 'Paragraph', + format: {}, }, - blockType: 'Paragraph', - format: {}, + ], + format: { + fontFamily: 'Calibri', + fontSize: '11pt', + textColor: '#000000', }, - ], - format: { - fontFamily: 'Calibri', - fontSize: '11pt', - textColor: '#000000', - }, -}; - -describe('ImageEditPlugin', () => { + }; let editor: IEditor; let mockedEnvironment: EditorEnvironment; let attachDomEventSpy: jasmine.Spy; @@ -424,6 +425,96 @@ describe('ImageEditPlugin', () => { plugin.dispose(); }); + it('mouseDown on non edit image', () => { + const mockedImage = { + getAttribute: getAttributeSpy, + }; + const plugin = new ImageEditPlugin(); + plugin.initialize(editor); + getDOMSelectionSpy.and.returnValue({ + type: 'image', + image: mockedImage, + }); + plugin.onPluginEvent({ + eventType: 'mouseDown', + rawEvent: { + button: 0, + target: new Image(), + } as any, + }); + expect(formatContentModelSpy).not.toHaveBeenCalled(); + plugin.dispose(); + }); + + it('mouseDown on text', () => { + const mockedImage = { + getAttribute: getAttributeSpy, + }; + const plugin = new ImageEditPlugin(); + plugin.initialize(editor); + getDOMSelectionSpy.and.returnValue({ + type: 'image', + image: mockedImage, + }); + plugin.onPluginEvent({ + eventType: 'mouseUp', + rawEvent: { + button: 0, + target: new Image(), + } as any, + }); + getDOMSelectionSpy.and.returnValue({ + type: 'range', + range: {} as any, + }); + plugin.onPluginEvent({ + eventType: 'mouseDown', + rawEvent: { + button: 0, + target: new Text(), + } as any, + }); + expect(formatContentModelSpy).toHaveBeenCalled(); + expect(formatContentModelSpy).toHaveBeenCalledTimes(1); + plugin.dispose(); + }); + + it('mouseDown on same line', () => { + const target = { + contains: () => true, + }; + const mockedImage = { + getAttribute: getAttributeSpy, + }; + const plugin = new ImageEditPlugin(); + plugin.initialize(editor); + getDOMSelectionSpy.and.returnValue({ + type: 'image', + image: mockedImage, + }); + plugin.onPluginEvent({ + eventType: 'mouseUp', + rawEvent: { + button: 0, + target: new Image(), + } as any, + }); + getDOMSelectionSpy.and.returnValue({ + type: 'range', + range: {} as any, + }); + plugin.onPluginEvent({ + eventType: 'mouseDown', + rawEvent: { + button: 0, + target: target, + } as any, + }); + expect(formatContentModelSpy).toHaveBeenCalled(); + expect(formatContentModelSpy).toHaveBeenCalledTimes(1); + plugin.dispose(); + }); + it('dragImage', () => { const mockedImage = { id: 'image_0', @@ -522,3 +613,1212 @@ describe('ImageEditPlugin', () => { expect(editor.setEditorStyle).toHaveBeenCalledWith('imageEditCaretColor', null); }); }); + +class TestPlugin extends ImageEditPlugin { + public setIsEditing(isEditing: boolean) { + this.isEditing = isEditing; + } + + public setEditingInfo(image: HTMLImageElement) { + this.imageEditInfo = { + src: image.getAttribute('src') || '', + widthPx: image.clientWidth, + heightPx: image.clientHeight, + naturalWidth: image.naturalWidth, + naturalHeight: image.naturalHeight, + leftPercent: 0, + rightPercent: 0, + topPercent: 0, + bottomPercent: 0, + angleRad: 0, + }; + } + + public applyFormatWithContentModel( + editor: IEditor, + isCropMode: boolean, + shouldSelectImage: boolean, + isApiOperation?: boolean + ) { + super.applyFormatWithContentModel(editor, isCropMode, shouldSelectImage, isApiOperation); + } +} + +interface TestOptions { + setImageStyle?: boolean; + removeImageStyle?: boolean; + shouldApplyChange?: boolean; + shouldNormalizeSelection?: boolean; + shouldCleanInfo?: boolean; +} + +describe('ImageEditPlugin - applyFormatWithContentModel', () => { + function runTest( + model: ContentModelDocument, + expectedModel: ContentModelDocument, + isEditing: boolean, + isCropMode: boolean, + shouldSelectImage: boolean, + isApiOperation: boolean, + moreOptions: Partial = {} + ) { + const plugin = new TestPlugin(); + let editor: IEditor | null = initEditor('image_edit', [plugin], model); + const setEditorStyleSpy = spyOn(editor, 'setEditorStyle'); + setEditorStyleSpy.and.callThrough(); + const cleanInfoSpy = spyOn(plugin, 'cleanInfo').and.callThrough(); + const applyChangeSpy = spyOn(applyChange, 'applyChange'); + const normalizeImageSelectionSpy = spyOn( + normalizeImageSelection, + 'normalizeImageSelection' + ); + plugin.initialize(editor); + const mockedImage = document.createElement('img'); + document.body.appendChild(mockedImage); + mockedImage.src = 'test'; + plugin.setEditingInfo(mockedImage); + plugin.startRotateAndResize(editor, mockedImage); + plugin.setIsEditing(isEditing); + plugin.applyFormatWithContentModel(editor, isCropMode, shouldSelectImage, isApiOperation); + const newModel = editor.getContentModelCopy('disconnected'); + + if (moreOptions.removeImageStyle !== undefined) { + expect(setEditorStyleSpy).toHaveBeenCalledWith('imageEdit', null); + expect(setEditorStyleSpy).toHaveBeenCalledWith('imageEditCaretColor', null); + } + + if (moreOptions.setImageStyle !== undefined) { + expect(setEditorStyleSpy).toHaveBeenCalledWith( + 'imageEdit', + 'outline-style:none!important;', + [`span:has(>img)`] + ); + expect(setEditorStyleSpy).toHaveBeenCalledWith( + 'imageEditCaretColor', + 'caret-color: transparent;' + ); + } + + if (moreOptions.shouldApplyChange) { + expect(applyChangeSpy).toHaveBeenCalled(); + } else { + expect(applyChangeSpy).not.toHaveBeenCalled(); + } + + if (moreOptions.shouldNormalizeSelection) { + expect(normalizeImageSelectionSpy).toHaveBeenCalled(); + } else { + expect(normalizeImageSelectionSpy).not.toHaveBeenCalled(); + } + if (moreOptions.shouldCleanInfo) { + expect(cleanInfoSpy).toHaveBeenCalled(); + } else { + expect(cleanInfoSpy).not.toHaveBeenCalled(); + } + expect(newModel).toEqual(expectedModel); + plugin.dispose(); + editor.dispose(); + editor = null; + } + + it('image to text', () => { + const model1: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: { + isEditing: 'true', + }, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + isSelected: true, + segmentType: 'SelectionMarker', + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + segmentFormat: {}, + blockType: 'Paragraph', + format: {}, + }, + ], + format: {}, + }; + const expectedModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: {}, + alt: undefined, + title: undefined, + isSelectedAsImageSelection: undefined, + isSelected: undefined, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: undefined, + }, + { + isSelected: true, + segmentType: 'SelectionMarker', + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: undefined, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + cachedElement: undefined, + isImplicit: undefined, + }, + ], + + format: {}, + }; + runTest(model1, expectedModel, true, false, false, false, { + shouldApplyChange: true, + shouldCleanInfo: true, + removeImageStyle: true, + }); + }); + + it('text to image', () => { + const testModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: {}, + isSelectedAsImageSelection: true, + isSelected: true, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + segmentFormat: {}, + blockType: 'Paragraph', + format: {}, + }, + ], + format: {}, + }; + const expectedModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: { + isEditing: 'true', + }, + alt: undefined, + title: undefined, + isSelectedAsImageSelection: true, + isSelected: true, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: undefined, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + cachedElement: undefined, + isImplicit: undefined, + }, + ], + + format: {}, + }; + runTest(testModel, expectedModel, false, false, false, false, { + shouldApplyChange: false, + shouldCleanInfo: false, + setImageStyle: true, + }); + }); + + it('image to image', () => { + const testModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: { + isEditing: 'true', + }, + }, + { + src: 'test2', + segmentType: 'Image', + format: {}, + dataset: {}, + isSelectedAsImageSelection: true, + isSelected: true, + }, + ], + segmentFormat: {}, + blockType: 'Paragraph', + format: {}, + }, + ], + format: {}, + }; + const expectedModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: {}, + alt: undefined, + title: undefined, + isSelectedAsImageSelection: undefined, + isSelected: undefined, + }, + { + src: 'test2', + segmentType: 'Image', + format: {}, + dataset: { + isEditing: 'true', + }, + isSelectedAsImageSelection: true, + isSelected: true, + alt: undefined, + title: undefined, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + cachedElement: undefined, + isImplicit: undefined, + }, + ], + + format: {}, + }; + runTest(testModel, expectedModel, true, false, false, false, { + shouldApplyChange: true, + shouldCleanInfo: true, + removeImageStyle: true, + setImageStyle: true, + }); + }); + + it('image in a table to text', () => { + const testModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + widths: [120], + rows: [ + { + height: 22, + cells: [ + { + spanAbove: false, + spanLeft: false, + isHeader: false, + blockGroupType: 'TableCell', + blocks: [ + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: { + isEditing: 'true', + }, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + }, + ], + format: {}, + dataset: {}, + }, + ], + format: {}, + }, + ], + blockType: 'Table', + format: { + borderCollapse: true, + useBorderBox: true, + }, + dataset: {}, + }, + { + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + }, + ], + format: {}, + }; + const expectedModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + widths: [122], + rows: [ + { + height: 24, + cells: [ + { + spanAbove: false, + spanLeft: false, + isHeader: false, + blockGroupType: 'TableCell', + blocks: [ + { + segments: [ + { + src: 'test', + isSelectedAsImageSelection: undefined, + segmentType: 'Image', + isSelected: undefined, + format: {}, + dataset: {}, + alt: undefined, + title: undefined, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + cachedElement: undefined, + isImplicit: undefined, + }, + ], + format: { + width: '120px', + height: '22px', + }, + dataset: {}, + isSelected: undefined, + cachedElement: undefined, + }, + ], + format: {}, + cachedElement: undefined, + }, + ], + blockType: 'Table', + format: { + borderCollapse: true, + useBorderBox: true, + }, + dataset: {}, + cachedElement: undefined, + }, + { + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: undefined, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + cachedElement: undefined, + isImplicit: undefined, + }, + ], + format: {}, + }; + runTest(testModel, expectedModel, true, false, false, false, { + shouldApplyChange: true, + shouldCleanInfo: true, + removeImageStyle: true, + }); + }); + + it('text to image in a table ', () => { + const testModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + widths: [120], + rows: [ + { + height: 22, + cells: [ + { + spanAbove: false, + spanLeft: false, + isHeader: false, + blockGroupType: 'TableCell', + blocks: [ + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: {}, + isSelectedAsImageSelection: true, + isSelected: true, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + }, + ], + format: {}, + dataset: {}, + }, + ], + format: {}, + }, + ], + blockType: 'Table', + format: { + borderCollapse: true, + useBorderBox: true, + }, + dataset: {}, + }, + { + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + }, + ], + format: {}, + }; + const expectedModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + widths: [122], + rows: [ + { + height: 24, + cells: [ + { + spanAbove: false, + spanLeft: false, + isHeader: false, + blockGroupType: 'TableCell', + blocks: [ + { + segments: [ + { + src: 'test', + isSelectedAsImageSelection: true, + segmentType: 'Image', + isSelected: true, + format: {}, + dataset: { + isEditing: 'true', + }, + alt: undefined, + title: undefined, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + cachedElement: undefined, + isImplicit: undefined, + }, + ], + format: { + width: '120px', + height: '22px', + }, + dataset: {}, + isSelected: undefined, + cachedElement: undefined, + }, + ], + format: {}, + cachedElement: undefined, + }, + ], + blockType: 'Table', + format: { + borderCollapse: true, + useBorderBox: true, + }, + dataset: {}, + cachedElement: undefined, + }, + { + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: undefined, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + cachedElement: undefined, + isImplicit: undefined, + }, + ], + format: {}, + }; + runTest(testModel, expectedModel, false, false, false, false, { + shouldApplyChange: false, + shouldCleanInfo: false, + setImageStyle: true, + }); + }); + + it('image to image in a table ', () => { + const testModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + widths: [120], + rows: [ + { + height: 22, + cells: [ + { + spanAbove: false, + spanLeft: false, + isHeader: false, + blockGroupType: 'TableCell', + blocks: [ + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: { + isEditing: 'true', + }, + isSelectedAsImageSelection: undefined, + isSelected: undefined, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + }, + ], + format: {}, + dataset: {}, + }, + ], + format: {}, + }, + ], + blockType: 'Table', + format: { + borderCollapse: true, + useBorderBox: true, + }, + dataset: {}, + }, + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: {}, + isSelectedAsImageSelection: true, + isSelected: true, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + }, + ], + format: {}, + }; + const expectedModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + widths: [122], + rows: [ + { + height: 24, + cells: [ + { + spanAbove: false, + spanLeft: false, + isHeader: false, + blockGroupType: 'TableCell', + blocks: [ + { + segments: [ + { + src: 'test', + isSelectedAsImageSelection: undefined, + segmentType: 'Image', + isSelected: undefined, + format: {}, + dataset: {}, + alt: undefined, + title: undefined, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + cachedElement: undefined, + isImplicit: undefined, + }, + ], + format: { + width: '120px', + height: '22px', + }, + dataset: {}, + isSelected: undefined, + cachedElement: undefined, + }, + ], + format: {}, + cachedElement: undefined, + }, + ], + blockType: 'Table', + format: { + borderCollapse: true, + useBorderBox: true, + }, + dataset: {}, + cachedElement: undefined, + }, + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: { + isEditing: 'true', + }, + isSelectedAsImageSelection: true, + isSelected: true, + alt: undefined, + title: undefined, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + cachedElement: undefined, + isImplicit: undefined, + }, + ], + format: {}, + }; + runTest(testModel, expectedModel, true, false, false, false, { + shouldApplyChange: true, + shouldCleanInfo: true, + setImageStyle: true, + removeImageStyle: true, + }); + }); + + it('image in a list to text', () => { + const testModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + formatHolder: { + isSelected: false, + segmentType: 'SelectionMarker', + format: {}, + }, + levels: [ + { + listType: 'OL', + format: { + startNumberOverride: 1, + listStyleType: 'decimal', + }, + dataset: { + editingInfo: + '{"applyListStyleFromLevel":false,"orderedStyleType":1}', + }, + }, + ], + blockType: 'BlockGroup', + format: {}, + blockGroupType: 'ListItem', + blocks: [ + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: { + isEditing: 'true', + }, + }, + ], + segmentFormat: {}, + blockType: 'Paragraph', + format: {}, + }, + ], + }, + { + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + isSelected: true, + segmentType: 'SelectionMarker', + format: {}, + }, + ], + segmentFormat: {}, + blockType: 'Paragraph', + format: {}, + }, + ], + format: {}, + }; + const expectedModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + formatHolder: { + isSelected: false, + segmentType: 'SelectionMarker', + format: {}, + }, + levels: [ + { + listType: 'OL', + format: { + startNumberOverride: 1, + listStyleType: 'decimal', + }, + dataset: { + editingInfo: + '{"applyListStyleFromLevel":false,"orderedStyleType":1}', + }, + }, + ], + blockType: 'BlockGroup', + format: {}, + blockGroupType: 'ListItem', + blocks: [ + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: {}, + isSelectedAsImageSelection: undefined, + isSelected: undefined, + alt: undefined, + title: undefined, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + cachedElement: undefined, + isImplicit: undefined, + }, + ], + cachedElement: undefined, + }, + { + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: undefined, + }, + { + isSelected: true, + segmentType: 'SelectionMarker', + format: {}, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + cachedElement: undefined, + isImplicit: undefined, + }, + ], + format: {}, + }; + runTest(testModel, expectedModel, true, false, false, false, { + shouldApplyChange: true, + shouldCleanInfo: true, + removeImageStyle: true, + }); + }); + + it('text to image in a list', () => { + const testModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + formatHolder: { + isSelected: false, + segmentType: 'SelectionMarker', + format: {}, + }, + levels: [ + { + listType: 'OL', + format: { + startNumberOverride: 1, + listStyleType: 'decimal', + }, + dataset: { + editingInfo: + '{"applyListStyleFromLevel":false,"orderedStyleType":1}', + }, + }, + ], + blockType: 'BlockGroup', + format: {}, + blockGroupType: 'ListItem', + blocks: [ + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: {}, + isSelectedAsImageSelection: true, + isSelected: true, + }, + ], + segmentFormat: {}, + blockType: 'Paragraph', + format: {}, + }, + ], + }, + { + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + segmentFormat: {}, + blockType: 'Paragraph', + format: {}, + }, + ], + format: {}, + }; + const expectedModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + formatHolder: { + isSelected: false, + segmentType: 'SelectionMarker', + format: {}, + }, + levels: [ + { + listType: 'OL', + format: { + startNumberOverride: 1, + listStyleType: 'decimal', + }, + dataset: { + editingInfo: + '{"applyListStyleFromLevel":false,"orderedStyleType":1}', + }, + }, + ], + blockType: 'BlockGroup', + format: {}, + blockGroupType: 'ListItem', + blocks: [ + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: { + isEditing: 'true', + }, + isSelectedAsImageSelection: true, + isSelected: true, + alt: undefined, + title: undefined, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + cachedElement: undefined, + isImplicit: undefined, + }, + ], + cachedElement: undefined, + }, + { + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: undefined, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + cachedElement: undefined, + isImplicit: undefined, + }, + ], + format: {}, + }; + runTest(testModel, expectedModel, false, false, false, false, { + shouldApplyChange: false, + shouldCleanInfo: false, + setImageStyle: true, + }); + }); + + it('image in a list to image', () => { + const testModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + formatHolder: { + isSelected: false, + segmentType: 'SelectionMarker', + format: {}, + }, + levels: [ + { + listType: 'OL', + format: { + startNumberOverride: 1, + listStyleType: 'decimal', + }, + dataset: { + editingInfo: + '{"applyListStyleFromLevel":false,"orderedStyleType":1}', + }, + }, + ], + blockType: 'BlockGroup', + format: {}, + blockGroupType: 'ListItem', + blocks: [ + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: { + isEditing: 'true', + }, + isSelectedAsImageSelection: undefined, + isSelected: undefined, + }, + ], + segmentFormat: {}, + blockType: 'Paragraph', + format: {}, + }, + ], + }, + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: {}, + isSelectedAsImageSelection: true, + isSelected: true, + }, + ], + segmentFormat: {}, + blockType: 'Paragraph', + format: {}, + }, + ], + format: {}, + }; + const expectedModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + formatHolder: { + isSelected: false, + segmentType: 'SelectionMarker', + format: {}, + }, + levels: [ + { + listType: 'OL', + format: { + startNumberOverride: 1, + listStyleType: 'decimal', + }, + dataset: { + editingInfo: + '{"applyListStyleFromLevel":false,"orderedStyleType":1}', + }, + }, + ], + blockType: 'BlockGroup', + format: {}, + blockGroupType: 'ListItem', + blocks: [ + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: {}, + isSelectedAsImageSelection: undefined, + isSelected: undefined, + alt: undefined, + title: undefined, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + cachedElement: undefined, + isImplicit: undefined, + }, + ], + cachedElement: undefined, + }, + { + segments: [ + { + src: 'test', + segmentType: 'Image', + format: {}, + dataset: { + isEditing: 'true', + }, + isSelectedAsImageSelection: true, + isSelected: true, + alt: undefined, + title: undefined, + }, + ], + segmentFormat: undefined, + blockType: 'Paragraph', + format: {}, + cachedElement: undefined, + isImplicit: undefined, + }, + ], + format: {}, + }; + runTest(testModel, expectedModel, true, false, false, false, { + shouldApplyChange: true, + shouldCleanInfo: true, + setImageStyle: true, + removeImageStyle: true, + }); + }); +}); diff --git a/packages/roosterjs-content-model-plugins/test/imageEdit/utils/getDropAndDragHelpersTest.ts b/packages/roosterjs-content-model-plugins/test/imageEdit/utils/getDropAndDragHelpersTest.ts index 8a7fd5ccfe1..cdb23cae1a3 100644 --- a/packages/roosterjs-content-model-plugins/test/imageEdit/utils/getDropAndDragHelpersTest.ts +++ b/packages/roosterjs-content-model-plugins/test/imageEdit/utils/getDropAndDragHelpersTest.ts @@ -54,7 +54,8 @@ describe('getDropAndDragHelpers', () => { elementClass, helper, () => {}, - 1 + 1, + false ); expect(JSON.stringify(result)).toEqual(JSON.stringify(expectResult)); } diff --git a/versions.json b/versions.json index 41d078cbf60..f8a1b412407 100644 --- a/versions.json +++ b/versions.json @@ -1,10 +1,6 @@ { "react": "9.0.0", - "main": "9.11.0", + "main": "9.11.2", "legacyAdapter": "8.62.1", - "overrides": { - "roosterjs-content-model-dom": "9.11.1", - "roosterjs-content-model-core": "9.11.1", - "roosterjs-content-model-plugins": "9.11.1" - } + "overrides": {} }